diff --git a/app/api/resource-revisions/methods.ts b/app/api/resource-revisions/methods.ts new file mode 100644 index 0000000..a703e6d --- /dev/null +++ b/app/api/resource-revisions/methods.ts @@ -0,0 +1,69 @@ +import type { SupabaseClient } from "@supabase/supabase-js"; +import type { + ResourceRevision, + RevisionStatus, +} from "~/types/ResourceRevision"; + +const TABLE_NAME = "resource_revisions"; + +export const getResourceRevisionAPI = (client: SupabaseClient) => { + const table = client.from(TABLE_NAME); + + return { + getList: async (params: { status?: RevisionStatus } = {}) => { + let query = table.select("*").order("date_created", { ascending: false }); + + if (params.status) { + query = query.eq("status", params.status); + } + + const { data, error } = await query; + + if (error) { + throw error; + } + + return (data ?? []) as ResourceRevision[]; + }, + getById: async (id: number) => { + const { data, error } = await table + .select("*") + .eq("id", id) + .single(); + + if (error) { + throw error; + } + + return data; + }, + updateStatus: async (id: number, status: RevisionStatus) => { + const { data, error } = await table + .update({ status }) + .eq("id", id) + .select() + .single(); + + if (error) { + throw error; + } + + return data; + }, + updateFields: async (id: number, values: Partial) => { + const { data, error } = await table + .update(values) + .eq("id", id) + .select() + .single(); + + if (error) { + throw error; + } + + return data; + }, + }; +}; + +export default getResourceRevisionAPI; 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/app/components/ThemeToggle.tsx b/app/components/ThemeToggle.tsx index c5ee989..206b561 100644 --- a/app/components/ThemeToggle.tsx +++ b/app/components/ThemeToggle.tsx @@ -4,8 +4,8 @@ import { useThemeMode } from "~/theme/ThemeModeProvider"; import { brand, navy } from "~/theme/theme"; // Styling and icon visibility are driven purely by the `.dark` class (via -// theme.applyStyles / Tailwind's dark: variant) rather than the `mode` value -// from React state, so this can never mismatch between SSR and hydration. +// theme.applyStyles) rather than the `mode` value from React state, so this +// can never mismatch between SSR and hydration. export function ThemeToggle() { const { toggleMode } = useThemeMode(); @@ -26,8 +26,19 @@ export function ThemeToggle() { }), })} > - - + ({ + ...theme.applyStyles("dark", { display: "none" }), + })} + /> + ({ + display: "none", + ...theme.applyStyles("dark", { display: "inline-block" }), + })} + /> ); 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/root.tsx b/app/root.tsx index 346688c..e68b313 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -1,3 +1,4 @@ +import { Box } from "@mui/material"; import { isRouteErrorResponse, Links, @@ -82,14 +83,17 @@ export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { } return ( -
+

{message}

{details}

