mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 12:37:20 +00:00
updated react example with todo example
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { createContext, lazy, useEffect, useState, Suspense, Fragment } from "react";
|
||||
import { App } from "bknd";
|
||||
import { Admin } from "bknd/ui";
|
||||
import { checksum } from "bknd/utils";
|
||||
import { em, entity, text } from "bknd/data";
|
||||
import { checksum, secureRandomString } from "bknd/utils";
|
||||
import { boolean, em, entity, text } from "bknd/data";
|
||||
import { SQLocalConnection } from "@bknd/sqlocal";
|
||||
import { Route, Router, Switch } from "wouter";
|
||||
import IndexPage from "~/routes/_index";
|
||||
const Admin = lazy(() => import("~/routes/admin"));
|
||||
import { Center } from "~/components/Center";
|
||||
import { ClientProvider } from "bknd/client";
|
||||
import "bknd/dist/styles.css";
|
||||
|
||||
export default function () {
|
||||
@@ -11,28 +15,65 @@ export default function () {
|
||||
const [hash, setHash] = useState<string>("");
|
||||
|
||||
async function onBuilt(app: App) {
|
||||
setApp(app);
|
||||
setHash(await checksum(app.toJSON()));
|
||||
document.startViewTransition(async () => {
|
||||
setApp(app);
|
||||
setHash(await checksum(app.toJSON()));
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setup({
|
||||
onBuilt,
|
||||
})
|
||||
setup({ onBuilt })
|
||||
.then((app) => console.log("setup", app?.version()))
|
||||
.catch(console.error);
|
||||
}, []);
|
||||
|
||||
if (!app) return null;
|
||||
if (!app)
|
||||
return (
|
||||
<Center>
|
||||
<span className="opacity-20">Loading...</span>
|
||||
</Center>
|
||||
);
|
||||
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Admin key={hash} withProvider={{ api: app.getApi() }} />
|
||||
<Router key={hash}>
|
||||
<Switch>
|
||||
<Route
|
||||
path="/"
|
||||
component={() => (
|
||||
<ClientProvider api={app.getApi()}>
|
||||
<IndexPage app={app} />
|
||||
</ClientProvider>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Route path="/admin/*?">
|
||||
<Suspense>
|
||||
<Admin config={{ basepath: "/admin", logo_return_path: "/../" }} app={app} />
|
||||
</Suspense>
|
||||
</Route>
|
||||
<Route path="*">
|
||||
<Center className="font-mono text-4xl">404</Center>
|
||||
</Route>
|
||||
</Switch>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
const schema = em({
|
||||
todos: entity("todos", {
|
||||
title: text(),
|
||||
done: boolean(),
|
||||
}),
|
||||
});
|
||||
|
||||
// register your schema to get automatic type completion
|
||||
type Database = (typeof schema)["DB"];
|
||||
declare module "bknd/core" {
|
||||
interface DB extends Database {}
|
||||
}
|
||||
|
||||
let initialized = false;
|
||||
export async function setup(opts?: {
|
||||
async function setup(opts?: {
|
||||
beforeBuild?: (app: App) => Promise<void>;
|
||||
onBuilt?: (app: App) => Promise<void>;
|
||||
}) {
|
||||
@@ -40,17 +81,30 @@ export async function setup(opts?: {
|
||||
initialized = true;
|
||||
|
||||
const connection = new SQLocalConnection({
|
||||
databasePath: ":localStorage:",
|
||||
verbose: true,
|
||||
});
|
||||
|
||||
const app = App.create({
|
||||
connection,
|
||||
// an initial config is only applied if the database is empty
|
||||
initialConfig: {
|
||||
data: em({
|
||||
test: entity("test", {
|
||||
name: text(),
|
||||
}),
|
||||
}).toJSON(),
|
||||
data: schema.toJSON(),
|
||||
},
|
||||
options: {
|
||||
// the seed option is only executed if the database was empty
|
||||
seed: async (ctx) => {
|
||||
await ctx.em.mutator("todos").insertMany([
|
||||
{ title: "Learn bknd", done: true },
|
||||
{ title: "Build something cool", done: false },
|
||||
]);
|
||||
|
||||
// @todo: auth is currently not working due to POST request
|
||||
/*await ctx.app.module.auth.createUser({
|
||||
email: "test@bknd.io",
|
||||
password: "12345678",
|
||||
});*/
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
10
examples/react/src/components/Center.tsx
Normal file
10
examples/react/src/components/Center.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { ComponentProps } from "react";
|
||||
|
||||
export function Center(props: ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={"w-full min-h-full flex justify-center items-center " + props.className}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import App from "./App.tsx";
|
||||
import App from "./App";
|
||||
import "./styles.css";
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
|
||||
94
examples/react/src/routes/_index.tsx
Normal file
94
examples/react/src/routes/_index.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import { Center } from "~/components/Center";
|
||||
import type { App } from "bknd";
|
||||
import { useEntityQuery } from "bknd/client";
|
||||
|
||||
export default function IndexPage({ app }: { app: App }) {
|
||||
const user = app.getApi().getUser();
|
||||
const limit = 5;
|
||||
const { data: todos, ...$q } = useEntityQuery("todos", undefined, {
|
||||
limit,
|
||||
});
|
||||
// @ts-ignore
|
||||
const total = todos?.body.meta.total || 0;
|
||||
|
||||
return (
|
||||
<div id="app">
|
||||
<Center className="flex-col gap-10 max-w-96 mx-auto text-lg">
|
||||
<div className="flex flex-col gap-2 items-center">
|
||||
<img src="/bknd.svg" alt="bknd" className="w-48 dark:invert" />
|
||||
<p className="font-mono">local</p>
|
||||
</div>
|
||||
|
||||
<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? ({total})</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 () => {
|
||||
await $q.update({ done: !todo.done }, todo.id);
|
||||
}}
|
||||
/>
|
||||
<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 () => {
|
||||
await $q._delete(todo.id);
|
||||
}}
|
||||
>
|
||||
❌
|
||||
</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) => {
|
||||
const title = formData.get("title") as string;
|
||||
await $q.create({ title });
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<a href="/admin">Go to Admin ➝</a>
|
||||
{/*<div className="opacity-50 text-sm">
|
||||
{user ? (
|
||||
<p>
|
||||
Authenticated as <b>{user.email}</b>
|
||||
</p>
|
||||
) : (
|
||||
<a href="/admin/auth/login">Login</a>
|
||||
)}
|
||||
</div>*/}
|
||||
</div>
|
||||
</Center>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
examples/react/src/routes/admin.tsx
Normal file
9
examples/react/src/routes/admin.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Admin, type BkndAdminProps } from "bknd/ui";
|
||||
import type { App } from "bknd";
|
||||
|
||||
export default function AdminPage({
|
||||
app,
|
||||
...props
|
||||
}: Omit<BkndAdminProps, "withProvider"> & { app: App }) {
|
||||
return <Admin {...props} withProvider={{ api: app.getApi() }} />;
|
||||
}
|
||||
25
examples/react/src/styles.css
Normal file
25
examples/react/src/styles.css
Normal file
@@ -0,0 +1,25 @@
|
||||
/* @todo: currently not working nicely */
|
||||
#app {
|
||||
@import "tailwindcss";
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
@theme {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
}
|
||||
|
||||
width: 100%;
|
||||
min-height: 100dvh;
|
||||
@apply bg-background text-foreground flex;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
Reference in New Issue
Block a user