diff --git a/bun.lockb b/bun.lockb index 078415f..f24e64a 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/packages/web/app/auth/requiresLogin.tsx b/packages/web/app/auth/requiresLogin.tsx new file mode 100644 index 0000000..7121e7d --- /dev/null +++ b/packages/web/app/auth/requiresLogin.tsx @@ -0,0 +1,13 @@ +import { Outlet } from "react-router"; + +export default function RequiresLogin() { + const user = { + name: "Michael", + }; + + return ( +
+ +
+ ); +} diff --git a/packages/web/app/auth/serverAuthMiddleware.ts b/packages/web/app/auth/serverAuthMiddleware.ts new file mode 100644 index 0000000..a273f2e --- /dev/null +++ b/packages/web/app/auth/serverAuthMiddleware.ts @@ -0,0 +1,35 @@ +"use server"; +import type { unstable_MiddlewareFunction } from "react-router"; +import { unstable_createContext } from "react-router"; +import { createCookie } from "react-router"; +import { redirect } from "react-router"; + +//TODO use session cookies: https://reactrouter.com/explanation/sessions-and-cookies#login-form-example + +export const authCookie = createCookie("tdk-session", { + //TODO Thnis is just a bit wonky + httpOnly: true, + path: "/", + sameSite: "lax", + secrets: ["dev-secret"], + secure: true, +}); + +export const serverAuthContext = unstable_createContext(); + +export const serverAuthMiddleware: unstable_MiddlewareFunction< + Response +> = async ({ request, context }, next) => { + const ac = await authCookie.parse(request.headers.get("Cookie")); + + console.log(ac); + + if (ac == null || typeof ac !== "string") { + throw redirect("/login"); + } + + context.set(serverAuthContext, ac); + + const res = await next(); + return res; +}; diff --git a/packages/web/app/root.tsx b/packages/web/app/root.tsx index 897cf5a..354ee02 100644 --- a/packages/web/app/root.tsx +++ b/packages/web/app/root.tsx @@ -9,23 +9,30 @@ import { import type { Route } from "./+types/root"; import "./app.css"; +import "@pigment-css/react/styles.css"; +import { css } from "@pigment-css/react"; + +const rootStyles = css({ + background: "black", + color: "white", +}); export const links: Route.LinksFunction = () => [ - { rel: "preconnect", href: "https://fonts.googleapis.com" }, - { - rel: "preconnect", - href: "https://fonts.gstatic.com", - crossOrigin: "anonymous", - }, - { - rel: "stylesheet", - href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap", - }, + // { rel: "preconnect", href: "https://fonts.googleapis.com" }, + // { + // rel: "preconnect", + // href: "https://fonts.gstatic.com", + // crossOrigin: "anonymous", + // }, + // { + // rel: "stylesheet", + // href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap", + // }, ]; export function Layout({ children }: { children: React.ReactNode }) { return ( - + @@ -62,11 +69,11 @@ export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { } return ( -
+

{message}

{details}

