mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 12:37:20 +00:00
updated nextjs example
This commit is contained in:
36
examples/nextjs/src/app/(main)/Footer.tsx
Normal file
36
examples/nextjs/src/app/(main)/Footer.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
76
examples/nextjs/src/app/(main)/layout.tsx
Normal file
76
examples/nextjs/src/app/(main)/layout.tsx
Normal 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">&</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>
|
||||
);
|
||||
91
examples/nextjs/src/app/(main)/page.tsx
Normal file
91
examples/nextjs/src/app/(main)/page.tsx
Normal 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."]}
|
||||
/>
|
||||
);
|
||||
33
examples/nextjs/src/app/(main)/ssr/page.tsx
Normal file
33
examples/nextjs/src/app/(main)/ssr/page.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
import { getApp } from "@/bknd";
|
||||
import { config } from "@/bknd";
|
||||
import { serve } from "bknd/adapter/nextjs";
|
||||
|
||||
// if you're not using a local media adapter, or file database,
|
||||
// you can uncomment this line to enable running bknd on edge
|
||||
// since we're using the local media adapter in this example,
|
||||
// we can't use the edge runtime.
|
||||
// export const runtime = "edge";
|
||||
|
||||
const handler = async (request: Request) => {
|
||||
const app = await getApp();
|
||||
return app.fetch(request);
|
||||
};
|
||||
const handler = serve({
|
||||
...config,
|
||||
cleanRequest: {
|
||||
searchParams: ["bknd"],
|
||||
},
|
||||
});
|
||||
|
||||
export const GET = handler;
|
||||
export const POST = handler;
|
||||
|
||||
3
examples/nextjs/src/app/env/route.ts
vendored
3
examples/nextjs/src/app/env/route.ts
vendored
@@ -1,3 +0,0 @@
|
||||
export const GET = async (req: Request) => {
|
||||
return Response.json(process.env);
|
||||
};
|
||||
@@ -1,97 +0,0 @@
|
||||
import Image from "next/image";
|
||||
import { getApi } from "@/bknd";
|
||||
|
||||
export default async function Home() {
|
||||
const api = await getApi();
|
||||
|
||||
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">&</div>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/bknd.svg"
|
||||
alt="bknd logo"
|
||||
width={183}
|
||||
height={59}
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
||||
<li className="mb-2">
|
||||
Get started by editing{" "}
|
||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
|
||||
src/app/page.tsx
|
||||
</code>
|
||||
.
|
||||
</li>
|
||||
<li>Save and see your changes instantly.</li>
|
||||
</ol>
|
||||
|
||||
<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>
|
||||
|
||||
<pre className="text-sm">
|
||||
{JSON.stringify(await api.data.readMany("posts"), null, 2)}
|
||||
</pre>
|
||||
</main>
|
||||
<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="/ssr"
|
||||
>
|
||||
<Image aria-hidden src="/file.svg" alt="File icon" width={16} height={16} />
|
||||
SSR
|
||||
</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>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { getApi } from "@/bknd";
|
||||
|
||||
export default async function SSRPage() {
|
||||
const api = await getApi({ verify: true });
|
||||
const { data } = await api.data.readMany("posts");
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Server-Side Rendered Page</h1>
|
||||
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||
<pre>{JSON.stringify(api.getUser(), null, 2)}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user