From d56594d3289002566f4653d607f0837befd65109 Mon Sep 17 00:00:00 2001 From: polwex Date: Thu, 15 May 2025 10:13:00 +0700 Subject: wtf man --- bun.lock | 9 ++ package.json | 3 + src/actions/login.ts | 58 ++++++++ src/actions/test.ts | 24 ++++ src/components/Login.tsx | 305 +++++++++++++++++++++++++++++++++++++++++ src/components/Login2.tsx | 148 ++++++++++++++++++++ src/components/actiontest.tsx | 27 ++++ src/components/counter.tsx | 12 +- src/components/ui/button.tsx | 58 ++++++++ src/components/ui/card.tsx | 68 +++++++++ src/components/ui/form.tsx | 165 ++++++++++++++++++++++ src/components/ui/input.tsx | 19 +++ src/components/ui/label.tsx | 24 ++++ src/components/ui/select.tsx | 179 ++++++++++++++++++++++++ src/components/ui/sonner.tsx | 23 ++++ src/components/ui/spinner.tsx | 8 ++ src/lib/db/index.ts | 36 +++-- src/lib/db/schema.sql | 172 +++++++++++++++++++++++ src/lib/server/cookie.ts | 47 +++++++ src/lib/server/cookiebridge.ts | 33 +++++ src/lib/server/header.ts | 12 ++ src/lib/server/setcookie.ts | 25 ++++ src/lib/utils.ts | 2 +- src/pages.gen.ts | 3 + src/pages/api/auth.ts | 17 +++ src/pages/login.tsx | 28 ++++ waku.config.ts | 23 +++- 27 files changed, 1508 insertions(+), 20 deletions(-) create mode 100644 src/actions/login.ts create mode 100644 src/actions/test.ts create mode 100644 src/components/Login.tsx create mode 100644 src/components/Login2.tsx create mode 100644 src/components/actiontest.tsx create mode 100644 src/components/ui/button.tsx create mode 100644 src/components/ui/card.tsx create mode 100644 src/components/ui/form.tsx create mode 100644 src/components/ui/input.tsx create mode 100644 src/components/ui/label.tsx create mode 100644 src/components/ui/select.tsx create mode 100644 src/components/ui/sonner.tsx create mode 100644 src/components/ui/spinner.tsx create mode 100644 src/lib/db/schema.sql create mode 100644 src/lib/server/cookie.ts create mode 100644 src/lib/server/cookiebridge.ts create mode 100644 src/lib/server/header.ts create mode 100644 src/lib/server/setcookie.ts create mode 100644 src/pages/api/auth.ts create mode 100644 src/pages/login.tsx diff --git a/bun.lock b/bun.lock index 6c36957..756e04c 100644 --- a/bun.lock +++ b/bun.lock @@ -4,12 +4,14 @@ "": { "name": "waku", "dependencies": { + "@edge-runtime/cookies": "^6.0.0", "@hookform/resolvers": "^5.0.1", "@radix-ui/react-label": "^2.1.6", "@radix-ui/react-select": "^2.2.4", "@radix-ui/react-slot": "^1.2.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cookie": "^1.0.2", "franc-all": "^7.2.0", "lucide-react": "^0.510.0", "next-themes": "^0.4.6", @@ -27,6 +29,7 @@ "devDependencies": { "@tailwindcss/postcss": "4.1.4", "@types/bun": "latest", + "@types/cookie": "^1.0.0", "@types/react": "19.1.2", "@types/react-dom": "19.1.2", "postcss": "8.5.3", @@ -77,6 +80,8 @@ "@babel/types": ["@babel/types@7.27.1", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q=="], + "@edge-runtime/cookies": ["@edge-runtime/cookies@6.0.0", "", {}, "sha512-VVO/8AwC2qVbygLb2IOkX1zWFx2yWIHzFv4D602CTnoRffd/+cdcXqpSydKaedFrk7a1dRYXbWwjzfV/gwZ2Gw=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="], @@ -313,6 +318,8 @@ "@types/bun": ["@types/bun@1.2.13", "", { "dependencies": { "bun-types": "1.2.13" } }, "sha512-u6vXep/i9VBxoJl3GjZsl/BFIsvML8DfVDO0RYLEwtSZSp981kEO1V5NwRcO1CPJ7AmvpbnDCiMKo3JvbDEjAg=="], + "@types/cookie": ["@types/cookie@1.0.0", "", { "dependencies": { "cookie": "*" } }, "sha512-mGFXbkDQJ6kAXByHS7QAggRXgols0mAdP4MuXgloGY1tXokvzaFFM4SMqWvf7AH0oafI7zlFJwoGWzmhDqTZ9w=="], + "@types/eslint": ["@types/eslint@9.6.1", "", { "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag=="], "@types/eslint-scope": ["@types/eslint-scope@3.7.7", "", { "dependencies": { "@types/eslint": "*", "@types/estree": "*" } }, "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg=="], @@ -405,6 +412,8 @@ "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], diff --git a/package.json b/package.json index 31f7965..8ee4fbb 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,14 @@ "start": "bunx --bun waku start" }, "dependencies": { + "@edge-runtime/cookies": "^6.0.0", "@hookform/resolvers": "^5.0.1", "@radix-ui/react-label": "^2.1.6", "@radix-ui/react-select": "^2.2.4", "@radix-ui/react-slot": "^1.2.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cookie": "^1.0.2", "franc-all": "^7.2.0", "lucide-react": "^0.510.0", "next-themes": "^0.4.6", @@ -32,6 +34,7 @@ "devDependencies": { "@tailwindcss/postcss": "4.1.4", "@types/bun": "latest", + "@types/cookie": "^1.0.0", "@types/react": "19.1.2", "@types/react-dom": "19.1.2", "postcss": "8.5.3", diff --git a/src/actions/login.ts b/src/actions/login.ts new file mode 100644 index 0000000..3d83b55 --- /dev/null +++ b/src/actions/login.ts @@ -0,0 +1,58 @@ +"use server"; +import { AsyncRes } from "@/lib/types"; +import db from "../lib/db"; +import cookie from "cookie"; +import { cookies } from "../lib/server/cookiebridge"; +import { unstable_redirect, unstable_rerenderRoute } from "waku/router/server"; + +export type FormState = { + name?: string; + password?: string; + error?: string; + success?: boolean; +}; +async function call(formData: FormData, register: boolean) { + const nam = formData.get("username"); + const creds = formData.get("password"); + const password = await Bun.password.hash(creds!.toString()); + const name = nam!.toString(); + const res = register + ? db.addUser(name, password) + : await db.loginUser(name, creds!.toString()); + return res; +} + +export async function postRegister( + prevState: FormState, + formData: FormData, +): Promise { + const res = await call(formData, true); + console.log("reg res", res); + if ("error" in res) return { error: "Something went wrong" }; + else { + return { success: true }; + } +} + +export async function postLogin( + prevState: FormState, + formData: FormData, +): Promise { + const res = await call(formData, false); + if ("error" in res) return { error: res.error }; + else { + setCookie(res.ok as number); + return { success: true }; + } +} +async function setCookie(userId: number) { + const COOKIE_EXPIRY = Date.now() + 1000 * 60 * 60 * 24 * 30; + const COOKIE_OPTS = { expires: new Date(COOKIE_EXPIRY) }; + + const { randomBytes } = await import("node:crypto"); + const cokistring = randomBytes(32).toBase64(); + const res = db.setCookie(cokistring, userId, COOKIE_EXPIRY); + const { setCookie } = cookies(); + setCookie("sorlang", cokistring, COOKIE_OPTS); + // unstable_redirect("/"); +} diff --git a/src/actions/test.ts b/src/actions/test.ts new file mode 100644 index 0000000..99d42a0 --- /dev/null +++ b/src/actions/test.ts @@ -0,0 +1,24 @@ +"use server"; +import db from "../lib/db"; + +export async function testFn(lol: any) { + console.log({ lol }); + return "lmao"; +} +export async function testLogin(state: number, formdata: FormData) { + return state + 9; +} +// export async function testLogin({ +// name, +// creds, +// }: { +// name: string; +// creds: string; +// }) { +// const res1 = db.loginUser(name, creds); +// console.log({ res1 }); +// return res1; +// // const res = db.addUser(name, creds); +// // console.log({ res }); +// // return res; +// } diff --git a/src/components/Login.tsx b/src/components/Login.tsx new file mode 100644 index 0000000..5747d06 --- /dev/null +++ b/src/components/Login.tsx @@ -0,0 +1,305 @@ +"use client"; + +import { postLogin, postRegister } from "@/actions/login"; +import { cn } from "@/lib/utils"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; + +import { toast } from "sonner"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardHeader, + CardDescription, + CardContent, + CardFooter, + CardTitle, +} from "@/components/ui/card"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { useActionState } from "react"; + +const FormSchema = z.object({ + username: z.string().min(2, { + message: "Username must be at least 2 characters.", + }), + password: z.string().min(2, { + message: "Password must be at least 2 characters.", + }), +}); +export default function AuthScreen() { + return ; +} + +function OOldform() { + const [state, formAction, isPending] = useActionState< + { msg: string }, + FormData + >(postLogin, { msg: "init" }); + return ( +
+ {state.msg} + + + +
+ Don't have an account?{" "} + + Sign up + +
+
+ ); +} +function Oldform() { + const [state, formAction, isPending] = useActionState< + { msg: string }, + FormData + >(postLogin, { msg: "init" }); + return ( +
+ {state.msg} +
+ + + Welcome back + Login to Sorlang +

{state.msg}

+
+ +
+
+ + +
+ +
+ Don't have an account?{" "} + + Sign up + +
+
+
+
+
+ By clicking continue, you agree to our{" "} + Terms of Service and Privacy Policy. +
+
+
+ ); +} + +function Register({ setRegister }: { setRegister: (b: boolean) => void }) { + const form = useForm>({ + resolver: zodResolver(FormSchema), + defaultValues: { + username: "", + password: "", + }, + }); + + async function onSubmit(data: z.infer) { + const body = JSON.stringify({ + name: data.username, + creds: data.password, + }); + const opts = { + method: "POST", + headers: { "Content-type": "application/json" }, + body, + }; + const res = await fetch("/api/db/user/new", opts); + const j = await res.json(); + console.log(j); + if ("error" in j) toast(`Error: ${j.error}`); + else { + toast("Register successful"); + } + } + + return ( + + Register +
+ + + ( + + Username + + + + + This is your public display name. + + + + )} + /> + ( + + Password + + + + + + )} + /> + + + + +
+ +
+ ); +} + +function LoginForm({ + className, + ...props +}: React.ComponentPropsWithoutRef<"div">) { + const form = useForm>({ + resolver: zodResolver(FormSchema), + defaultValues: { + username: "", + password: "", + }, + }); + async function onSubmit(data: z.infer) { + console.log("oh hai"); + const body = JSON.stringify({ + name: data.username, + creds: data.password, + }); + const opts = { + method: "POST", + headers: { "Content-type": "application/json" }, + body, + }; + const res = await fetch("/api/login", opts); + const j = await res.json(); + console.log({ j }); + if ("error" in j) toast(`Error! ${j.error}`); + else { + toast("Login successful"); + } + } + return ( +
+ + + Welcome back + Login to Sorlang + + +
+ +
+
+ ( + + Username + + + + + + )} + /> + ( + + + Password + + Forgot your password? + + + + + + + + )} + /> +
+ +
+ Don't have an account?{" "} + + Sign up + +
+
+
+ +
+
+
+ By clicking continue, you agree to our Terms of Service{" "} + and Privacy Policy. +
+
+ ); +} diff --git a/src/components/Login2.tsx b/src/components/Login2.tsx new file mode 100644 index 0000000..2164b1a --- /dev/null +++ b/src/components/Login2.tsx @@ -0,0 +1,148 @@ +"use client"; + +import { FormState, postLogin, postRegister } from "@/actions/login"; + +import { useActionState, useEffect, useState } from "react"; +import { + Card, + CardHeader, + CardDescription, + CardContent, + CardFooter, + CardTitle, +} from "@/components/ui/card"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { useRouter } from "waku"; + +export default function AuthScreen() { + const [isReg, setReg] = useState(false); + return setReg((b) => !b)} />; +} + +function OOldform({ isReg, toggle }: { isReg: boolean; toggle: () => void }) { + const regstrings = { + title: "Welcome to Sorlang", + desc: "Sign up", + button: "Sign up", + toggle: "Have an account?", + toggle2: "Login", + }; + const logstrings = { + title: "Welcome back", + desc: "Login to Sorlang", + button: "Login", + toggle: "Don't have an account?", + toggle2: "Sign up", + }; + const [strings, setStrings] = useState(logstrings); + + useEffect(() => { + if (isReg) setStrings(regstrings); + else setStrings(logstrings); + }, [isReg]); + + const [state, formAction, isPending] = useActionState( + isReg ? postRegister : postLogin, + {}, + "/login", + ); + // const nav = useRouter(); + // useEffect(() => { + // if (state.success) nav.replace("/"); + // }, [state]); + return ( +
+
+ + + {strings.title} + {strings.desc} + + +
+
+ + +
+ +
+ {strings.toggle} + + {strings.toggle2} + +
+
+ {state.error &&

{state.error}

} +
+
+
+ By clicking continue, you agree to our{" "} + Terms of Service and Privacy Policy. +
+
+
+ //
+ // {state.msg} + // + // + // + //
+ // Don't have an account?{" "} + // + // Sign up + // + //
+ //
+ ); +} diff --git a/src/components/actiontest.tsx b/src/components/actiontest.tsx new file mode 100644 index 0000000..863f289 --- /dev/null +++ b/src/components/actiontest.tsx @@ -0,0 +1,27 @@ +"use client"; + +import { testFn, testLogin } from "@/actions/test"; +import { useActionState, useState } from "react"; + +export default function TestForm() { + const [state, formAction, isPending] = useActionState( + testLogin, + 0, + ); + return ( +
+

State: {state}

+ + + +
+ ); +} diff --git a/src/components/counter.tsx b/src/components/counter.tsx index 0e540b8..2122b75 100644 --- a/src/components/counter.tsx +++ b/src/components/counter.tsx @@ -1,11 +1,17 @@ -'use client'; +"use client"; -import { useState } from 'react'; +import { testFn, testLogin } from "@/actions/test"; +import { useState } from "react"; export const Counter = () => { const [count, setCount] = useState(0); - const handleIncrement = () => setCount((c) => c + 1); + const handleIncrement = async () => { + setCount((c) => c + 1); + const res = await testFn("rofl"); + console.log({ res }); + const oof = testLogin({ name: "yago", creds: "xd" }); + }; return (
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..61875fc --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,58 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 ring-ring/10 dark:ring-ring/20 dark:outline-ring/40 outline-ring/50 focus-visible:ring-4 focus-visible:outline-1 aria-invalid:focus-visible:ring-0", + { + variants: { + variant: { + default: + "bg-primary text-primary-foreground shadow-sm hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90", + outline: + "border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2 has-[>svg]:px-3", + sm: "h-8 rounded-md px-3 has-[>svg]:px-2.5", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + icon: "size-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +function Button({ + className, + variant, + size, + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean + }) { + const Comp = asChild ? Slot : "button" + + return ( + + ) +} + +export { Button, buttonVariants } diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..3ff199b --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,68 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Card({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx new file mode 100644 index 0000000..8a83b32 --- /dev/null +++ b/src/components/ui/form.tsx @@ -0,0 +1,165 @@ +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { Slot } from "@radix-ui/react-slot" +import { + Controller, + ControllerProps, + FieldPath, + FieldValues, + FormProvider, + useFormContext, + useFormState, +} from "react-hook-form" + +import { cn } from "@/lib/utils" +import { Label } from "@/components/ui/label" + +const Form = FormProvider + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +> = { + name: TName +} + +const FormFieldContext = React.createContext( + {} as FormFieldContextValue +) + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +>({ + ...props +}: ControllerProps) => { + return ( + + + + ) +} + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext) + const itemContext = React.useContext(FormItemContext) + const { getFieldState } = useFormContext() + const formState = useFormState({ name: fieldContext.name }) + const fieldState = getFieldState(fieldContext.name, formState) + + if (!fieldContext) { + throw new Error("useFormField should be used within ") + } + + const { id } = itemContext + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + } +} + +type FormItemContextValue = { + id: string +} + +const FormItemContext = React.createContext( + {} as FormItemContextValue +) + +function FormItem({ className, ...props }: React.ComponentProps<"div">) { + const id = React.useId() + + return ( + +
+ + ) +} + +function FormLabel({ + className, + ...props +}: React.ComponentProps) { + const { error, formItemId } = useFormField() + + return ( +