{stack && ( -
+        
           {stack}
-        
+
)} -
+ ); } diff --git a/app/routes.ts b/app/routes.ts index de7ef64..4ba9f72 100644 --- a/app/routes.ts +++ b/app/routes.ts @@ -8,6 +8,7 @@ import { export default [ layout("routes/authenticated/_layout.tsx", [ index("routes/authenticated/dashboard.tsx"), + route("reviews", "routes/authenticated/reviews/index.tsx"), route("logout", "routes/authenticated/logout.tsx"), ]), route("auth", "routes/unauthenticated/_layout.tsx", [ diff --git a/app/routes/authenticated/_layout.tsx b/app/routes/authenticated/_layout.tsx index a0983a8..0634bd2 100644 --- a/app/routes/authenticated/_layout.tsx +++ b/app/routes/authenticated/_layout.tsx @@ -1,50 +1,106 @@ +import { Box } from "@mui/material"; +import { styled } from "@mui/material/styles"; import { Link, NavLink, Outlet } from "react-router"; import phlasklogo from "~/assets/PHLASK_v2.svg"; export const action = () => {}; +const SidebarNavLink = styled(NavLink)({ + display: "block", + borderRadius: 8, + paddingInline: 16, + paddingBlock: 10, + fontSize: "0.875rem", + fontWeight: 500, + textDecoration: "none", + color: "#4b5563", + transition: "background-color 0.2s ease, color 0.2s ease", + "&:hover": { backgroundColor: "#f3f4f6" }, + "&.active": { + color: "#1d4ed8", + backgroundColor: "#eff6ff", + }, +}); + export default function DashboardLayout() { return ( -
- + + + -
-
+ + -
-
-
+ + + ); } diff --git a/app/routes/authenticated/reviews/index.tsx b/app/routes/authenticated/reviews/index.tsx new file mode 100644 index 0000000..3c13900 --- /dev/null +++ b/app/routes/authenticated/reviews/index.tsx @@ -0,0 +1,189 @@ +import { + Chip, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, +} from "@mui/material"; +import { + type ColumnDef, + flexRender, + getCoreRowModel, + getSortedRowModel, + type SortingState, + useReactTable, +} from "@tanstack/react-table"; +import { useMemo, useState } from "react"; +import { type LoaderFunction, useLoaderData, useNavigate } from "react-router"; +import { getDatabaseClient } from "~/api/client.server"; +import { getResourceRevisionAPI } from "~/api/resource-revisions/methods"; +import { authMiddleware } from "~/middleware/auth"; +import type { ResourceRevision } from "~/types/ResourceRevision"; +import { + resourceTypeChipColor, + resourceTypeChipIcon, +} from "~/utils/chipColors"; + +export const middleware = [authMiddleware]; + +export const loader: LoaderFunction = async ({ request }) => { + const { client } = getDatabaseClient(request); + const revisionAPI = getResourceRevisionAPI(client); + const revisions = await revisionAPI.getList({ status: "PENDING" }); + + const resourceIds = [...new Set(revisions.map((r) => r.mapped_resources))]; + const resourceNames = new Map(); + + if (resourceIds.length > 0) { + const { data, error } = await client + .from("resources") + .select("id, name, address") + .in("id", resourceIds); + + if (error) { + throw error; + } + + for (const resource of data ?? []) { + resourceNames.set(resource.id, resource.name || resource.address || null); + } + } + + return { + revisions, + resourceNames: Object.fromEntries(resourceNames), + }; +}; + +type RowData = ResourceRevision & { resourceLabel: string }; + +const columns: ColumnDef[] = [ + { + accessorKey: "name", + header: "Proposed name", + cell: ({ row }) => row.original.name || "—", + }, + { + accessorKey: "resource_type", + header: "Type", + cell: ({ row }) => ( + + ), + }, + { + accessorKey: "resourceLabel", + header: "Existing resource", + }, + { + accessorKey: "date_created", + header: "Submitted", + cell: ({ row }) => + new Date(row.original.date_created).toLocaleString(undefined, { + dateStyle: "medium", + timeStyle: "short", + }), + }, +]; + +const ReviewsQueue = () => { + const { revisions, resourceNames } = useLoaderData<{ + revisions: ResourceRevision[]; + resourceNames: Record; + }>(); + const navigate = useNavigate(); + const [sorting, setSorting] = useState([ + { id: "date_created", desc: true }, + ]); + + const data: RowData[] = useMemo( + () => + revisions.map((revision) => ({ + ...revision, + resourceLabel: + resourceNames[revision.mapped_resources] || + `Resource #${revision.mapped_resources}`, + })), + [revisions, resourceNames], + ); + + const table = useReactTable({ + data, + columns, + state: { sorting }, + onSortingChange: setSorting, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + }); + return ( +
+ + Resource edit reviews + + + Proposed edits to existing PHLask resources, pending approval. + + + + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {flexRender( + header.column.columnDef.header, + header.getContext(), + )} + {{ asc: " ↑", desc: " ↓" }[ + header.column.getIsSorted() as string + ] ?? null} + + ))} + + ))} + + + {table.getRowModel().rows.length === 0 && ( + + + + No pending reviews. Nothing to do here right now. + + + + )} + {table.getRowModel().rows.map((row) => ( + navigate(`/reviews/${row.original.id}`)} + hover + sx={{ cursor: "pointer" }} + > + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ))} + +
+
+
+ ); +}; + +export default ReviewsQueue; diff --git a/app/routes/unauthenticated/_layout.tsx b/app/routes/unauthenticated/_layout.tsx index a855b88..0f74ef2 100644 --- a/app/routes/unauthenticated/_layout.tsx +++ b/app/routes/unauthenticated/_layout.tsx @@ -1,23 +1,133 @@ +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 }, + { 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 ( -
- + ({ + 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, + height: bubble.size, + animation: `bubble-rise ${bubble.duration}s ease-in infinite`, + animationDelay: `${bubble.delay}s`, + }} + /> + ))} + + ({ + bottom: 0, + ...theme.applyStyles("dark", { opacity: 0.2 }), + })} + /> + ({ + bottom: 0, + ...theme.applyStyles("dark", { opacity: 0.25 }), + })} + /> + ({ + bottom: 0, + ...theme.applyStyles("dark", { opacity: 0.3 }), + })} + /> -
-
+ + + ({ + 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`, + }), + })} + > -
-
-
+ + + ); } diff --git a/app/types/ResourceRevision.ts b/app/types/ResourceRevision.ts new file mode 100644 index 0000000..cd79fa6 --- /dev/null +++ b/app/types/ResourceRevision.ts @@ -0,0 +1,29 @@ +import type { ResourceEntry } from "~/types/ResourceEntry"; + +/** + * Review status of a `resource_revisions` row. Note this table reuses the + * `status` column for review state rather than the resource's operational + * status (`ResourceStatus`) — the two are unrelated despite the shared + * column name. + */ +export type RevisionStatus = "PENDING" | "APPROVED" | "REJECTED"; + +/** + * A proposed edit to an existing resource, staged in `resource_revisions`. + * + * Shape mirrors `ResourceEntry` (it's a full proposed snapshot of the + * resource) plus: + * - `mapped_resources`: FK to `resources.id` — the resource this revision + * proposes changes to. Every revision maps to an existing resource; this + * table has no concept of a brand-new, not-yet-existing resource. + * - `mapped_resource`: a second, unconstrained (no FK) int column that + * exists on the table but isn't used by this feature. + * - `status`: review state (`PENDING` / `APPROVED` / `REJECTED`), not the + * resource's operational status. + */ +export type ResourceRevision = Omit & { + id: number; + mapped_resource: number; + mapped_resources: number; + status: RevisionStatus; +}; diff --git a/app/utils/chipColors.tsx b/app/utils/chipColors.tsx new file mode 100644 index 0000000..9509c68 --- /dev/null +++ b/app/utils/chipColors.tsx @@ -0,0 +1,87 @@ +import { LocalDining, Park, WaterDrop, Wc } from "@mui/icons-material"; +import type { ChipProps } from "@mui/material"; +import type { ReactElement } from "react"; +import type { ResourceType } from "~/types/ResourceEntry"; +import type { RevisionStatus } from "~/types/ResourceRevision"; + +export const resourceTypeChipColor = ( + type: ResourceType | string, +): ChipProps["color"] => { + switch (type) { + case "WATER": + return "primary"; + case "FOOD": + return "warning"; + case "FORAGE": + return "success"; + case "BATHROOM": + return "secondary"; + default: + return "default"; + } +}; + +export const resourceTypeChipIcon = ( + type: ResourceType | string, +): ReactElement | undefined => { + switch (type) { + case "WATER": + return ( + + ); + case "FOOD": + return ( + + ); + case "FORAGE": + return ( + + ); + case "BATHROOM": + return ( + + ); + default: + return undefined; + } +}; + +export const statusChipColor = ( + status: RevisionStatus | string, +): ChipProps["color"] => { + switch (status) { + case "PENDING": + return "warning"; + case "APPROVED": + return "success"; + case "REJECTED": + return "error"; + default: + return "default"; + } +}; 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, },