{stack && ( -
+        
           {stack}
         
)} diff --git a/packages/web/app/routes.ts b/packages/web/app/routes.ts index 102b402..294169e 100644 --- a/packages/web/app/routes.ts +++ b/packages/web/app/routes.ts @@ -1,3 +1,17 @@ -import { type RouteConfig, index } from "@react-router/dev/routes"; +import { + type RouteConfig, + index, + layout, + route, +} from "@react-router/dev/routes"; -export default [index("routes/home.tsx")] satisfies RouteConfig; +export default [ + index("routes/home.tsx"), + + route("login", "routes/login.tsx"), + route("logout", "routes/logout.tsx"), + + layout("auth/requiresLogin.tsx", [ + route("temp_list", "routes/temp_list.tsx"), + ]), +] satisfies RouteConfig; diff --git a/packages/web/app/routes/home.tsx b/packages/web/app/routes/home.tsx index 4777829..7864be2 100644 --- a/packages/web/app/routes/home.tsx +++ b/packages/web/app/routes/home.tsx @@ -1,4 +1,7 @@ -import { Welcome } from "../welcome/welcome"; +import { useOutletContext } from "react-router"; +import type { LoaderFunctionArgs } from "react-router"; +import { unstable_createContext } from "react-router"; +import { NavLink } from "react-router"; import type { Route } from "./+types/home"; export function meta(m: Route.MetaArgs) { @@ -8,6 +11,10 @@ export function meta(m: Route.MetaArgs) { ]; } -export default function Home() { - return ; +export default function Home(p: Route.ComponentProps) { + return ( + + ); } diff --git a/packages/web/app/routes/login.tsx b/packages/web/app/routes/login.tsx new file mode 100644 index 0000000..9133092 --- /dev/null +++ b/packages/web/app/routes/login.tsx @@ -0,0 +1,30 @@ +import { data, redirect, useFetcher } from "react-router"; +import { authCookie } from "../auth/serverAuthMiddleware"; +import type { Route } from "./+types/login"; + +export async function loader(p: Route.LoaderArgs) { + const c = await authCookie.parse(p.request.headers.get("Cookie")); + if (c != null) { + return redirect("/"); + } + return data(""); +} + +export async function action(p: Route.ActionArgs) { + console.log("Logged in"); + return data("", { + headers: { "Set-Cookie": await authCookie.serialize("Blubby") }, + }); +} + +export default function Login(p: Route.ComponentProps) { + const fetcher = useFetcher(); + + return ( +
+ + + +
+ ); +} diff --git a/packages/web/app/routes/logout.tsx b/packages/web/app/routes/logout.tsx new file mode 100644 index 0000000..e79c4e6 --- /dev/null +++ b/packages/web/app/routes/logout.tsx @@ -0,0 +1,11 @@ +import { data } from "react-router"; +import { authCookie } from "../auth/serverAuthMiddleware"; +import type { Route } from "./+types/logout"; + +export async function action(p: Route.ActionArgs) { + return data("", { + headers: { + "Set-Cookie": await authCookie.serialize("", { expires: new Date(0) }), + }, + }); +} diff --git a/packages/web/app/routes/temp_list.tsx b/packages/web/app/routes/temp_list.tsx new file mode 100644 index 0000000..cc1fe30 --- /dev/null +++ b/packages/web/app/routes/temp_list.tsx @@ -0,0 +1,32 @@ +import type { unstable_MiddlewareFunction } from "react-router"; +import { useFetcher, useRouteError } from "react-router"; +import type { ShouldRevalidateFunctionArgs } from "react-router"; +import { + serverAuthContext, + serverAuthMiddleware, +} from "../auth/serverAuthMiddleware"; +import type { Route } from "./+types/temp_list"; + +export async function loader(p: Route.LoaderArgs) { + const authc = p.context.get(serverAuthContext); + return { + user: authc, + }; +} + +export const unstable_middleware: Route.unstable_MiddlewareFunction[] = [ + serverAuthMiddleware, +]; + +export default function TempList({ loaderData }: Route.ComponentProps) { + const logout = useFetcher(); + + return ( +
+

Route TempList User: {JSON.stringify(loaderData)}

+ + + +
+ ); +} diff --git a/packages/web/app/welcome/welcome.tsx b/packages/web/app/welcome/welcome.tsx deleted file mode 100644 index 22db177..0000000 --- a/packages/web/app/welcome/welcome.tsx +++ /dev/null @@ -1,77 +0,0 @@ -export function Welcome() { - return ( -
-
-
-
Hoo
-
-
- -
-
-
- ); -} - -const resources = [ - { - href: "https://reactrouter.com/docs", - text: "React Router Docs", - icon: ( - // biome-ignore lint/a11y/noSvgWithoutTitle: - - - - ), - }, - { - href: "https://rmx.as/discord", - text: "Join Discord", - icon: ( - // biome-ignore lint/a11y/noSvgWithoutTitle: - - - - ), - }, -]; diff --git a/packages/web/package.json b/packages/web/package.json index ecc47c3..ffeb837 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -13,6 +13,7 @@ "test": "vitest" }, "dependencies": { + "@pigment-css/react": "0.0.30", "@react-router/node": "7.4.1", "@react-router/serve": "7.4.1", "effect": "3.14.5", @@ -25,6 +26,7 @@ }, "devDependencies": { "@biomejs/biome": "1.9.4", + "@pigment-css/vite-plugin": "0.0.30", "@react-router/dev": "7.4.1", "@types/lodash": "4.17.16", "@types/node": "22.13.17", diff --git a/packages/web/react-router.config.ts b/packages/web/react-router.config.ts index 6ff16f9..f4fd49c 100644 --- a/packages/web/react-router.config.ts +++ b/packages/web/react-router.config.ts @@ -1,7 +1,23 @@ import type { Config } from "@react-router/dev/config"; +import { type Future, Route } from "react-router"; + +declare module "react-router" { + interface Future { + unstable_middleware: true; // 👈 Enable middleware types + } + //AppLoadContext is replaced with the ContextAPI enabled by the unstable_middlewares flag + // interface AppLoadContext { + // remove_me_i_am_a_test: string; + // } +} export default { // Config options... // Server-side render by default, to enable SPA mode set this to `false` ssr: true, + future: { + //TODO Keep up to date with the unstable releases over at https://reactrouter.com/changelog + //See: https://github.com/remix-run/react-router/blob/release-next/decisions/0014-context-middleware.md + unstable_middleware: true, + }, } satisfies Config; diff --git a/packages/web/tsconfig.json b/packages/web/tsconfig.json index 9699591..0769318 100644 --- a/packages/web/tsconfig.json +++ b/packages/web/tsconfig.json @@ -1,7 +1,7 @@ { "files": [], "extends": "../../tsconfig.base.json", - "include": ["app"], + "include": ["app", "*.ts"], "compilerOptions": { "types": ["vitest/importMeta", "vite/client", "node"], "rootDirs": [".", "./.react-router/types"], diff --git a/packages/web/vite.config.ts b/packages/web/vite.config.ts index 389061b..2e230db 100644 --- a/packages/web/vite.config.ts +++ b/packages/web/vite.config.ts @@ -1,3 +1,4 @@ +import { pigment } from "@pigment-css/vite-plugin"; import { reactRouter } from "@react-router/dev/vite"; import { defineConfig } from "vite"; import checker from "vite-plugin-checker"; @@ -5,6 +6,7 @@ import checker from "vite-plugin-checker"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [ + pigment({}), checker({ biome: { command: "check", dev: { command: "lint" } }, typescript: true,