From 20cb8fdab76ac1e176783d4ff3e122dd2856a750 Mon Sep 17 00:00:00 2001 From: tayloraj10 Date: Tue, 14 Jul 2026 19:50:30 -0400 Subject: [PATCH 1/4] Redesign auth pages and add password reset flow Reworks the login page and adds forgot-password, reset-password, and confirm routes, plus their validation schemas. --- app/routes.ts | 3 + app/routes/unauthenticated/_layout.tsx | 72 ++++++-- app/routes/unauthenticated/confirm.tsx | 20 +++ .../unauthenticated/forgot-password.tsx | 132 +++++++++++++++ app/routes/unauthenticated/login.tsx | 120 ++++++++++---- app/routes/unauthenticated/reset-password.tsx | 154 ++++++++++++++++++ app/schemas/forgot-password.ts | 5 + app/schemas/reset-password.ts | 15 ++ 8 files changed, 477 insertions(+), 44 deletions(-) create mode 100644 app/routes/unauthenticated/confirm.tsx create mode 100644 app/routes/unauthenticated/forgot-password.tsx create mode 100644 app/routes/unauthenticated/reset-password.tsx create mode 100644 app/schemas/forgot-password.ts create mode 100644 app/schemas/reset-password.ts diff --git a/app/routes.ts b/app/routes.ts index de7ef64..1a14b97 100644 --- a/app/routes.ts +++ b/app/routes.ts @@ -12,5 +12,8 @@ export default [ ]), route("auth", "routes/unauthenticated/_layout.tsx", [ index("routes/unauthenticated/login.tsx"), + route("forgot-password", "routes/unauthenticated/forgot-password.tsx"), + route("confirm", "routes/unauthenticated/confirm.tsx"), + route("reset-password", "routes/unauthenticated/reset-password.tsx"), ]), ] satisfies RouteConfig; diff --git a/app/routes/unauthenticated/_layout.tsx b/app/routes/unauthenticated/_layout.tsx index a855b88..fc11d06 100644 --- a/app/routes/unauthenticated/_layout.tsx +++ b/app/routes/unauthenticated/_layout.tsx @@ -1,23 +1,73 @@ import { Outlet } from "react-router"; import phlasklogo from "~/assets/PHLASK_v2.svg"; +import { ThemeToggle } from "~/components/ThemeToggle"; +import { WaveDivider } from "~/components/WaveDivider"; + +const BUBBLES = [ + { left: "8%", size: 14, delay: 0, duration: 9 }, + { left: "18%", size: 8, delay: 2.5, duration: 7 }, + { left: "32%", size: 20, delay: 1, duration: 11 }, + { left: "62%", size: 10, delay: 3.5, duration: 8 }, + { left: "78%", size: 16, delay: 0.5, duration: 10 }, + { left: "90%", size: 9, delay: 2, duration: 7.5 }, +]; export default function UnauthenticatedLayout() { return ( -
- +
+
+ +
+ + {BUBBLES.map((bubble) => ( + + ))} -
-
+ + + + +
+
+
+ PHLASK Logo +
+
+
-
+
); } diff --git a/app/routes/unauthenticated/confirm.tsx b/app/routes/unauthenticated/confirm.tsx new file mode 100644 index 0000000..4893f05 --- /dev/null +++ b/app/routes/unauthenticated/confirm.tsx @@ -0,0 +1,20 @@ +import { type LoaderFunction, redirect } from "react-router"; +import { getDatabaseClient } from "~/api/client.server"; + +export const loader: LoaderFunction = async ({ request }) => { + const url = new URL(request.url); + const code = url.searchParams.get("code"); + + if (!code) { + return redirect("/auth/forgot-password"); + } + + const { client, headers } = getDatabaseClient(request); + const { error } = await client.auth.exchangeCodeForSession(code); + + if (error) { + return redirect("/auth/forgot-password"); + } + + return redirect("/auth/reset-password", { headers }); +}; diff --git a/app/routes/unauthenticated/forgot-password.tsx b/app/routes/unauthenticated/forgot-password.tsx new file mode 100644 index 0000000..2a68578 --- /dev/null +++ b/app/routes/unauthenticated/forgot-password.tsx @@ -0,0 +1,132 @@ +import { + Button, + Link as MuiLink, + Stack, + TextField, + Typography, +} from "@mui/material"; +import { applySchema } from "composable-functions"; +import { + type ActionFunction, + data, + Link, + type LoaderFunction, + redirect, + useActionData, +} from "react-router"; +import { performMutation, SchemaForm } from "remix-forms"; +import { getDatabaseClient } from "~/api/client.server"; +import { forgotPasswordSchema } from "~/schemas/forgot-password"; + +export const loader: LoaderFunction = async ({ request }) => { + const { client } = getDatabaseClient(request); + + const response = await client.auth.getUser(); + if (response.error) { + return null; + } + + return redirect("/"); +}; + +const mutation = applySchema(forgotPasswordSchema)(async (values) => values); + +export const action: ActionFunction = async ({ request }) => { + const result = await performMutation({ + request, + schema: forgotPasswordSchema, + mutation, + }); + + if (!result.success) { + return data(result, 400); + } + + const { client } = getDatabaseClient(request); + const origin = new URL(request.url).origin; + + await client.auth.resetPasswordForEmail(result.data.email, { + redirectTo: `${origin}/auth/confirm`, + }); + + return data({ sent: true }); +}; + +const ForgotPassword = () => { + const action = useActionData<{ sent?: boolean }>(); + + if (action?.sent) { + return ( + + + + Check your email + + + If an account exists for that email, we've sent a link to reset your + password. + + + + Back to sign in + + + ); + } + + return ( + + + + Reset your password + + + Enter the email associated with your account and we'll send you a link + to reset your password. + + + + + {({ Field, formState, register }) => ( + + + {({ name, errors, required }) => ( + + )} + + + + + + Back to sign in + + + )} + + + ); +}; + +export default ForgotPassword; diff --git a/app/routes/unauthenticated/login.tsx b/app/routes/unauthenticated/login.tsx index 9356b99..72cca10 100644 --- a/app/routes/unauthenticated/login.tsx +++ b/app/routes/unauthenticated/login.tsx @@ -1,8 +1,20 @@ -import { Button, Stack, TextField, Typography } from "@mui/material"; +import { Visibility, VisibilityOff } from "@mui/icons-material"; +import { + Alert, + Button, + IconButton, + InputAdornment, + Link as MuiLink, + Stack, + TextField, + Typography, +} from "@mui/material"; import { applySchema } from "composable-functions"; +import { useState } from "react"; import { type ActionFunction, data, + Link, type LoaderFunction, redirect, useActionData, @@ -40,7 +52,10 @@ export const action: ActionFunction = async ({ context, request }) => { const response = await client.auth.signInWithPassword(result.data); if (response.error) { - return data(response.error, { status: response.error.status }); + return data( + { message: response.error.message }, + { status: response.error.status ?? 400 }, + ); } context.set(userContext, response.data.user); @@ -49,57 +64,96 @@ export const action: ActionFunction = async ({ context, request }) => { }; const Login = () => { - const action = useActionData<{ message: string; status: number }>(); + const action = useActionData<{ message?: string }>(); + const [showPassword, setShowPassword] = useState(false); return ( - - Login + + + + Sign in + + + Enter your credentials to access the PHLask admin dashboard. + + + {({ Field, formState, register }) => ( - + - {({ name, errors, required }) => { - return ( - - ); - }} - - {({ name, errors, required }) => ( )} - - {action?.message} - + + + + {({ name, errors, required }) => ( + + setShowPassword((value) => !value)} + edge="end" + tabIndex={-1} + > + {showPassword ? ( + + ) : ( + + )} + + + ), + }, + }} + /> + )} + + + + Forgot password? + + + + {action?.message && ( + {action.message} + )} diff --git a/app/routes/unauthenticated/reset-password.tsx b/app/routes/unauthenticated/reset-password.tsx new file mode 100644 index 0000000..12ff94d --- /dev/null +++ b/app/routes/unauthenticated/reset-password.tsx @@ -0,0 +1,154 @@ +import { Visibility, VisibilityOff } from "@mui/icons-material"; +import { + Alert, + Button, + IconButton, + InputAdornment, + Stack, + TextField, + Typography, +} from "@mui/material"; +import { applySchema } from "composable-functions"; +import { useState } from "react"; +import { + type ActionFunction, + data, + type LoaderFunction, + redirect, + useActionData, +} from "react-router"; +import { performMutation, SchemaForm } from "remix-forms"; +import { getDatabaseClient } from "~/api/client.server"; +import { resetPasswordSchema } from "~/schemas/reset-password"; + +export const loader: LoaderFunction = async ({ request }) => { + const { client } = getDatabaseClient(request); + + const response = await client.auth.getUser(); + if (response.error) { + return redirect("/auth/forgot-password"); + } + + return null; +}; + +const mutation = applySchema(resetPasswordSchema)(async (values) => values); + +export const action: ActionFunction = async ({ request }) => { + const result = await performMutation({ + request, + schema: resetPasswordSchema, + mutation, + }); + + if (!result.success) { + return data(result, 400); + } + + const { client, headers } = getDatabaseClient(request); + const response = await client.auth.updateUser({ + password: result.data.password, + }); + + if (response.error) { + return data( + { message: response.error.message }, + { status: response.error.status ?? 400 }, + ); + } + + return redirect("/", { headers }); +}; + +const ResetPassword = () => { + const action = useActionData<{ message?: string }>(); + const [showPassword, setShowPassword] = useState(false); + + return ( + + + + Set a new password + + + Choose a new password for your account. + + + + + {({ Field, formState, register }) => ( + + + {({ name, errors, required }) => ( + + setShowPassword((value) => !value)} + edge="end" + tabIndex={-1} + > + {showPassword ? ( + + ) : ( + + )} + + + ), + }, + }} + /> + )} + + + + {({ name, errors, required }) => ( + + )} + + + {action?.message && ( + {action.message} + )} + + + + )} + + + ); +}; + +export default ResetPassword; diff --git a/app/schemas/forgot-password.ts b/app/schemas/forgot-password.ts new file mode 100644 index 0000000..531a179 --- /dev/null +++ b/app/schemas/forgot-password.ts @@ -0,0 +1,5 @@ +import { z } from "zod"; + +export const forgotPasswordSchema = z.object({ + email: z.email().min(1), +}); diff --git a/app/schemas/reset-password.ts b/app/schemas/reset-password.ts new file mode 100644 index 0000000..4e0d3b5 --- /dev/null +++ b/app/schemas/reset-password.ts @@ -0,0 +1,15 @@ +import { z } from "zod"; + +export const resetPasswordSchema = z + .object({ + password: z + .string() + .min(8, { error: "Password must be at least 8 characters" }), + confirmPassword: z + .string() + .min(1, { error: "Please confirm your password" }), + }) + .refine((values) => values.password === values.confirmPassword, { + error: "Passwords do not match", + path: ["confirmPassword"], + }); From fa706580677baf7ef140a5a72d073a827146548a Mon Sep 17 00:00:00 2001 From: tayloraj10 Date: Tue, 14 Jul 2026 20:32:41 -0400 Subject: [PATCH 2/4] Convert unauthenticated layout from Tailwind to MUI Ports WaveDivider to sx-based styling (className -> sx) and rewrites the auth layout's Tailwind classes as MUI Box/sx, matching the conversion started on pr/theming-infra. --- app/components/WaveDivider.tsx | 36 +++++++--- app/routes/unauthenticated/_layout.tsx | 96 +++++++++++++++++++++----- 2 files changed, 104 insertions(+), 28 deletions(-) diff --git a/app/components/WaveDivider.tsx b/app/components/WaveDivider.tsx index 06057c5..9618bfc 100644 --- a/app/components/WaveDivider.tsx +++ b/app/components/WaveDivider.tsx @@ -1,3 +1,5 @@ +import { Box, type SxProps, type Theme } from "@mui/material"; + // Two periods of the same wave back-to-back so a -50% translateX loops seamlessly. const WAVE_PATH = "M0,40 C150,90 350,0 600,40 C850,80 1050,0 1200,40 C1350,80 1550,0 1800,40 C1950,80 2150,0 2400,40 L2400,120 L0,120 Z"; @@ -8,7 +10,7 @@ type WaveDividerProps = { opacity?: number; duration?: number; reverse?: boolean; - className?: string; + sx?: SxProps; }; export function WaveDivider({ @@ -17,27 +19,41 @@ export function WaveDivider({ opacity = 1, duration = 18, reverse = false, - className, + sx, }: WaveDividerProps) { return ( -
- -
+ + ); } diff --git a/app/routes/unauthenticated/_layout.tsx b/app/routes/unauthenticated/_layout.tsx index fc11d06..a9ecfe6 100644 --- a/app/routes/unauthenticated/_layout.tsx +++ b/app/routes/unauthenticated/_layout.tsx @@ -1,8 +1,10 @@ +import { Box } from "@mui/material"; import { Outlet } from "react-router"; import phlasklogo from "~/assets/PHLASK_v2.svg"; import { ThemeToggle } from "~/components/ThemeToggle"; import { WaveDivider } from "~/components/WaveDivider"; +import { brand, navy } from "~/theme/theme"; const BUBBLES = [ { left: "8%", size: 14, delay: 0, duration: 9 }, @@ -15,16 +17,40 @@ const BUBBLES = [ export default function UnauthenticatedLayout() { return ( -
-
+ ({ + position: "relative", + display: "flex", + minHeight: "100vh", + alignItems: "center", + justifyContent: "center", + overflow: "hidden", + px: 2, + py: 6, + backgroundImage: `linear-gradient(to bottom, ${brand[100]}, ${brand[300]}, ${brand[600]})`, + ...theme.applyStyles("dark", { + backgroundImage: `linear-gradient(to bottom, ${navy[950]}, ${navy[900]}, ${navy[800]})`, + }), + })} + > + -
+ {BUBBLES.map((bubble) => ( - ({ + pointerEvents: "none", + position: "absolute", + bottom: 0, + borderRadius: "9999px", + bgcolor: "rgba(255,255,255,0.4)", + ...theme.applyStyles("dark", { + bgcolor: `${brand[200]}33`, + }), + })} style={{ left: bubble.left, width: bubble.size, @@ -40,7 +66,10 @@ export default function UnauthenticatedLayout() { height={140} opacity={0.35} duration={26} - className="bottom-0 dark:opacity-20" + sx={(theme) => ({ + bottom: 0, + ...theme.applyStyles("dark", { opacity: 0.2 }), + })} /> ({ + bottom: 0, + ...theme.applyStyles("dark", { opacity: 0.25 }), + })} /> ({ + bottom: 0, + ...theme.applyStyles("dark", { opacity: 0.3 }), + })} /> -
-
-
- PHLASK Logo -
-
-
+ + + ({ + display: "inline-flex", + borderRadius: 4, + bgcolor: "rgba(255,255,255,0.8)", + p: 1.5, + boxShadow: 2, + backdropFilter: "blur(4px)", + ...theme.applyStyles("dark", { bgcolor: `${navy[950]}99` }), + })} + > + PHLASK Logo + + + ({ + borderRadius: 4, + border: "1px solid rgba(255,255,255,0.5)", + bgcolor: "rgba(255,255,255,0.75)", + p: 4, + boxShadow: 8, + backdropFilter: "blur(8px)", + ...theme.applyStyles("dark", { + borderColor: `${navy[700]}99`, + bgcolor: `${navy[900]}bf`, + }), + })} + > -
-
-
+ + + ); } From 8a493d74a1ff61bd7934b0403c9c4884555ccfa1 Mon Sep 17 00:00:00 2001 From: tayloraj10 Date: Tue, 14 Jul 2026 20:43:18 -0400 Subject: [PATCH 3/4] Remove Tailwind CSS infrastructure All components now use MUI's sx prop / theme system; no Tailwind utility classes remain anywhere in the app, so the plugin, deps, and directives are no longer needed. Custom properties in app.css are trimmed to only the tokens still referenced by plain CSS (keyframes, scrollbar colors); component-level design tokens live in theme.ts. --- app/app.css | 30 +++---- package.json | 2 - pnpm-lock.yaml | 207 ++----------------------------------------------- vite.config.ts | 3 +- 4 files changed, 17 insertions(+), 225 deletions(-) diff --git a/app/app.css b/app/app.css index ceb77af..342b3a5 100644 --- a/app/app.css +++ b/app/app.css @@ -1,40 +1,32 @@ -@import 'tailwindcss'; - /* Dark mode is toggled by adding/removing `.dark` on (see ThemeModeProvider), rather than following the OS preference, so it can stay in sync with the MUI theme. */ -@custom-variant dark (&:where(.dark, .dark *)); - -@theme { - --font-sans: - 'Inter', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', - 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; - /* Water/brand blue, anchored on the PHLASK logo color (#10B6FF = brand-500) */ +:root { + /* Water/brand blue, anchored on the PHLASK logo color (#10B6FF = brand-500). + Mirrored in app/theme/theme.ts (kept in sync manually). */ --color-brand-50: #eafaff; --color-brand-100: #d6f4ff; - --color-brand-200: #ade8ff; --color-brand-300: #6fd6ff; - --color-brand-400: #38c1ff; - --color-brand-500: #10b6ff; - --color-brand-600: #0090de; - --color-brand-700: #0072b3; - --color-brand-800: #045c90; - --color-brand-900: #0a3a5c; /* Deep ocean navy, used for dark-mode surfaces */ --color-navy-950: #071522; --color-navy-900: #0a1929; - --color-navy-800: #10263a; - --color-navy-700: #17344c; --color-navy-600: #1f4560; } html, body { - @apply bg-brand-50 text-navy-900 dark:bg-navy-950 dark:text-brand-50; + background-color: var(--color-brand-50); + color: var(--color-navy-900); color-scheme: light; } +html.dark, +html.dark body { + background-color: var(--color-navy-950); + color: var(--color-brand-50); +} + html.dark { color-scheme: dark; } diff --git a/package.json b/package.json index ca30df3..798bbe5 100644 --- a/package.json +++ b/package.json @@ -33,12 +33,10 @@ "devDependencies": { "@biomejs/biome": "2.3.14", "@react-router/dev": "^7.13.0", - "@tailwindcss/vite": "^4.1.18", "@types/node": "^22.19.11", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "lefthook": "^2.1.1", - "tailwindcss": "^4.1.18", "typescript": "^5.9.3", "vite": "^7.3.1", "vite-tsconfig-paths": "^5.1.4" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f33c4e3..9890669 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,9 +66,6 @@ importers: '@react-router/dev': specifier: ^7.13.0 version: 7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3))(@types/node@22.19.11)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.30.2)) - '@tailwindcss/vite': - specifier: ^4.1.18 - version: 4.1.18(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.30.2)) '@types/node': specifier: ^22.19.11 version: 22.19.11 @@ -81,9 +78,6 @@ importers: lefthook: specifier: ^2.1.1 version: 2.1.1 - tailwindcss: - specifier: ^4.1.18 - version: 4.1.18 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -831,100 +825,6 @@ packages: resolution: {integrity: sha512-KaIHsO6LU1cuqpYMsprQhfedRJFuTAf4dr8/k4/4Po6urRVXIWzmdY/lHPnf8Mzw2Fh0ueQVQYdaXzOvCBGnGQ==} engines: {node: '>=20.0.0'} - '@tailwindcss/node@4.1.18': - resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} - - '@tailwindcss/oxide-android-arm64@4.1.18': - resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [android] - - '@tailwindcss/oxide-darwin-arm64@4.1.18': - resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@tailwindcss/oxide-darwin-x64@4.1.18': - resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@tailwindcss/oxide-freebsd-x64@4.1.18': - resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [freebsd] - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': - resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==} - engines: {node: '>= 10'} - cpu: [arm] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': - resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-arm64-musl@4.1.18': - resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-linux-x64-gnu@4.1.18': - resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-x64-musl@4.1.18': - resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-wasm32-wasi@4.1.18': - resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' - - tslib - - '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': - resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@tailwindcss/oxide-win32-x64-msvc@4.1.18': - resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@tailwindcss/oxide@4.1.18': - resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==} - engines: {node: '>= 10'} - - '@tailwindcss/vite@4.1.18': - resolution: {integrity: sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==} - peerDependencies: - vite: ^5.2.0 || ^6 || ^7 - '@tanstack/react-table@8.21.3': resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==} engines: {node: '>=12'} @@ -1135,10 +1035,6 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} - enhanced-resolve@5.19.0: - resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} - engines: {node: '>=10.13.0'} - error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} @@ -1243,9 +1139,6 @@ packages: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -1452,9 +1345,6 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -1748,13 +1638,6 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - tailwindcss@4.1.18: - resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} - - tapable@2.3.0: - resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} - engines: {node: '>=6'} - tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -2585,74 +2468,6 @@ snapshots: - bufferutil - utf-8-validate - '@tailwindcss/node@4.1.18': - dependencies: - '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.19.0 - jiti: 2.6.1 - lightningcss: 1.30.2 - magic-string: 0.30.21 - source-map-js: 1.2.1 - tailwindcss: 4.1.18 - - '@tailwindcss/oxide-android-arm64@4.1.18': - optional: true - - '@tailwindcss/oxide-darwin-arm64@4.1.18': - optional: true - - '@tailwindcss/oxide-darwin-x64@4.1.18': - optional: true - - '@tailwindcss/oxide-freebsd-x64@4.1.18': - optional: true - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': - optional: true - - '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': - optional: true - - '@tailwindcss/oxide-linux-arm64-musl@4.1.18': - optional: true - - '@tailwindcss/oxide-linux-x64-gnu@4.1.18': - optional: true - - '@tailwindcss/oxide-linux-x64-musl@4.1.18': - optional: true - - '@tailwindcss/oxide-wasm32-wasi@4.1.18': - optional: true - - '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': - optional: true - - '@tailwindcss/oxide-win32-x64-msvc@4.1.18': - optional: true - - '@tailwindcss/oxide@4.1.18': - optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.1.18 - '@tailwindcss/oxide-darwin-arm64': 4.1.18 - '@tailwindcss/oxide-darwin-x64': 4.1.18 - '@tailwindcss/oxide-freebsd-x64': 4.1.18 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18 - '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18 - '@tailwindcss/oxide-linux-arm64-musl': 4.1.18 - '@tailwindcss/oxide-linux-x64-gnu': 4.1.18 - '@tailwindcss/oxide-linux-x64-musl': 4.1.18 - '@tailwindcss/oxide-wasm32-wasi': 4.1.18 - '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 - '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 - - '@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.30.2))': - dependencies: - '@tailwindcss/node': 4.1.18 - '@tailwindcss/oxide': 4.1.18 - tailwindcss: 4.1.18 - vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.30.2) - '@tanstack/react-table@8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@tanstack/table-core': 8.21.3 @@ -2832,7 +2647,8 @@ snapshots: destroy@1.2.0: {} - detect-libc@2.1.2: {} + detect-libc@2.1.2: + optional: true dom-helpers@5.2.1: dependencies: @@ -2851,11 +2667,6 @@ snapshots: encodeurl@2.0.0: {} - enhanced-resolve@5.19.0: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.0 - error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 @@ -3000,8 +2811,6 @@ snapshots: gopd@1.2.0: {} - graceful-fs@4.2.11: {} - has-symbols@1.1.0: {} hasown@2.0.2: @@ -3043,7 +2852,8 @@ snapshots: isbot@5.1.35: {} - jiti@2.6.1: {} + jiti@2.6.1: + optional: true js-tokens@4.0.0: {} @@ -3144,6 +2954,7 @@ snapshots: lightningcss-linux-x64-musl: 1.30.2 lightningcss-win32-arm64-msvc: 1.30.2 lightningcss-win32-x64-msvc: 1.30.2 + optional: true lines-and-columns@1.2.4: {} @@ -3157,10 +2968,6 @@ snapshots: dependencies: yallist: 3.1.1 - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - math-intrinsics@1.1.0: {} media-typer@0.3.0: {} @@ -3453,10 +3260,6 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - tailwindcss@4.1.18: {} - - tapable@2.3.0: {} - tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) diff --git a/vite.config.ts b/vite.config.ts index 3f3f041..c4140c9 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,10 +1,9 @@ import { reactRouter } from "@react-router/dev/vite"; -import tailwindcss from "@tailwindcss/vite"; import { defineConfig } from "vite"; import tsconfigPaths from "vite-tsconfig-paths"; export default defineConfig({ - plugins: [tailwindcss(), reactRouter(), tsconfigPaths()], + plugins: [reactRouter(), tsconfigPaths()], server: { port: 5174, }, From d47349ce1482329e4d2fbe785156f66a779eaaa7 Mon Sep 17 00:00:00 2001 From: tayloraj10 Date: Tue, 14 Jul 2026 20:50:44 -0400 Subject: [PATCH 4/4] Fix WaveDivider colors referencing removed CSS custom properties The trimmed app.css no longer defines --color-brand-400/500/700, so these now use the existing brand[] JS constants like the rest of the file already does. --- app/routes/unauthenticated/_layout.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/routes/unauthenticated/_layout.tsx b/app/routes/unauthenticated/_layout.tsx index a9ecfe6..0f74ef2 100644 --- a/app/routes/unauthenticated/_layout.tsx +++ b/app/routes/unauthenticated/_layout.tsx @@ -62,7 +62,7 @@ export default function UnauthenticatedLayout() { ))}