diff --git a/examples/demo/.gitignore b/examples/demo/.gitignore index 4e06ffc..00e2e86 100644 --- a/examples/demo/.gitignore +++ b/examples/demo/.gitignore @@ -4,3 +4,6 @@ .env.test.local .env.production.local .env.local + +# Fresh build directory +_fresh/ diff --git a/examples/demo/README.md b/examples/demo/README.md index f605bd9..ddbf7cb 100644 --- a/examples/demo/README.md +++ b/examples/demo/README.md @@ -9,3 +9,9 @@ deno task start ``` This will watch the project directory and restart as necessary. + + +### Testing locally + +Swap out "x-pentagon" and "pentagon" in the `import_map.json` to switch between testing the local library or the latest +release. diff --git a/examples/demo/components/Button.tsx b/examples/demo/components/Button.tsx deleted file mode 100644 index 909d380..0000000 --- a/examples/demo/components/Button.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { JSX } from "preact"; -import { IS_BROWSER } from "$fresh/runtime.ts"; - -export function Button(props: JSX.HTMLAttributes) { - return ( - - - - ); -} diff --git a/examples/demo/islands/SubmitTask.tsx b/examples/demo/islands/SubmitTask.tsx deleted file mode 100644 index ef9c1ec..0000000 --- a/examples/demo/islands/SubmitTask.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { useState } from "preact/hooks"; -import { Input } from "../components/ui/input.tsx"; -import { Button } from "../components/ui/button.tsx"; - -type SubmitTaskProps = { - userId: string; -}; - -export default function SubmitTask({ userId }: SubmitTaskProps) { - const [description, setDescription] = useState(""); - - async function saveTask() { - await fetch("/", { - body: JSON.stringify({ description, userId }), - method: "POST", - }); - alert("Task saved!"); - location.reload(); - } - - return ( -
- setDescription((e.target as any).value)} - /> - -
- ); -} diff --git a/examples/demo/lib/db.ts b/examples/demo/lib/db.ts index abfd7c1..4682940 100644 --- a/examples/demo/lib/db.ts +++ b/examples/demo/lib/db.ts @@ -1,14 +1,14 @@ -import { createPentagon } from "https://deno.land/x/pentagon@v0.0.2/mod.ts"; -import { z } from "https://deno.land/x/zod@v3.21.4/mod.ts"; +import { createPentagon } from "pentagon"; +import { z } from "zod"; export const User = z.object({ - id: z.string().uuid().describe("primary, unique"), + id: z.string().uuid().describe("primary"), createdAt: z.date(), name: z.string(), }); export const TodoTask = z.object({ - id: z.string().uuid().describe("primary, unique"), + id: z.string().uuid().describe("primary"), userId: z.string().uuid(), createdAt: z.date(), description: z.string(), @@ -21,7 +21,7 @@ export const db = createPentagon(kv, { users: { schema: User, relations: { - tasks: ["tasks", [TodoTask], undefined, "userId"], + tasks: ["tasks", [TodoTask], "", "userId"], }, }, tasks: { @@ -32,12 +32,12 @@ export const db = createPentagon(kv, { }, }); -export async function createUser() { - return await db.users.create({ +export function createUser() { + return db.users.create({ data: { id: crypto.randomUUID(), createdAt: new Date(), - name: "Random", + name: "Anonymous", }, }); } diff --git a/examples/demo/main.ts b/examples/demo/main.ts index 984b0ae..25dd27d 100644 --- a/examples/demo/main.ts +++ b/examples/demo/main.ts @@ -3,13 +3,13 @@ /// /// /// +/// import "$std/dotenv/load.ts"; import { start } from "$fresh/server.ts"; import manifest from "./fresh.gen.ts"; -import twindPlugin from "$fresh/plugins/twind.ts"; -import twindConfig from "./twind.config.ts"; +import tailwind from "$fresh/plugins/tailwind.ts"; -await start(manifest, { plugins: [twindPlugin(twindConfig)] }); +await start(manifest, { plugins: [tailwind()] }); diff --git a/examples/demo/routes/[name].tsx b/examples/demo/routes/[name].tsx deleted file mode 100644 index 9c06827..0000000 --- a/examples/demo/routes/[name].tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { PageProps } from "$fresh/server.ts"; - -export default function Greet(props: PageProps) { - return
Hello {props.params.name}
; -} diff --git a/examples/demo/routes/_app.tsx b/examples/demo/routes/_app.tsx new file mode 100644 index 0000000..88fb6be --- /dev/null +++ b/examples/demo/routes/_app.tsx @@ -0,0 +1,17 @@ +import { PageProps } from "$fresh/server.ts"; + +export default function App({ Component }: PageProps) { + return ( + + + + + + demo + + + + + + ); +} diff --git a/examples/demo/routes/_middleware.ts b/examples/demo/routes/_middleware.ts index b22b0ee..4d8f165 100644 --- a/examples/demo/routes/_middleware.ts +++ b/examples/demo/routes/_middleware.ts @@ -1,29 +1,39 @@ -import { MiddlewareHandlerContext } from "$fresh/server.ts"; -import { createUser } from "../lib/db.ts"; +import { getCookies, setCookie } from "$std/http/cookie.ts"; +import { FreshContext } from "$fresh/server.ts"; +import { createUser, db } from "../lib/db.ts"; -interface State { - data: string; -} +type State = { + user: { id: string }; +}; export async function handler( - _: Request, - ctx: MiddlewareHandlerContext, + req: Request, + ctx: FreshContext, ) { - const resp = await ctx.next(); - const userId = resp.headers.get("userid"); - - if (!userId) { - const createdUser = await createUser(); - if (!createdUser) { - return new Response(`Oops! Something went wrong!`, { status: 500 }); + const cookies = getCookies(req.headers); + if (ctx.destination !== "route" || cookies.userid != null) { + if (cookies.userid) { + ctx.state.user = await db.users.findFirst({ + where: { id: cookies.userid }, + }); } - const expirationDate = new Date(); - expirationDate.setMonth(expirationDate.getMonth() + 1); - resp.headers.set( - `set-cookie`, - `userid=${createdUser.id}; Path=/; Expires=${expirationDate.toUTCString()}; HttpOnly`, - ); + return ctx.next(); } + const createdUser = await createUser(); + if (!createdUser) { + return new Response(`Oops! Something went wrong!`, { status: 500 }); + } + ctx.state.user = createdUser; + + const resp = await ctx.next(); + const expirationDate = new Date(); + expirationDate.setMonth(expirationDate.getMonth() + 1); + setCookie(resp.headers, { + name: "userid", + value: createdUser.id, + expires: expirationDate, + }); + return resp; } diff --git a/examples/demo/routes/api/joke.ts b/examples/demo/routes/api/joke.ts deleted file mode 100644 index a3f4243..0000000 --- a/examples/demo/routes/api/joke.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { HandlerContext } from "$fresh/server.ts"; - -// Jokes courtesy of https://punsandoneliners.com/randomness/programmer-jokes/ -const JOKES = [ - "Why do Java developers often wear glasses? They can't C#.", - "A SQL query walks into a bar, goes up to two tables and says “can I join you?”", - "Wasn't hard to crack Forrest Gump's password. 1forrest1.", - "I love pressing the F5 key. It's refreshing.", - "Called IT support and a chap from Australia came to fix my network connection. I asked “Do you come from a LAN down under?”", - "There are 10 types of people in the world. Those who understand binary and those who don't.", - "Why are assembly programmers often wet? They work below C level.", - "My favourite computer based band is the Black IPs.", - "What programme do you use to predict the music tastes of former US presidential candidates? An Al Gore Rhythm.", - "An SEO expert walked into a bar, pub, inn, tavern, hostelry, public house.", -]; - -export const handler = (_req: Request, _ctx: HandlerContext): Response => { - const randomIndex = Math.floor(Math.random() * JOKES.length); - const body = JOKES[randomIndex]; - return new Response(body); -}; diff --git a/examples/demo/routes/index.tsx b/examples/demo/routes/index.tsx index ab6c3c6..afb2f14 100644 --- a/examples/demo/routes/index.tsx +++ b/examples/demo/routes/index.tsx @@ -1,94 +1,82 @@ -import { Head } from "$fresh/runtime.ts"; -import { Handlers, PageProps } from "https://deno.land/x/fresh@1.1.6/server.ts"; -import { db, TodoTask, User } from "../lib/db.ts"; -import { z } from "https://deno.land/x/zod@v3.21.4/mod.ts"; -import SubmitTask from "../islands/SubmitTask.tsx"; +import {Head} from "$fresh/runtime.ts"; +import {Handlers, PageProps} from "$fresh/server.ts"; +import {z} from "zod"; + +import {db, TodoTask, User} from "../lib/db.ts"; +import Input from "../components/ui/input.tsx"; +import Button from "../components/ui/button.tsx"; type Task = z.infer; type User = z.infer; type TasksAndUser = { tasks: Task[]; user: User }; -function parseCookie(cookieString: string): Record { - const cookies: Record = {}; - const cookiePairs = cookieString.split(";"); - - for (const cookiePair of cookiePairs) { - const [name, value] = cookiePair.trim().split("="); - cookies[name] = value; - } - - return cookies; -} - -export const handler: Handlers = { - async GET(req, ctx) { - const { userid: userId } = parseCookie(req.headers.get("cookie") ?? ""); - if (!userId) { - // Reload headers - return new Response("Please reload!"); - } - +export const handler: Handlers = { + async GET(_, ctx) { // Get my tasks const tasks = await db.tasks.findMany({ - where: { userId }, + where: { userId: ctx.state?.user.id }, }); - const user = await db.users.findFirst({ where: { id: userId } }); - - return ctx.render({ tasks, user }); + return ctx.render({ tasks, user: ctx.state?.user }); }, - async POST(req) { - type RequestType = { - description: string; - userId: string; - }; - const requestBody: RequestType = await req.json(); - if (!requestBody.description || !requestBody.userId) { + async POST(req, ctx) { + const form = await req.formData(); + const description = form.get("description")?.toString(); + + if (!description || !ctx.state?.user) { return new Response("Bad request", { status: 400 }); } - const createdTask = await db.tasks.create({ + await db.tasks.create({ data: { id: crypto.randomUUID(), createdAt: new Date(), - description: requestBody.description, - userId: requestBody.userId, + description, + userId: ctx.state.user.id, completed: false, }, }); - return new Response(JSON.stringify(createdTask), { - headers: new Headers({ "Content-Type": "application/json" }), - }); + return Response.redirect(req.url); }, }; -export default function Home({ data }: PageProps) { +export default function Home( + { data: { tasks, user } }: PageProps, +) { return ( <> Pentagon Todo List -
-
-
-

- Welcome back {data?.user.name}! -

-

- Your user id is{" "} - {data?.user.id}, here's a list of your tasks for this month! -

-
-
- {/**/} -
-
- {/**/} - {data?.user && } - {data && data.tasks.length === 0 &&

No Tasks Found!

} - {data && data.tasks.length > 0 && - data.tasks.map((task) =>

{task.description}

)} -
+ +
+

+ Welcome back {user.name}! +

+

+ Your user id is{" "} + {user.id}, here's a list of your tasks for this month! +

+
+
+
+ + +
+
+
+ {tasks.length === 0 + ?

No Tasks Found!

+ : ( +
    + {tasks.map((task) =>
  • {task.description}
  • )} +
+ )} +
); } diff --git a/examples/demo/static/styles.css b/examples/demo/static/styles.css new file mode 100644 index 0000000..b5c61c9 --- /dev/null +++ b/examples/demo/static/styles.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/examples/demo/tailwind.config.ts b/examples/demo/tailwind.config.ts new file mode 100644 index 0000000..99dba8f --- /dev/null +++ b/examples/demo/tailwind.config.ts @@ -0,0 +1,7 @@ +import { type Config } from "tailwindcss"; + +export default { + content: [ + "{routes,islands,components}/**/*.{ts,tsx}", + ], +} satisfies Config; diff --git a/examples/demo/twind.config.ts b/examples/demo/twind.config.ts deleted file mode 100644 index d96eebd..0000000 --- a/examples/demo/twind.config.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Options } from "$fresh/plugins/twind.ts"; -/* - -const purple = { - purple1: 'hsl(280, 65.0%, 99.4%)', - purple2: 'hsl(276, 100%, 99.0%)', - purple3: 'hsl(276, 83.1%, 97.0%)', - purple4: 'hsl(275, 76.4%, 94.7%)', - purple5: 'hsl(275, 70.8%, 91.8%)', - purple6: 'hsl(274, 65.4%, 87.8%)', - purple7: 'hsl(273, 61.0%, 81.7%)', - purple8: 'hsl(272, 60.0%, 73.5%)', - purple9: 'hsl(272, 51.0%, 54.0%)', - purple10: 'hsl(272, 46.8%, 50.3%)', - purple11: 'hsl(272, 50.0%, 45.8%)', - purple12: 'hsl(272, 66.0%, 16.0%)', -} - -*/ - -export default { - selfURL: import.meta.url, - theme: { - extend: { - border: "hsl(273, 61.0%, 81.7%)", - input: "hsl(273, 61.0%, 81.7%)", - ring: "hsl(273, 61.0%, 81.7%)", - background: "hsl(280, 65.0%, 99.4%)", - foreground: "hsl(272, 50.0%, 45.8%)", - primary: { - DEFAULT: "hsl(272, 51.0%, 54.0%)", - foreground: "hsl(272, 50.0%, 45.8%)", - }, - secondary: { - DEFAULT: "hsl(272, 51.0%, 54.0%)", - foreground: "hsl(272, 50.0%, 45.8%)", - }, - destructive: { - DEFAULT: "hsl(272, 51.0%, 54.0%)", - foreground: "hsl(272, 50.0%, 45.8%)", - }, - muted: { - DEFAULT: "hsl(272, 51.0%, 54.0%)", - foreground: "hsl(272, 50.0%, 45.8%)", - }, - accent: { - DEFAULT: "hsl(272, 51.0%, 54.0%)", - foreground: "hsl(272, 50.0%, 45.8%)", - }, - popover: { - DEFAULT: "hsl(var(--popover))", - foreground: "hsl(var(--popover-foreground))", - }, - card: { - DEFAULT: "hsl(var(--card))", - foreground: "hsl(var(--card-foreground))", - }, - }, - }, -} as Options;