scaffold Vike app with Bati
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
// https://vike.dev/Head
|
||||
|
||||
import logoUrl from "../assets/logo.svg";
|
||||
|
||||
import { ColorSchemeScript } from "@mantine/core";
|
||||
|
||||
export default function HeadDefault() {
|
||||
return (
|
||||
<>
|
||||
<link rel="icon" href={logoUrl} />
|
||||
<ColorSchemeScript />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import vikeReact from "vike-react/config";
|
||||
import type { Config } from "vike/types";
|
||||
import Layout from "../layouts/LayoutDefault.js";
|
||||
|
||||
// Default config (can be overridden by pages)
|
||||
// https://vike.dev/config
|
||||
|
||||
export default {
|
||||
// https://vike.dev/Layout
|
||||
Layout,
|
||||
|
||||
// https://vike.dev/head-tags
|
||||
title: "My Vike App",
|
||||
description: "Demo showcasing Vike",
|
||||
|
||||
passToClient: ["user"],
|
||||
extends: vikeReact,
|
||||
} satisfies Config;
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { OnPageTransitionEndAsync } from "vike/types";
|
||||
|
||||
export const onPageTransitionEnd: OnPageTransitionEndAsync = async () => {
|
||||
console.log("Page transition end");
|
||||
document.querySelector("body")?.classList.remove("page-is-transitioning");
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { OnPageTransitionStartAsync } from "vike/types";
|
||||
|
||||
export const onPageTransitionStart: OnPageTransitionStartAsync = async () => {
|
||||
console.log("Page transition start");
|
||||
document.querySelector("body")?.classList.add("page-is-transitioning");
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
import { usePageContext } from "vike-react/usePageContext";
|
||||
|
||||
export default function Page() {
|
||||
const { is404 } = usePageContext();
|
||||
if (is404) {
|
||||
return (
|
||||
<>
|
||||
<h1>404 Page Not Found</h1>
|
||||
<p>This page could not be found.</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<h1>500 Internal Server Error</h1>
|
||||
<p>Something went wrong.</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Counter } from "./Counter.js";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<h1 css={{ fontWeight: "700", fontSize: "1.875rem", paddingBottom: "1rem" }}>My Vike app</h1>
|
||||
This page is:
|
||||
<ul>
|
||||
<li>Rendered to HTML.</li>
|
||||
<li>
|
||||
Interactive. <Counter />
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { useState } from "react";
|
||||
|
||||
export function Counter() {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
css={{
|
||||
display: "inline-block",
|
||||
border: "1px solid black",
|
||||
borderRadius: "0.25rem",
|
||||
backgroundColor: "#e5e7eb",
|
||||
padding: "4px 8px 4px 8px",
|
||||
fontSize: "0.75rem",
|
||||
fontWeight: "500",
|
||||
textTransform: "uppercase",
|
||||
lineHeight: "1.5",
|
||||
}}
|
||||
onClick={() => setCount((count) => count + 1)}
|
||||
>
|
||||
Counter {count}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { useData } from "vike-react/useData";
|
||||
import type { Data } from "./+data.js";
|
||||
|
||||
export default function Page() {
|
||||
const movie = useData<Data>();
|
||||
return (
|
||||
<>
|
||||
<h1>{movie.title}</h1>
|
||||
Release Date: {movie.release_date}
|
||||
<br />
|
||||
Director: {movie.director}
|
||||
<br />
|
||||
Producer: {movie.producer}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// https://vike.dev/data
|
||||
|
||||
import type { PageContextServer } from "vike/types";
|
||||
import type { MovieDetails } from "../types.js";
|
||||
import { useConfig } from "vike-react/useConfig";
|
||||
|
||||
export type Data = Awaited<ReturnType<typeof data>>;
|
||||
|
||||
export const data = async (pageContext: PageContextServer) => {
|
||||
// https://vike.dev/useConfig
|
||||
const config = useConfig();
|
||||
|
||||
const response = await fetch(`https://brillout.github.io/star-wars/api/films/${pageContext.routeParams.id}.json`);
|
||||
let movie = (await response.json()) as MovieDetails;
|
||||
|
||||
config({
|
||||
// Set <title>
|
||||
title: movie.title,
|
||||
});
|
||||
|
||||
// We remove data we don't need because the data is passed to
|
||||
// the client; we should minimize what is sent over the network.
|
||||
movie = minimize(movie);
|
||||
|
||||
return movie;
|
||||
};
|
||||
|
||||
function minimize(movie: MovieDetails): MovieDetails {
|
||||
const { id, title, release_date, director, producer } = movie;
|
||||
const minimizedMovie = { id, title, release_date, director, producer };
|
||||
return minimizedMovie;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { useData } from "vike-react/useData";
|
||||
import type { Data } from "./+data.js";
|
||||
|
||||
export default function Page() {
|
||||
const movies = useData<Data>();
|
||||
return (
|
||||
<>
|
||||
<h1>Star Wars Movies</h1>
|
||||
<ol>
|
||||
{movies.map(({ id, title, release_date }) => (
|
||||
<li key={id}>
|
||||
<a href={`/star-wars/${id}`}>{title}</a> ({release_date})
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
<p>
|
||||
Source: <a href="https://brillout.github.io/star-wars">brillout.github.io/star-wars</a>.
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// https://vike.dev/data
|
||||
|
||||
import type { Movie, MovieDetails } from "../types.js";
|
||||
import { useConfig } from "vike-react/useConfig";
|
||||
|
||||
export type Data = Awaited<ReturnType<typeof data>>;
|
||||
|
||||
export const data = async () => {
|
||||
// https://vike.dev/useConfig
|
||||
const config = useConfig();
|
||||
|
||||
const response = await fetch("https://brillout.github.io/star-wars/api/films.json");
|
||||
const moviesData = (await response.json()) as MovieDetails[];
|
||||
|
||||
config({
|
||||
// Set <title>
|
||||
title: `${moviesData.length} Star Wars Movies`,
|
||||
});
|
||||
|
||||
// We remove data we don't need because the data is passed to the client; we should
|
||||
// minimize what is sent over the network.
|
||||
const movies = minimize(moviesData);
|
||||
|
||||
return movies;
|
||||
};
|
||||
|
||||
function minimize(movies: MovieDetails[]): Movie[] {
|
||||
return movies.map((movie) => {
|
||||
const { title, release_date, id } = movie;
|
||||
return { title, release_date, id };
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export type Movie = {
|
||||
id: string;
|
||||
title: string;
|
||||
release_date: string;
|
||||
};
|
||||
|
||||
export type MovieDetails = Movie & {
|
||||
director: string;
|
||||
producer: string;
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { Data } from "./+data";
|
||||
import { useData } from "vike-react/useData";
|
||||
import { TodoList } from "./TodoList.js";
|
||||
|
||||
export default function Page() {
|
||||
const data = useData<Data>();
|
||||
return (
|
||||
<>
|
||||
<h1>To-do List</h1>
|
||||
<TodoList initialTodoItems={data.todo} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export const config = {
|
||||
prerender: false,
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
// https://vike.dev/data
|
||||
import { todos } from "../../database/todoItems";
|
||||
import type { PageContextServer } from "vike/types";
|
||||
|
||||
export type Data = {
|
||||
todo: { text: string }[];
|
||||
};
|
||||
|
||||
export default async function data(_pageContext: PageContextServer): Promise<Data> {
|
||||
return { todo: todos };
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { trpc } from "../../trpc/client";
|
||||
import { useState } from "react";
|
||||
|
||||
export function TodoList({ initialTodoItems }: { initialTodoItems: { text: string }[] }) {
|
||||
const [todoItems, setTodoItems] = useState(initialTodoItems);
|
||||
const [newTodo, setNewTodo] = useState("");
|
||||
return (
|
||||
<>
|
||||
<ul>
|
||||
{todoItems.map((todoItem, index) => (
|
||||
// biome-ignore lint:
|
||||
<li key={index}>{todoItem.text}</li>
|
||||
))}
|
||||
</ul>
|
||||
<div>
|
||||
<form
|
||||
onSubmit={async (ev) => {
|
||||
ev.preventDefault();
|
||||
|
||||
// Optimistic UI update
|
||||
setTodoItems((prev) => [...prev, { text: newTodo }]);
|
||||
try {
|
||||
await trpc.onNewTodo.mutate(newTodo);
|
||||
setNewTodo("");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
// rollback
|
||||
setTodoItems((prev) => prev.slice(0, -1));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input type="text" onChange={(ev) => setNewTodo(ev.target.value)} value={newTodo} />
|
||||
<button type="submit">Add to-do</button>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user