mirror of
https://github.com/shishantbiswas/bknd-examples.git
synced 2026-02-27 03:51:17 +00:00
minor refactors to the adapter code
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,3 +12,4 @@ count.txt
|
|||||||
.vinxi
|
.vinxi
|
||||||
todos.json
|
todos.json
|
||||||
data.db
|
data.db
|
||||||
|
public/uploads
|
||||||
@@ -1,10 +1,39 @@
|
|||||||
import type { NextjsBkndConfig } from "bknd/adapter/nextjs";
|
import { type BkndConfig, em, entity, text, boolean } from "bknd";
|
||||||
|
|
||||||
|
// Unrelated to framework adapters
|
||||||
|
import { registerLocalMediaAdapter } from "bknd/adapter/node";
|
||||||
|
|
||||||
|
const local = registerLocalMediaAdapter();
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------- SCHEMA -----------------------
|
||||||
|
// this just for testing
|
||||||
|
const schema = em({
|
||||||
|
todos: entity("todos", {
|
||||||
|
title: text(),
|
||||||
|
done: boolean(),
|
||||||
|
}),
|
||||||
|
post:entity("posts",{
|
||||||
|
title: text(),
|
||||||
|
content: text(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// --------------------- SCHEMA END -----------------------
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
connection: {
|
connection: {
|
||||||
url: "data.db",
|
url: "data.db",
|
||||||
},
|
},
|
||||||
options:{
|
options: {},
|
||||||
|
config: {
|
||||||
}
|
data: schema.toJSON(),
|
||||||
} satisfies NextjsBkndConfig;
|
auth: { enabled: true },
|
||||||
|
media: {
|
||||||
|
enabled: true,
|
||||||
|
adapter: local({
|
||||||
|
path: "./public/uploads",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies BkndConfig;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
// Both work just fine
|
||||||
|
// import { getApp as getBkndApp } from "bknd/adapter/react-router";
|
||||||
import { getApp as getBkndApp } from "bknd/adapter/nextjs";
|
import { getApp as getBkndApp } from "bknd/adapter/nextjs";
|
||||||
import config from "../bknd.config";
|
import config from "../bknd.config";
|
||||||
|
|
||||||
// import { headers } from "next/headers";
|
|
||||||
|
|
||||||
export async function getApi({
|
export async function getApi({
|
||||||
headers,
|
headers,
|
||||||
verify,
|
verify,
|
||||||
@@ -20,5 +20,3 @@ export async function getApi({
|
|||||||
|
|
||||||
return app.getApi();
|
return app.getApi();
|
||||||
}
|
}
|
||||||
|
|
||||||
export { config };
|
|
||||||
|
|||||||
@@ -1,33 +1,34 @@
|
|||||||
import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'
|
import { HeadContent, Scripts, createRootRoute } from "@tanstack/react-router";
|
||||||
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
|
import { TanStackRouterDevtoolsPanel } from "@tanstack/react-router-devtools";
|
||||||
import { TanStackDevtools } from '@tanstack/react-devtools'
|
import { TanStackDevtools } from "@tanstack/react-devtools";
|
||||||
|
import { ClientProvider } from "bknd/client";
|
||||||
|
|
||||||
import appCss from '../styles.css?url'
|
import appCss from "../styles.css?url";
|
||||||
|
|
||||||
export const Route = createRootRoute({
|
export const Route = createRootRoute({
|
||||||
head: () => ({
|
head: () => ({
|
||||||
meta: [
|
meta: [
|
||||||
{
|
{
|
||||||
charSet: 'utf-8',
|
charSet: "utf-8",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'viewport',
|
name: "viewport",
|
||||||
content: 'width=device-width, initial-scale=1',
|
content: "width=device-width, initial-scale=1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'TanStack Start Starter',
|
title: "TanStack Start Starter",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
rel: 'stylesheet',
|
rel: "stylesheet",
|
||||||
href: appCss,
|
href: appCss,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
|
||||||
shellComponent: RootDocument,
|
shellComponent: RootDocument,
|
||||||
})
|
});
|
||||||
|
|
||||||
function RootDocument({ children }: { children: React.ReactNode }) {
|
function RootDocument({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
@@ -36,14 +37,16 @@ function RootDocument({ children }: { children: React.ReactNode }) {
|
|||||||
<HeadContent />
|
<HeadContent />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<ClientProvider verbose baseUrl="http://localhost:3000">
|
||||||
{children}
|
{children}
|
||||||
|
</ClientProvider>
|
||||||
<TanStackDevtools
|
<TanStackDevtools
|
||||||
config={{
|
config={{
|
||||||
position: 'bottom-right',
|
position: "bottom-right",
|
||||||
}}
|
}}
|
||||||
plugins={[
|
plugins={[
|
||||||
{
|
{
|
||||||
name: 'Tanstack Router',
|
name: "Tanstack Router",
|
||||||
render: <TanStackRouterDevtoolsPanel />,
|
render: <TanStackRouterDevtoolsPanel />,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
@@ -51,5 +54,5 @@ function RootDocument({ children }: { children: React.ReactNode }) {
|
|||||||
<Scripts />
|
<Scripts />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +1,21 @@
|
|||||||
import { getApi } from "@/bknd";
|
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { createServerFn } from "@tanstack/react-start";
|
import { useAuth } from "bknd/client";
|
||||||
import { getRequestHeaders } from "@tanstack/react-start/server";
|
|
||||||
import "bknd/dist/styles.css";
|
import "bknd/dist/styles.css";
|
||||||
import { Admin } from "bknd/ui";
|
import { Admin } from "bknd/ui";
|
||||||
|
|
||||||
export const getUser = createServerFn({ method: "GET" }).handler(async () => {
|
|
||||||
const headers = getRequestHeaders();
|
|
||||||
const api = await getApi({ verify: true, headers });
|
|
||||||
return { user: api.getUser() };
|
|
||||||
});
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/admin/$")({
|
export const Route = createFileRoute("/admin/$")({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
loader: async () => {
|
|
||||||
const user = await getUser();
|
|
||||||
return { user };
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const { user } = Route.useLoaderData();
|
const { user } = useAuth();
|
||||||
console.log(user);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Admin
|
<Admin
|
||||||
withProvider={{
|
withProvider={{ user: user }}
|
||||||
user: {
|
|
||||||
email: "ada@example.com",
|
|
||||||
id: "",
|
|
||||||
strategy: "",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
config={{
|
config={{
|
||||||
basepath: "/admin",
|
basepath: "/admin",
|
||||||
logo_return_path: "/../",
|
logo_return_path: "/../",
|
||||||
theme: "system",
|
|
||||||
}}
|
}}
|
||||||
baseUrl="http://localhost:3000"
|
baseUrl="http://localhost:3000"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,25 +1,17 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { config } from "@/bknd";
|
import config from "../../bknd.config";
|
||||||
|
|
||||||
|
// Works fine
|
||||||
import { serve } from "bknd/adapter/nextjs";
|
import { serve } from "bknd/adapter/nextjs";
|
||||||
|
|
||||||
const handler = serve({
|
const handler = serve({
|
||||||
...config,
|
...config,
|
||||||
// cleanRequest: {
|
|
||||||
// depending on what name you used for the catch-all route,
|
|
||||||
// you need to change this to clean it from the request.
|
|
||||||
// searchParams: ["$"],
|
|
||||||
// },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Route = createFileRoute("/api/$")({
|
export const Route = createFileRoute("/api/$")({
|
||||||
server: {
|
server: {
|
||||||
handlers: {
|
handlers: {
|
||||||
ANY: async ({ request }) => {
|
ANY: async ({ request }) => await handler(request),
|
||||||
const res = await handler(request);
|
|
||||||
// console.log("[API] ", res);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,27 +1,142 @@
|
|||||||
import { createFileRoute, Link } from "@tanstack/react-router";
|
import { createFileRoute, Link } from "@tanstack/react-router";
|
||||||
import "../App.css";
|
import "../App.css";
|
||||||
|
import { useAuth } from "bknd/client";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
export const Route = createFileRoute("/")({ component: App });
|
export const Route = createFileRoute("/")({ component: App });
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const { user, verified, register, logout, login } = useAuth();
|
||||||
|
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [mode, setMode] = useState<"login" | "register">("login");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
async function handleSubmit(e: React.SubmitEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
if (mode === "login") {
|
||||||
|
// attempt login
|
||||||
|
await login({ email, password } as any);
|
||||||
|
} else {
|
||||||
|
// attempt register
|
||||||
|
await register({ email, password } as any);
|
||||||
|
}
|
||||||
|
setEmail("");
|
||||||
|
setPassword("");
|
||||||
|
} catch (err: any) {
|
||||||
|
setError(err?.message ?? String(err));
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleLogout() {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await logout();
|
||||||
|
} catch (err: any) {
|
||||||
|
setError(err?.message ?? String(err));
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div>
|
||||||
<header className="App-header">
|
<main style={{ padding: 20 }} className="App-header">
|
||||||
|
<header>
|
||||||
<img
|
<img
|
||||||
src="/tanstack-circle-logo.png"
|
src="/tanstack-circle-logo.png"
|
||||||
alt="TanStack Logo"
|
alt="TanStack Logo"
|
||||||
style={{
|
style={{ width: "100px", height: "100px" }}
|
||||||
width: "100px",
|
|
||||||
height: "100px",
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<Link
|
|
||||||
className="App-link"
|
|
||||||
to="/admin"
|
|
||||||
>
|
|
||||||
Admin Dashboard
|
|
||||||
</Link>
|
|
||||||
</header>
|
</header>
|
||||||
|
<section>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style={{ maxWidth: 420, margin: "0 auto" }}>
|
||||||
|
<h2 style={{margin:"24px 0 "}}>Account</h2>
|
||||||
|
{user ? (
|
||||||
|
<div style={{ gap: 8, display: "flex", flexDirection: "column" }}>
|
||||||
|
<div>
|
||||||
|
<strong>Signed in as:</strong> {user?.email ?? "Unknown"}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Verified:</strong> {verified ? "Yes" : "No"}
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", gap: 8 }}>
|
||||||
|
<button onClick={handleLogout} disabled={loading}>
|
||||||
|
{loading ? "Signing out..." : "Sign out"}
|
||||||
|
</button>
|
||||||
|
<Link to={"/admin" as string}>
|
||||||
|
<button>Go to Admin</button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<form onSubmit={handleSubmit} style={{ display: "grid", gap: 8 }}>
|
||||||
|
<div style={{ display: "flex", gap: 8 }}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setMode("login")}
|
||||||
|
style={{ textDecoration: mode === "login" ? "underline" : "none" }}
|
||||||
|
>
|
||||||
|
Log in
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setMode("register")}
|
||||||
|
style={{ textDecoration: mode === "register" ? "underline" : "none" }}
|
||||||
|
>
|
||||||
|
Register
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
placeholder="Email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
required
|
||||||
|
style={{
|
||||||
|
border: "1px solid white",
|
||||||
|
padding: "4px",
|
||||||
|
borderRadius: "4px"
|
||||||
|
}}
|
||||||
|
type="email"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<input
|
||||||
|
placeholder="Password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
required
|
||||||
|
style={{
|
||||||
|
border: "1px solid white",
|
||||||
|
padding: "4px",
|
||||||
|
borderRadius: "4px"
|
||||||
|
}}
|
||||||
|
type="password"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{error ? <div style={{ color: "#ff6b6b" }}>{error}</div> : null}
|
||||||
|
|
||||||
|
<button type="submit" disabled={loading}>
|
||||||
|
{loading
|
||||||
|
? "Please wait..."
|
||||||
|
: mode === "login"
|
||||||
|
? "Log in"
|
||||||
|
: "Create account"}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user