updated nextjs example

This commit is contained in:
dswbx
2025-03-04 14:39:32 +01:00
parent ab73b02138
commit 4f52537ea0
15 changed files with 440 additions and 211 deletions

View File

@@ -0,0 +1,36 @@
"use client";
import Image from "next/image";
import { usePathname } from "next/navigation";
export function Footer() {
const pathname = usePathname();
return (
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href={pathname === "/" ? "/ssr" : "/"}
>
<Image aria-hidden src="/file.svg" alt="File icon" width={16} height={16} />
{pathname === "/" ? "SSR" : "Home"}
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="/admin"
>
<Image aria-hidden src="/window.svg" alt="Window icon" width={16} height={16} />
Admin
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://bknd.io"
target="_blank"
rel="noopener noreferrer"
>
<Image aria-hidden src="/globe.svg" alt="Globe icon" width={16} height={16} />
Go to bknd.io
</a>
</footer>
);
}

View File

@@ -0,0 +1,76 @@
import Image from "next/image";
import type { ReactNode } from "react";
import { Footer } from "./Footer";
export default async function Layout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
<div className="flex flex-row items-center ">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<div className="ml-3.5 mr-2 font-mono opacity-70">&amp;</div>
<Image
className="dark:invert"
src="/bknd.svg"
alt="bknd logo"
width={183}
height={59}
priority
/>
</div>
{children}
</main>
<Footer />
</div>
);
}
export const Buttons = () => (
<div className="flex gap-4 items-center flex-col sm:flex-row">
<a
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
href="https://docs.bknd.io/integration/nextjs"
target="_blank"
rel="noopener noreferrer"
>
Read our docs
</a>
</div>
);
export const List = ({ items = [] }: { items: ReactNode[] }) => (
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
{items.map((item, i) => (
<li key={i} className={i < items.length - 1 ? "mb-2" : ""}>
{item}
</li>
))}
</ol>
);

View File

@@ -0,0 +1,91 @@
import { getApi } from "@/bknd";
import { revalidatePath } from "next/cache";
import { Fragment } from "react";
import { List } from "@/app/(main)/layout";
export default async function Home() {
// without "{ verify: true }", this page can be static
const api = await getApi();
const limit = 5;
const todos = await api.data.readMany("todos", { limit, sort: "-id" });
const total = todos.body.meta.total;
return (
<>
<Description />
<div className="flex flex-col border border-foreground/15 w-full py-4 px-5 gap-2">
<h2 className="font-mono mb-1 opacity-70">
<code>What's next?</code>
</h2>
<div className="flex flex-col w-full gap-2">
{total > limit && (
<div className="bg-foreground/10 flex justify-center p-1 text-xs rounded text-foreground/40">
{total - limit} more todo(s) hidden
</div>
)}
<div className="flex flex-col gap-3">
{todos.reverse().map((todo) => (
<div className="flex flex-row" key={String(todo.id)}>
<div className="flex flex-row flex-grow items-center gap-3 ml-1">
<input
type="checkbox"
className="flex-shrink-0 cursor-pointer"
defaultChecked={!!todo.done}
onChange={async () => {
"use server";
const api = await getApi();
await api.data.updateOne("todos", todo.id, {
done: !todo.done,
});
revalidatePath("/");
}}
/>
<div className="text-foreground/90 leading-none">{todo.title}</div>
</div>
<button
type="button"
className="cursor-pointer grayscale transition-all hover:grayscale-0 text-xs "
onClick={async () => {
"use server";
const api = await getApi();
await api.data.deleteOne("todos", todo.id);
revalidatePath("/");
}}
>
</button>
</div>
))}
</div>
<form
className="flex flex-row w-full gap-3 mt-2"
key={todos.map((t) => t.id).join()}
action={async (formData: FormData) => {
"use server";
const title = formData.get("title") as string;
const api = await getApi();
await api.data.createOne("todos", { title });
revalidatePath("/");
}}
>
<input
type="text"
name="title"
placeholder="New todo"
className="py-2 px-4 flex flex-grow rounded-sm bg-foreground/10 focus:bg-foreground/20 transition-colors outline-none"
/>
<button type="submit" className="cursor-pointer">
Add
</button>
</form>
</div>
</div>
</>
);
}
const Description = () => (
<List
items={["Get started with a full backend.", "Focus on what matters instead of repetition."]}
/>
);

View File

@@ -0,0 +1,33 @@
import { getApi } from "@/bknd";
import { Buttons, List } from "../layout";
export default async function SSRPage() {
const api = await getApi({ verify: true });
const { data } = await api.data.readMany("todos");
const user = api.getUser();
return (
<>
<List items={data.map((todo) => todo.title)} />
<Buttons />
<p>
{user ? (
<>
Logged in as {user.email}.{" "}
<a className="font-medium underline" href="/api/auth/logout">
Logout
</a>
</>
) : (
<>
Not logged in.{" "}
<a className="font-medium underline" href="/admin/auth/login">
Login
</a>
</>
)}
</p>
</>
);
}