From 052f53444e35d95291cebbf8c280a6ac6885d333 Mon Sep 17 00:00:00 2001 From: Kyle Yohler Date: Sat, 30 Aug 2025 01:42:23 -0500 Subject: [PATCH] significant refactor --- drizzle.config.ts | 10 - package.json | 8 +- src/app/routeTree.gen.ts | 21 -- src/app/router.tsx | 36 +-- src/app/routes/__root.tsx | 126 +++++---- src/app/routes/_authed.tsx | 20 +- src/app/routes/_authed/admin.tsx | 14 +- src/app/routes/_authed/admin/index.tsx | 10 +- src/app/routes/_authed/admin/preview.tsx | 16 +- .../routes/_authed/admin/tournaments/$id.tsx | 30 ++- .../_authed/admin/tournaments/index.tsx | 8 +- src/app/routes/_authed/index.tsx | 5 - src/app/routes/_authed/profile.$playerId.tsx | 24 +- src/app/routes/_authed/settings.tsx | 50 ++-- src/app/routes/_authed/teams.$teamId.tsx | 17 +- .../_authed/tournaments/$tournamentId.tsx | 16 +- src/app/routes/_authed/tournaments/index.tsx | 8 +- src/app/routes/api/test.ts | 9 - src/app/routes/login.tsx | 13 +- src/app/routes/logout.tsx | 19 +- src/components/DefaultCatchBoundary.tsx | 2 +- src/components/avatar.tsx | 43 ++- src/components/button.tsx | 11 +- src/components/date-time-picker.tsx | 35 +-- src/components/list-button.tsx | 19 +- src/components/list-link.tsx | 18 +- src/components/page.tsx | 16 +- src/components/phone-number-input.tsx | 40 ++- src/components/sheet/drawer.tsx | 63 +++-- src/components/sheet/modal.tsx | 8 +- src/components/sheet/sheet.tsx | 20 +- .../sheet/slide-panel/slide-panel-field.tsx | 51 +++- .../sheet/slide-panel/slide-panel.tsx | 107 +++++--- src/components/swipeable-tabs.tsx | 126 +++++---- src/contexts/auth-context.tsx | 21 +- src/features/admin/components/admin-page.tsx | 28 +- .../admin/components/manage-tournaments.tsx | 7 +- src/features/admin/index.ts | 4 +- src/features/bracket/components/bracket.tsx | 14 +- .../bracket/components/match-slot.tsx | 50 ++-- src/features/bracket/components/preview.tsx | 10 +- .../bracket/components/seed-badge.tsx | 22 +- src/features/bracket/components/seed-list.tsx | 8 +- src/features/bracket/queries.ts | 9 +- src/features/bracket/server.ts | 17 +- src/features/bracket/types.ts | 1 - src/features/bracket/utils/bracket-maps.ts | 22 +- src/features/bracket/utils/index.ts | 38 +-- .../core/components/nav-link/nav-link.tsx | 5 +- src/features/core/components/navbar.tsx | 1 + src/features/core/hooks/use-links.ts | 4 +- .../login/components/player-prompt/index.tsx | 7 +- src/features/login/hooks/use-consume-code.ts | 8 +- src/features/login/hooks/use-create-user.ts | 39 +-- .../players/components/profile/index.tsx | 16 +- .../players/components/profile/name-form.tsx | 37 ++- src/features/players/queries.ts | 50 +++- src/features/players/server.ts | 103 +++----- src/features/players/types.ts | 4 +- src/features/teams/components/team-list.tsx | 88 +++--- .../teams/components/team-profile/header.tsx | 19 +- .../teams/components/team-profile/index.tsx | 42 +-- src/features/teams/queries.ts | 9 +- src/features/teams/server.ts | 9 +- src/features/teams/types.ts | 54 ++-- .../components/edit-enrolled-teams.tsx | 99 ++++--- .../components/manage-tournament.tsx | 77 ++++-- .../tournaments/components/profile/index.tsx | 12 +- .../components/tournament-card.tsx | 87 +++--- .../components/tournament-form.tsx | 250 ++++++++++-------- .../hooks/use-create-tournament.ts | 24 +- .../tournaments/hooks/use-enroll-team.ts | 24 +- .../tournaments/hooks/use-unenroll-team.ts | 26 +- .../hooks/use-update-tournament.ts | 8 +- src/features/tournaments/queries.ts | 19 +- src/features/tournaments/server.ts | 82 ++---- src/features/tournaments/types.ts | 14 - src/hooks/use-appshell-height.ts | 12 +- src/hooks/use-is-mobile.ts | 6 +- src/hooks/use-sheet.ts | 6 +- src/lib/logger/index.ts | 140 ++++------ src/lib/mantine/color-scheme-provider.tsx | 15 +- src/lib/mantine/mantine-provider.tsx | 65 ++--- src/lib/pocketbase/client.ts | 40 +-- src/lib/pocketbase/services/players.ts | 34 +-- src/lib/pocketbase/services/teams.ts | 14 +- src/lib/pocketbase/services/tournaments.ts | 85 +++--- src/lib/pocketbase/util/transform-types.ts | 4 +- src/lib/sonner/index.tsx | 43 +-- src/lib/supertokens/client.ts | 30 +-- .../recipes/passwordless-development-mode.ts | 22 +- .../recipes/passwordless-twilio-verify.ts | 36 +-- src/lib/supertokens/recipes/start-session.ts | 18 +- src/lib/supertokens/server.ts | 20 +- src/lib/tanstack-query/hooks/index.ts | 4 + .../hooks/use-optimistic-mutation.ts | 37 +++ .../hooks/use-server-mutation.ts | 47 ++++ .../tanstack-query/hooks/use-server-query.ts | 31 +++ .../tanstack-query/hooks/use-server-result.ts | 132 --------- .../hooks/user-server-suspense-query.ts | 32 +++ src/lib/tanstack-query/utils/ensure.ts | 23 ++ src/lib/tanstack-query/utils/prefetch.ts | 23 ++ .../{utils.ts => utils/to-server-result.ts} | 29 +- src/lib/twilio/index.ts | 18 +- src/utils/supertokens.ts | 238 +++++++++-------- src/utils/test-event.ts | 4 +- 106 files changed, 1994 insertions(+), 1701 deletions(-) delete mode 100644 drizzle.config.ts delete mode 100644 src/app/routes/api/test.ts create mode 100644 src/lib/tanstack-query/hooks/index.ts create mode 100644 src/lib/tanstack-query/hooks/use-optimistic-mutation.ts create mode 100644 src/lib/tanstack-query/hooks/use-server-mutation.ts create mode 100644 src/lib/tanstack-query/hooks/use-server-query.ts delete mode 100644 src/lib/tanstack-query/hooks/use-server-result.ts create mode 100644 src/lib/tanstack-query/hooks/user-server-suspense-query.ts create mode 100644 src/lib/tanstack-query/utils/ensure.ts create mode 100644 src/lib/tanstack-query/utils/prefetch.ts rename src/lib/tanstack-query/{utils.ts => utils/to-server-result.ts} (73%) diff --git a/drizzle.config.ts b/drizzle.config.ts deleted file mode 100644 index cab06d8..0000000 --- a/drizzle.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from "drizzle-kit"; - -export default defineConfig({ - schema: "./src/lib/drizzle/schema", - out: "./src/lib/drizzle/migrations", - dialect: "postgresql", - dbCredentials: { - url: process.env.VITE_DATABASE_URL ?? "", - }, -}); \ No newline at end of file diff --git a/package.json b/package.json index 17d7828..d617fa3 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,7 @@ "scripts": { "dev": "vite dev --host 0.0.0.0", "build": "vite build && tsc --noEmit", - "start": "vite start", - "db:generate": "npx drizzle-kit generate --config drizzle.config.ts", - "db:push": "npx drizzle-kit push", - "db:studio": "npx drizzle-kit studio" + "start": "vite start" }, "dependencies": { "@hello-pangea/dnd": "^18.0.1", @@ -28,8 +25,6 @@ "@tanstack/react-router-with-query": "^1.130.12", "@tanstack/react-start": "^1.130.15", "@types/ioredis": "^4.28.10", - "drizzle-orm": "^0.44.4", - "drizzle-zod": "^0.8.3", "embla-carousel-react": "^8.6.0", "framer-motion": "^12.23.12", "ioredis": "^5.7.0", @@ -57,7 +52,6 @@ "@vitejs/plugin-react": "^5.0.0", "autoprefixer": "^10.4.20", "dotenv-cli": "^10.0.0", - "drizzle-kit": "^0.31.4", "postcss": "^8.5.1", "postcss-preset-mantine": "^1.18.0", "postcss-simple-vars": "^7.0.1", diff --git a/src/app/routeTree.gen.ts b/src/app/routeTree.gen.ts index c689437..cf8736a 100644 --- a/src/app/routeTree.gen.ts +++ b/src/app/routeTree.gen.ts @@ -25,7 +25,6 @@ import { Route as AuthedProfilePlayerIdRouteImport } from './routes/_authed/prof import { Route as AuthedAdminPreviewRouteImport } from './routes/_authed/admin/preview' import { Route as AuthedAdminTournamentsIndexRouteImport } from './routes/_authed/admin/tournaments/index' import { Route as AuthedAdminTournamentsIdRouteImport } from './routes/_authed/admin/tournaments/$id' -import { ServerRoute as ApiTestServerRouteImport } from './routes/api/test' import { ServerRoute as ApiTournamentsUploadLogoServerRouteImport } from './routes/api/tournaments/upload-logo' import { ServerRoute as ApiEventsSplatServerRouteImport } from './routes/api/events.$' import { ServerRoute as ApiAuthSplatServerRouteImport } from './routes/api/auth.$' @@ -105,11 +104,6 @@ const AuthedAdminTournamentsIdRoute = path: '/tournaments/$id', getParentRoute: () => AuthedAdminRoute, } as any) -const ApiTestServerRoute = ApiTestServerRouteImport.update({ - id: '/api/test', - path: '/api/test', - getParentRoute: () => rootServerRouteImport, -} as any) const ApiTournamentsUploadLogoServerRoute = ApiTournamentsUploadLogoServerRouteImport.update({ id: '/api/tournaments/upload-logo', @@ -233,14 +227,12 @@ export interface RootRouteChildren { LogoutRoute: typeof LogoutRoute } export interface FileServerRoutesByFullPath { - '/api/test': typeof ApiTestServerRoute '/api/auth/$': typeof ApiAuthSplatServerRoute '/api/events/$': typeof ApiEventsSplatServerRoute '/api/tournaments/upload-logo': typeof ApiTournamentsUploadLogoServerRoute '/api/files/$collection/$recordId/$file': typeof ApiFilesCollectionRecordIdFileServerRoute } export interface FileServerRoutesByTo { - '/api/test': typeof ApiTestServerRoute '/api/auth/$': typeof ApiAuthSplatServerRoute '/api/events/$': typeof ApiEventsSplatServerRoute '/api/tournaments/upload-logo': typeof ApiTournamentsUploadLogoServerRoute @@ -248,7 +240,6 @@ export interface FileServerRoutesByTo { } export interface FileServerRoutesById { __root__: typeof rootServerRouteImport - '/api/test': typeof ApiTestServerRoute '/api/auth/$': typeof ApiAuthSplatServerRoute '/api/events/$': typeof ApiEventsSplatServerRoute '/api/tournaments/upload-logo': typeof ApiTournamentsUploadLogoServerRoute @@ -257,21 +248,18 @@ export interface FileServerRoutesById { export interface FileServerRouteTypes { fileServerRoutesByFullPath: FileServerRoutesByFullPath fullPaths: - | '/api/test' | '/api/auth/$' | '/api/events/$' | '/api/tournaments/upload-logo' | '/api/files/$collection/$recordId/$file' fileServerRoutesByTo: FileServerRoutesByTo to: - | '/api/test' | '/api/auth/$' | '/api/events/$' | '/api/tournaments/upload-logo' | '/api/files/$collection/$recordId/$file' id: | '__root__' - | '/api/test' | '/api/auth/$' | '/api/events/$' | '/api/tournaments/upload-logo' @@ -279,7 +267,6 @@ export interface FileServerRouteTypes { fileServerRoutesById: FileServerRoutesById } export interface RootServerRouteChildren { - ApiTestServerRoute: typeof ApiTestServerRoute ApiAuthSplatServerRoute: typeof ApiAuthSplatServerRoute ApiEventsSplatServerRoute: typeof ApiEventsSplatServerRoute ApiTournamentsUploadLogoServerRoute: typeof ApiTournamentsUploadLogoServerRoute @@ -390,13 +377,6 @@ declare module '@tanstack/react-router' { } declare module '@tanstack/react-start/server' { interface ServerFileRoutesByPath { - '/api/test': { - id: '/api/test' - path: '/api/test' - fullPath: '/api/test' - preLoaderRoute: typeof ApiTestServerRouteImport - parentRoute: typeof rootServerRouteImport - } '/api/tournaments/upload-logo': { id: '/api/tournaments/upload-logo' path: '/api/tournaments/upload-logo' @@ -478,7 +458,6 @@ export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() const rootServerRouteChildren: RootServerRouteChildren = { - ApiTestServerRoute: ApiTestServerRoute, ApiAuthSplatServerRoute: ApiAuthSplatServerRoute, ApiEventsSplatServerRoute: ApiEventsSplatServerRoute, ApiTournamentsUploadLogoServerRoute: ApiTournamentsUploadLogoServerRoute, diff --git a/src/app/router.tsx b/src/app/router.tsx index 3bd66d5..02fe954 100644 --- a/src/app/router.tsx +++ b/src/app/router.tsx @@ -1,9 +1,9 @@ -import { QueryClient } from '@tanstack/react-query' -import { createRouter as createTanStackRouter } from '@tanstack/react-router' -import { routerWithQueryClient } from '@tanstack/react-router-with-query' -import { routeTree } from './routeTree.gen' -import { DefaultCatchBoundary } from '../components/DefaultCatchBoundary' -import { defaultHeaderConfig } from '@/features/core/hooks/use-router-config' +import { QueryClient } from "@tanstack/react-query"; +import { createRouter as createTanStackRouter } from "@tanstack/react-router"; +import { routerWithQueryClient } from "@tanstack/react-router-with-query"; +import { routeTree } from "./routeTree.gen"; +import { DefaultCatchBoundary } from "../components/DefaultCatchBoundary"; +import { defaultHeaderConfig } from "@/features/core/hooks/use-router-config"; export function createRouter() { const queryClient = new QueryClient({ @@ -12,27 +12,33 @@ export function createRouter() { staleTime: 60 * 1000, // 60 seconds gcTime: 5 * 60 * 1000, // 5 minutes refetchOnWindowFocus: false, - refetchOnReconnect: 'always', + refetchOnReconnect: "always", retry: 3, }, }, - }) + }); return routerWithQueryClient( createTanStackRouter({ routeTree, - context: { queryClient, auth: undefined!, header: defaultHeaderConfig, refresh: [], withPadding: true }, - defaultPreload: 'intent', + context: { + queryClient, + auth: undefined!, + header: defaultHeaderConfig, + refresh: [], + withPadding: true, + }, + defaultPreload: "intent", defaultErrorComponent: DefaultCatchBoundary, scrollRestoration: true, - defaultViewTransition: true + defaultViewTransition: true, }), - queryClient, - ) + queryClient + ); } -declare module '@tanstack/react-router' { +declare module "@tanstack/react-router" { interface Register { - router: ReturnType + router: ReturnType; } } diff --git a/src/app/routes/__root.tsx b/src/app/routes/__root.tsx index 9c232ad..574b364 100644 --- a/src/app/routes/__root.tsx +++ b/src/app/routes/__root.tsx @@ -1,59 +1,63 @@ -import '@mantine/core/styles.css'; -import '@mantine/dates/styles.css'; -import '@mantine/carousel/styles.css'; +import "@mantine/core/styles.css"; +import "@mantine/dates/styles.css"; +import "@mantine/carousel/styles.css"; import { HeadContent, Navigate, Outlet, Scripts, - createRootRouteWithContext -} from '@tanstack/react-router' -import * as React from 'react' -import { DefaultCatchBoundary } from '@/components/DefaultCatchBoundary' -import { type QueryClient } from '@tanstack/react-query' -import { ensureSuperTokensFrontend } from '@/lib/supertokens/client' -import { AuthContextType, authQueryConfig } from '@/contexts/auth-context' -import Providers from '@/features/core/components/providers' -import { ColorSchemeScript, mantineHtmlProps } from '@mantine/core'; -import { HeaderConfig } from '@/features/core/types/header-config'; + createRootRouteWithContext, +} from "@tanstack/react-router"; +import * as React from "react"; +import { DefaultCatchBoundary } from "@/components/DefaultCatchBoundary"; +import { type QueryClient } from "@tanstack/react-query"; +import { ensureSuperTokensFrontend } from "@/lib/supertokens/client"; +import { AuthContextType } from "@/contexts/auth-context"; +import Providers from "@/features/core/components/providers"; +import { ColorSchemeScript, mantineHtmlProps } from "@mantine/core"; +import { HeaderConfig } from "@/features/core/types/header-config"; +import { playerQueries } from "@/features/players/queries"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure"; export const Route = createRootRouteWithContext<{ - queryClient: QueryClient, - auth: AuthContextType, - header: HeaderConfig, - refresh: string[] - withPadding: boolean + queryClient: QueryClient; + auth: AuthContextType; + header: HeaderConfig; + refresh: string[]; + withPadding: boolean; }>()({ head: () => ({ meta: [ { - charSet: 'utf-8' + charSet: "utf-8", }, { - name: 'viewport', - content: 'width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, interactive-widget=overlays-content', - } + name: "viewport", + content: + "width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, interactive-widget=overlays-content", + }, ], links: [ { - rel: 'apple-touch-icon', - sizes: '180x180', - href: '/apple-touch-icon.png', + rel: "apple-touch-icon", + sizes: "180x180", + href: "/apple-touch-icon.png", }, { - rel: 'icon', - type: 'image/png', - sizes: '32x32', - href: '/favicon-32x32.png', + rel: "icon", + type: "image/png", + sizes: "32x32", + href: "/favicon-32x32.png", }, { - rel: 'icon', - type: 'image/png', - sizes: '16x16', - href: '/favicon-16x16.png', + rel: "icon", + type: "image/png", + sizes: "16x16", + href: "/favicon-16x16.png", }, - { rel: 'manifest', href: '/site.webmanifest' }, - { rel: 'icon', href: '/favicon.ico' }, + { rel: "manifest", href: "/site.webmanifest" }, + { rel: "icon", href: "/favicon.ico" }, ], }), errorComponent: (props) => { @@ -61,25 +65,24 @@ export const Route = createRootRouteWithContext<{ - ) + ); }, component: RootComponent, notFoundComponent: () => , beforeLoad: async ({ context }) => { - // I don't really like this. I wish there was some way before the router is rendered to useAuth() and pass context there. - // See: https://github.com/TanStack/router/discussions/3531 - const auth = await context.queryClient.ensureQueryData(authQueryConfig) - return { - auth - }; - } -}) + const auth = await ensureServerQueryData( + context.queryClient, + playerQueries.auth() + ); + return { auth }; + }, + pendingComponent: () =>

Loading...

, +}); function RootComponent() { - React.useEffect(() => { - ensureSuperTokensFrontend() - }, []) + ensureSuperTokensFrontend(); + }, []); return ( @@ -87,25 +90,38 @@ function RootComponent() { - ) + ); } // todo: analytics -> process.env data-website-id function RootDocument({ children }: { children: React.ReactNode }) { return ( - + - - -
- {children} -
+ +
{children}
+ - ) + ); } diff --git a/src/app/routes/_authed.tsx b/src/app/routes/_authed.tsx index 9ed65a3..55c176f 100644 --- a/src/app/routes/_authed.tsx +++ b/src/app/routes/_authed.tsx @@ -1,18 +1,19 @@ import { redirect, createFileRoute, Outlet } from "@tanstack/react-router"; import Layout from "@/features/core/components/layout"; import { useServerEvents } from "@/hooks/use-server-events"; +import { Loader } from "@mantine/core"; -export const Route = createFileRoute('/_authed')({ +export const Route = createFileRoute("/_authed")({ beforeLoad: ({ context }) => { if (!context.auth?.user) { - throw redirect({ to: '/login' }) + throw redirect({ to: "/login" }); } return { auth: { ...context.auth, - user: context.auth.user - } + user: context.auth.user, + }, }; }, component: () => { @@ -21,6 +22,11 @@ export const Route = createFileRoute('/_authed')({ - ) - } -}) + ); + }, + pendingComponent: () => ( + + + + ), +}); diff --git a/src/app/routes/_authed/admin.tsx b/src/app/routes/_authed/admin.tsx index 44d7901..09755e1 100644 --- a/src/app/routes/_authed/admin.tsx +++ b/src/app/routes/_authed/admin.tsx @@ -1,18 +1,18 @@ import { Outlet, redirect, createFileRoute } from "@tanstack/react-router"; -export const Route = createFileRoute('/_authed/admin')({ +export const Route = createFileRoute("/_authed/admin")({ component: Outlet, beforeLoad: ({ context }) => { - if (!context.auth?.roles?.includes('Admin')) { - throw redirect({ to: '/' }) + if (!context.auth?.roles?.includes("Admin")) { + throw redirect({ to: "/" }); } return { header: { ...context.header, - title: 'Admin', - withBackButton: true + title: "Admin", + withBackButton: true, }, }; - } -}) + }, +}); diff --git a/src/app/routes/_authed/admin/index.tsx b/src/app/routes/_authed/admin/index.tsx index 6d92114..7468ff0 100644 --- a/src/app/routes/_authed/admin/index.tsx +++ b/src/app/routes/_authed/admin/index.tsx @@ -1,13 +1,13 @@ -import { createFileRoute } from "@tanstack/react-router" +import { createFileRoute } from "@tanstack/react-router"; import { AdminPage } from "@/features/admin"; export const Route = createFileRoute("/_authed/admin/")({ loader: () => ({ header: { withBackButton: true, - title: "Admin" + title: "Admin", }, - withPadding: false + withPadding: false, }), - component: () => -}) + component: () => , +}); diff --git a/src/app/routes/_authed/admin/preview.tsx b/src/app/routes/_authed/admin/preview.tsx index 95353f7..076fb33 100644 --- a/src/app/routes/_authed/admin/preview.tsx +++ b/src/app/routes/_authed/admin/preview.tsx @@ -1,17 +1,17 @@ -import { PreviewBracket } from '@/features/bracket/components/preview' -import { createFileRoute } from '@tanstack/react-router' +import { PreviewBracket } from "@/features/bracket/components/preview"; +import { createFileRoute } from "@tanstack/react-router"; -export const Route = createFileRoute('/_authed/admin/preview')({ +export const Route = createFileRoute("/_authed/admin/preview")({ component: RouteComponent, loader: () => ({ header: { withBackButton: true, - title: "Bracket Preview" + title: "Bracket Preview", }, - withPadding: false - }) -}) + withPadding: false, + }), +}); function RouteComponent() { - return + return ; } diff --git a/src/app/routes/_authed/admin/tournaments/$id.tsx b/src/app/routes/_authed/admin/tournaments/$id.tsx index f1b67c4..2870035 100644 --- a/src/app/routes/_authed/admin/tournaments/$id.tsx +++ b/src/app/routes/_authed/admin/tournaments/$id.tsx @@ -1,27 +1,31 @@ -import { createFileRoute, redirect } from '@tanstack/react-router' -import { tournamentQueries } from '@/features/tournaments/queries' -import ManageTournament from '@/features/tournaments/components/manage-tournament' +import { createFileRoute, redirect } from "@tanstack/react-router"; +import { tournamentQueries } from "@/features/tournaments/queries"; +import ManageTournament from "@/features/tournaments/components/manage-tournament"; +import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure"; -export const Route = createFileRoute('/_authed/admin/tournaments/$id')({ +export const Route = createFileRoute("/_authed/admin/tournaments/$id")({ beforeLoad: async ({ context, params }) => { const { queryClient } = context; - const tournament = await queryClient.ensureQueryData(tournamentQueries.details(params.id)) - if (!tournament) throw redirect({ to: '/admin/tournaments' }); + const tournament = await ensureServerQueryData( + queryClient, + tournamentQueries.details(params.id) + ); + if (!tournament) throw redirect({ to: "/admin/tournaments" }); return { - tournament - } + tournament, + }; }, loader: ({ context }) => ({ header: { withBackButton: true, title: `Manage ${context.tournament.name}`, }, - withPadding: false + withPadding: false, }), component: RouteComponent, -}) +}); function RouteComponent() { - const { id } = Route.useParams() - return -} \ No newline at end of file + const { id } = Route.useParams(); + return ; +} diff --git a/src/app/routes/_authed/admin/tournaments/index.tsx b/src/app/routes/_authed/admin/tournaments/index.tsx index 8b9ab86..1c69d84 100644 --- a/src/app/routes/_authed/admin/tournaments/index.tsx +++ b/src/app/routes/_authed/admin/tournaments/index.tsx @@ -1,12 +1,12 @@ -import Page from "@/components/page"; import ManageTournaments from "@/features/admin/components/manage-tournaments"; import { tournamentQueries } from "@/features/tournaments/queries"; +import { prefetchServerQuery } from "@/lib/tanstack-query/utils/prefetch"; import { createFileRoute } from "@tanstack/react-router"; export const Route = createFileRoute("/_authed/admin/tournaments/")({ beforeLoad: async ({ context }) => { const { queryClient } = context; - await queryClient.ensureQueryData(tournamentQueries.list()); + await prefetchServerQuery(queryClient, tournamentQueries.list()); }, loader: () => ({ header: { @@ -14,11 +14,11 @@ export const Route = createFileRoute("/_authed/admin/tournaments/")({ title: "Manage Tournaments", }, refresh: tournamentQueries.list().queryKey, - withPadding: false + withPadding: false, }), component: RouteComponent, }); function RouteComponent() { - return + return ; } diff --git a/src/app/routes/_authed/index.tsx b/src/app/routes/_authed/index.tsx index 7c0f17a..be521c3 100644 --- a/src/app/routes/_authed/index.tsx +++ b/src/app/routes/_authed/index.tsx @@ -1,15 +1,10 @@ import { createFileRoute } from "@tanstack/react-router"; -import Page from "@/components/page"; import { TrophyIcon } from "@phosphor-icons/react"; import ListLink from "@/components/list-link"; -import { tournamentQueries } from "@/features/tournaments/queries"; import { Box, Divider, Text } from "@mantine/core"; export const Route = createFileRoute("/_authed/")({ component: Home, - beforeLoad: async ({ context }) => { - await context.queryClient.ensureQueryData(tournamentQueries.list()); - }, loader: () => ({ withPadding: false }) diff --git a/src/app/routes/_authed/profile.$playerId.tsx b/src/app/routes/_authed/profile.$playerId.tsx index 5ebfd2e..aed4459 100644 --- a/src/app/routes/_authed/profile.$playerId.tsx +++ b/src/app/routes/_authed/profile.$playerId.tsx @@ -1,7 +1,7 @@ -import Page from "@/components/page"; import Profile from "@/features/players/components/profile"; import { playerQueries } from "@/features/players/queries"; -import { redirect, createFileRoute } from "@tanstack/react-router"; +import { prefetchServerQuery } from "@/lib/tanstack-query/utils/prefetch"; +import { createFileRoute } from "@tanstack/react-router"; import { z } from "zod"; const searchSchema = z.object({ @@ -12,22 +12,22 @@ export const Route = createFileRoute("/_authed/profile/$playerId")({ validateSearch: searchSchema, beforeLoad: async ({ params, context }) => { const { queryClient } = context; - const player = await queryClient.ensureQueryData(playerQueries.details(params.playerId)) - if (!player) throw redirect({ to: '/' }); - return { - player - } + await prefetchServerQuery( + queryClient, + playerQueries.details(params.playerId) + ); }, loader: ({ params, context }) => ({ header: { collapsed: true, withBackButton: true, - settingsLink: context?.auth.user.id === params.playerId ? 'settings' : undefined + settingsLink: + context?.auth.user.id === params.playerId ? "/settings" : undefined, }, - refresh: [playerQueries.details(params.playerId).queryKey] + refresh: [playerQueries.details(params.playerId).queryKey], }), component: () => { - const { player } = Route.useRouteContext(); - return + const { playerId } = Route.useParams(); + return ; }, -}) +}); diff --git a/src/app/routes/_authed/settings.tsx b/src/app/routes/_authed/settings.tsx index 71e09fc..bc5f152 100644 --- a/src/app/routes/_authed/settings.tsx +++ b/src/app/routes/_authed/settings.tsx @@ -1,34 +1,38 @@ -import { createFileRoute } from "@tanstack/react-router" -import { Box, Title, Stack } from "@mantine/core" -import { ColorSchemePicker } from "@/features/settings/components/color-scheme-picker" -import AccentColorPicker from "@/features/settings/components/accent-color-picker" -import { SignOutIcon } from "@phosphor-icons/react" -import ListLink from "@/components/list-link" +import { createFileRoute } from "@tanstack/react-router"; +import { Box, Title, Stack } from "@mantine/core"; +import { ColorSchemePicker } from "@/features/settings/components/color-scheme-picker"; +import AccentColorPicker from "@/features/settings/components/accent-color-picker"; +import { SignOutIcon } from "@phosphor-icons/react"; +import ListLink from "@/components/list-link"; export const Route = createFileRoute("/_authed/settings")({ loader: () => ({ header: { - title: 'Settings', + title: "Settings", withBackButton: true, }, - withPadding: false + withPadding: false, }), component: RouteComponent, -}) +}); function RouteComponent() { - return <> - - Appearance - - - - - - - + return ( + <> + + Appearance + + + + + + + + ); } diff --git a/src/app/routes/_authed/teams.$teamId.tsx b/src/app/routes/_authed/teams.$teamId.tsx index 7095cd9..ff5cd4b 100644 --- a/src/app/routes/_authed/teams.$teamId.tsx +++ b/src/app/routes/_authed/teams.$teamId.tsx @@ -1,6 +1,7 @@ -import Page from "@/components/page"; import TeamProfile from "@/features/teams/components/team-profile"; import { teamQueries } from "@/features/teams/queries"; +import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure"; +import { prefetchServerQuery } from "@/lib/tanstack-query/utils/prefetch"; import { redirect, createFileRoute } from "@tanstack/react-router"; import { z } from "zod"; @@ -12,19 +13,17 @@ export const Route = createFileRoute("/_authed/teams/$teamId")({ validateSearch: searchSchema, beforeLoad: async ({ params, context }) => { const { queryClient } = context; - const team = await queryClient.ensureQueryData(teamQueries.details(params.teamId)) - if (!team) throw redirect({ to: '/' }); - return { team } + await prefetchServerQuery(queryClient, teamQueries.details(params.teamId)); }, loader: ({ params }) => ({ header: { collapsed: true, - withBackButton: true + withBackButton: true, }, - refresh: [teamQueries.details(params.teamId).queryKey] + refresh: [teamQueries.details(params.teamId).queryKey], }), component: () => { - const { team } = Route.useRouteContext(); - return + const { teamId } = Route.useParams(); + return ; }, -}) +}); diff --git a/src/app/routes/_authed/tournaments/$tournamentId.tsx b/src/app/routes/_authed/tournaments/$tournamentId.tsx index 16e30bc..c67eeff 100644 --- a/src/app/routes/_authed/tournaments/$tournamentId.tsx +++ b/src/app/routes/_authed/tournaments/$tournamentId.tsx @@ -1,16 +1,8 @@ import { createFileRoute } from '@tanstack/react-router' import { tournamentQueries } from '@/features/tournaments/queries'; -import Page from '@/components/page' -import { useQuery } from '@tanstack/react-query'; -import { Box, Group, Title } from '@mantine/core'; -import { useSheet } from '@/hooks/use-sheet'; -import Sheet from '@/components/sheet/sheet'; -import { Tournament } from '@/features/tournaments/types'; -import TeamList from '@/features/teams/components/team-list'; -import Button from '@/components/button'; -import Avatar from '@/components/avatar'; import Profile from '@/features/tournaments/components/profile'; import { z } from "zod"; +import { prefetchServerQuery } from '@/lib/tanstack-query/utils/prefetch'; const searchSchema = z.object({ tab: z.string().optional(), @@ -20,7 +12,7 @@ export const Route = createFileRoute('/_authed/tournaments/$tournamentId')({ validateSearch: searchSchema, beforeLoad: async ({ context, params }) => { const { queryClient } = context; - await queryClient.ensureQueryData(tournamentQueries.details(params.tournamentId)) + await prefetchServerQuery(queryClient, tournamentQueries.details(params.tournamentId)) }, loader: ({ params, context }) => ({ header: { @@ -35,6 +27,6 @@ export const Route = createFileRoute('/_authed/tournaments/$tournamentId')({ }) function RouteComponent() { - const { data: tournament } = useQuery(tournamentQueries.details(Route.useParams().tournamentId)); - return + const tournamentId = Route.useParams().tournamentId; + return } diff --git a/src/app/routes/_authed/tournaments/index.tsx b/src/app/routes/_authed/tournaments/index.tsx index 8b9c58e..902ab64 100644 --- a/src/app/routes/_authed/tournaments/index.tsx +++ b/src/app/routes/_authed/tournaments/index.tsx @@ -2,19 +2,19 @@ import Page from '@/components/page' import { Stack } from '@mantine/core' import { createFileRoute } from '@tanstack/react-router' import { TournamentCard } from '@/features/tournaments/components/tournament-card' -import { tournamentQueries } from '@/features/tournaments/queries' -import { useQuery } from '@tanstack/react-query' +import { tournamentQueries, useTournaments } from '@/features/tournaments/queries' import { useAuth } from '@/contexts/auth-context' import { useSheet } from '@/hooks/use-sheet' import Sheet from '@/components/sheet/sheet' import TournamentForm from '@/features/tournaments/components/tournament-form' import { PlusIcon } from '@phosphor-icons/react' import Button from '@/components/button' +import { prefetchServerQuery } from '@/lib/tanstack-query/utils/prefetch' export const Route = createFileRoute('/_authed/tournaments/')({ beforeLoad: async ({ context }) => { const { queryClient } = context; - await queryClient.ensureQueryData(tournamentQueries.list()) + await prefetchServerQuery(queryClient, tournamentQueries.list()) }, loader: () => ({ header: { @@ -27,7 +27,7 @@ export const Route = createFileRoute('/_authed/tournaments/')({ }) function RouteComponent() { - const { data: tournaments } = useQuery(tournamentQueries.list()); + const { data: tournaments } = useTournaments(); const { roles } = useAuth(); const sheet = useSheet(); diff --git a/src/app/routes/api/test.ts b/src/app/routes/api/test.ts deleted file mode 100644 index 8986448..0000000 --- a/src/app/routes/api/test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { createServerFileRoute } from '@tanstack/react-start/server'; -import { superTokensRequestMiddleware } from '@/utils/supertokens'; - -// Simple test route for testing the auth middleware -export const ServerRoute = createServerFileRoute('/api/test').middleware([superTokensRequestMiddleware]).methods({ - GET: () => { - return new Response('Hello from the authenticated API!') - }, -}) diff --git a/src/app/routes/login.tsx b/src/app/routes/login.tsx index 6fca9fe..bca5592 100644 --- a/src/app/routes/login.tsx +++ b/src/app/routes/login.tsx @@ -4,17 +4,16 @@ import { redirect, createFileRoute } from "@tanstack/react-router"; import z from "zod"; const loginSearchSchema = z.object({ - stage: z.enum(['code', 'name']).optional(), + stage: z.enum(["code", "name"]).optional(), number: z.string().optional(), - callback: z.string().optional() + callback: z.string().optional(), }); - export const Route = createFileRoute("/login")({ validateSearch: loginSearchSchema, beforeLoad: async ({ context }) => { if (context.auth?.user) { - throw redirect({ to: '/' }) + throw redirect({ to: "/" }); } }, component: () => { @@ -22,6 +21,6 @@ export const Route = createFileRoute("/login")({ - ) - } -}) + ); + }, +}); diff --git a/src/app/routes/logout.tsx b/src/app/routes/logout.tsx index f38c489..78feff0 100644 --- a/src/app/routes/logout.tsx +++ b/src/app/routes/logout.tsx @@ -1,14 +1,15 @@ -import { LoadingOverlay } from '@mantine/core' -import { signOut } from 'supertokens-web-js/recipe/passwordless' -import { redirect, createFileRoute } from '@tanstack/react-router' -import { authQueryConfig, defaultAuthData } from '@/contexts/auth-context' +import { LoadingOverlay } from "@mantine/core"; +import { signOut } from "supertokens-web-js/recipe/passwordless"; +import { redirect, createFileRoute } from "@tanstack/react-router"; +import { defaultAuthData } from "@/contexts/auth-context"; +import { playerKeys } from "@/features/players/queries"; -export const Route = createFileRoute('/logout')({ +export const Route = createFileRoute("/logout")({ preload: false, loader: async ({ context }) => { - await context.queryClient.setQueryData(authQueryConfig.queryKey, defaultAuthData); + await context.queryClient.setQueryData(playerKeys.auth, defaultAuthData); await signOut(); - throw redirect({ to: '/login' }); + throw redirect({ to: "/login" }); }, - pendingComponent: () => -}) + pendingComponent: () => , +}); diff --git a/src/components/DefaultCatchBoundary.tsx b/src/components/DefaultCatchBoundary.tsx index 2b2bd82..d5b80b7 100644 --- a/src/components/DefaultCatchBoundary.tsx +++ b/src/components/DefaultCatchBoundary.tsx @@ -122,7 +122,7 @@ export function DefaultCatchBoundary({ error }: ErrorComponentProps) { size="compact-sm" onClick={toggleDetails} > - {detailsOpened ? 'Hide' : 'Show'} technical details + {detailsOpened ? 'Hide' : 'Show'} stack trace diff --git a/src/components/avatar.tsx b/src/components/avatar.tsx index 424ea5d..34186b8 100644 --- a/src/components/avatar.tsx +++ b/src/components/avatar.tsx @@ -1,18 +1,43 @@ -import { Avatar as MantineAvatar, AvatarProps as MantineAvatarProps, Paper } from '@mantine/core'; +import { + Avatar as MantineAvatar, + AvatarProps as MantineAvatarProps, + Paper, +} from "@mantine/core"; -interface AvatarProps extends Omit { +interface AvatarProps + extends Omit { name: string; size?: number; radius?: string | number; withBorder?: boolean; } -const Avatar = ({ name, size = 35, radius = '100%', withBorder = true, ...props }: AvatarProps) => { - return - - -} +const Avatar = ({ + name, + size = 35, + radius = "100%", + withBorder = true, + ...props +}: AvatarProps) => { + return ( + + + + ); +}; export default Avatar; diff --git a/src/components/button.tsx b/src/components/button.tsx index 66f8e63..68b6dab 100644 --- a/src/components/button.tsx +++ b/src/components/button.tsx @@ -1,11 +1,14 @@ -import { Button as MantineButton, ButtonProps as MantineButtonProps } from '@mantine/core'; -import { forwardRef, ComponentPropsWithoutRef } from 'react'; +import { + Button as MantineButton, + ButtonProps as MantineButtonProps, +} from "@mantine/core"; +import { forwardRef, ComponentPropsWithoutRef } from "react"; -type ButtonProps = MantineButtonProps & ComponentPropsWithoutRef<'button'>; +type ButtonProps = MantineButtonProps & ComponentPropsWithoutRef<"button">; const Button = forwardRef((props, ref) => { return ; }); -Button.displayName = 'Button'; +Button.displayName = "Button"; export default Button; diff --git a/src/components/date-time-picker.tsx b/src/components/date-time-picker.tsx index d947391..69abddd 100644 --- a/src/components/date-time-picker.tsx +++ b/src/components/date-time-picker.tsx @@ -10,13 +10,18 @@ interface DateTimePickerProps { [key: string]: any; } -const DateTimePicker = ({ value, onChange, label, ...rest }: DateTimePickerProps) => { +const DateTimePicker = ({ + value, + onChange, + label, + ...rest +}: DateTimePickerProps) => { const timeRef = useRef(null); const currentDate = value ? new Date(value) : null; const formatDate = (date: Date | null): string => { if (!date) return ""; - return date.toISOString().split('T')[0]; + return date.toISOString().split("T")[0]; }; const formatTime = (date: Date | null): string => { @@ -26,35 +31,35 @@ const DateTimePicker = ({ value, onChange, label, ...rest }: DateTimePickerProps const handleDateChange = (dateString: string | null) => { if (!dateString) { - onChange(''); + onChange(""); return; } - const newDate = new Date(dateString + 'T00:00:00'); - + const newDate = new Date(dateString + "T00:00:00"); + if (currentDate) { newDate.setHours(currentDate.getHours()); newDate.setMinutes(currentDate.getMinutes()); } - + onChange(newDate.toISOString()); }; const handleTimeChange = (event: React.ChangeEvent) => { const timeValue = event.target.value; if (!timeValue) return; - - const [hours, minutes] = timeValue.split(':').map(Number); + + const [hours, minutes] = timeValue.split(":").map(Number); if (isNaN(hours) || isNaN(minutes)) return; - + const baseDate = currentDate || new Date(); const newDate = new Date(baseDate); - + newDate.setHours(hours); newDate.setMinutes(minutes); newDate.setSeconds(0); newDate.setMilliseconds(0); - + onChange(newDate.toISOString()); }; @@ -73,9 +78,9 @@ const DateTimePicker = ({ value, onChange, label, ...rest }: DateTimePickerProps value={formatTime(currentDate)} onChange={handleTimeChange} rightSection={ - timeRef.current?.showPicker()} > @@ -87,4 +92,4 @@ const DateTimePicker = ({ value, onChange, label, ...rest }: DateTimePickerProps ); }; -export { DateTimePicker }; \ No newline at end of file +export { DateTimePicker }; diff --git a/src/components/list-button.tsx b/src/components/list-button.tsx index aaca803..02f5ac2 100644 --- a/src/components/list-button.tsx +++ b/src/components/list-button.tsx @@ -10,21 +10,18 @@ interface ListButtonProps { const ListButton = ({ label, onClick, Icon }: ListButtonProps) => { return ( <> - + - - {label} - + + + {label} + + - ) -} + ); +}; export default ListButton; diff --git a/src/components/list-link.tsx b/src/components/list-link.tsx index 003d96f..815cf34 100644 --- a/src/components/list-link.tsx +++ b/src/components/list-link.tsx @@ -14,17 +14,21 @@ const ListLink = ({ label, to, Icon }: ListLinkProps) => { return ( <> navigate({ to })} - label={{label}} - leftSection={Icon && } + label={ + + {label} + + } + leftSection={Icon && } rightSection={} /> - ) -} + ); +}; export default ListLink; diff --git a/src/components/page.tsx b/src/components/page.tsx index 6d4382f..3b77c86 100644 --- a/src/components/page.tsx +++ b/src/components/page.tsx @@ -7,9 +7,19 @@ interface PageProps extends ContainerProps, React.PropsWithChildren { const Page = ({ children, noPadding, ...props }: PageProps) => { const { header } = useRouterConfig(); - return + return ( + {children} - -} + + ); +}; export default Page; diff --git a/src/components/phone-number-input.tsx b/src/components/phone-number-input.tsx index 3989bdb..60df2b6 100644 --- a/src/components/phone-number-input.tsx +++ b/src/components/phone-number-input.tsx @@ -1,6 +1,6 @@ -import { Input, InputProps, Group, Text } from '@mantine/core'; -import { CheckFat, Phone } from '@phosphor-icons/react'; -import { IMaskInput } from 'react-imask'; +import { Input, InputProps, Group, Text } from "@mantine/core"; +import { CheckFat, Phone } from "@phosphor-icons/react"; +import { IMaskInput } from "react-imask"; interface PhoneNumberInputProps extends InputProps { id: string; @@ -11,24 +11,48 @@ interface PhoneNumberInputProps extends InputProps { error?: string; } -const PhoneNumberInput: React.FC = ({ id, value, onChange, label, description, error, ...props }) => { +const PhoneNumberInput: React.FC = ({ + id, + value, + onChange, + label, + description, + error, + ...props +}) => { return ( - +   +1} + leftSection={ + +  {" "} + + +1 + + + } leftSectionWidth={50} leftSectionProps={{ style: { padding: 0 } }} placeholder="(713) 867-5309" onAccept={(_, mask) => onChange(mask.unmaskedValue)} - rightSection={value?.length === 10 && } + rightSection={ + value?.length === 10 && ( + + ) + } value={value} {...props} /> ); -} +}; export default PhoneNumberInput; diff --git a/src/components/sheet/drawer.tsx b/src/components/sheet/drawer.tsx index c21d90d..123c891 100644 --- a/src/components/sheet/drawer.tsx +++ b/src/components/sheet/drawer.tsx @@ -1,8 +1,8 @@ import { Box, Container, useComputedColorScheme } from "@mantine/core"; import { PropsWithChildren, useEffect } from "react"; -import { Drawer as VaulDrawer } from 'vaul'; -import { useMantineColorScheme } from '@mantine/core'; -import styles from './styles.module.css'; +import { Drawer as VaulDrawer } from "vaul"; +import { useMantineColorScheme } from "@mantine/core"; +import styles from "./styles.module.css"; interface DrawerProps extends PropsWithChildren { title?: string; @@ -10,44 +10,51 @@ interface DrawerProps extends PropsWithChildren { onChange: (next: boolean) => void; } -const Drawer: React.FC = ({ title, children, opened, onChange }) => { - const colorScheme = useComputedColorScheme('light'); +const Drawer: React.FC = ({ + title, + children, + opened, + onChange, +}) => { + const colorScheme = useComputedColorScheme("light"); useEffect(() => { - const appElement = document.querySelector('.app') as HTMLElement; - + const appElement = document.querySelector(".app") as HTMLElement; + if (!appElement) return; - let themeColorMeta = document.querySelector('meta[name="theme-color"]') as HTMLMetaElement; + let themeColorMeta = document.querySelector( + 'meta[name="theme-color"]' + ) as HTMLMetaElement; if (!themeColorMeta) { - themeColorMeta = document.createElement('meta'); - themeColorMeta.name = 'theme-color'; + themeColorMeta = document.createElement("meta"); + themeColorMeta.name = "theme-color"; document.head.appendChild(themeColorMeta); } const colors = { light: { - normal: 'rgb(255,255,255)', - overlay: 'rgb(153,153,153)' + normal: "rgb(255,255,255)", + overlay: "rgb(153,153,153)", }, dark: { - normal: 'rgb(36,36,36)', - overlay: 'rgb(22,22,22)' - } + normal: "rgb(36,36,36)", + overlay: "rgb(22,22,22)", + }, }; const currentColors = colors[colorScheme] || colors.light; - + if (opened) { - appElement.classList.add('drawer-scaling'); + appElement.classList.add("drawer-scaling"); themeColorMeta.content = currentColors.overlay; } else { - appElement.classList.remove('drawer-scaling'); + appElement.classList.remove("drawer-scaling"); themeColorMeta.content = currentColors.normal; } return () => { - appElement.classList.remove('drawer-scaling'); + appElement.classList.remove("drawer-scaling"); themeColorMeta.content = currentColors.normal; }; }, [opened, colorScheme]); @@ -57,9 +64,17 @@ const Drawer: React.FC = ({ title, children, opened, onChange }) => - - - + + + {title} {children} @@ -67,7 +82,7 @@ const Drawer: React.FC = ({ title, children, opened, onChange }) => - ) -} + ); +}; export default Drawer; diff --git a/src/components/sheet/modal.tsx b/src/components/sheet/modal.tsx index 07873f2..7633973 100644 --- a/src/components/sheet/modal.tsx +++ b/src/components/sheet/modal.tsx @@ -8,9 +8,13 @@ interface ModalProps extends PropsWithChildren { } const Modal: React.FC = ({ title, children, opened, onClose }) => ( - {title}}> + {title}} + > {children} -) +); export default Modal; diff --git a/src/components/sheet/sheet.tsx b/src/components/sheet/sheet.tsx index dca26c9..5f18216 100644 --- a/src/components/sheet/sheet.tsx +++ b/src/components/sheet/sheet.tsx @@ -17,14 +17,22 @@ const Sheet: React.FC = ({ title, children, opened, onChange }) => { const SheetComponent = isMobile ? Drawer : Modal; return ( - - - - {children} - + + + {children} ); }; -export default Sheet; \ No newline at end of file +export default Sheet; diff --git a/src/components/sheet/slide-panel/slide-panel-field.tsx b/src/components/sheet/slide-panel/slide-panel-field.tsx index f111f41..0677a81 100644 --- a/src/components/sheet/slide-panel/slide-panel-field.tsx +++ b/src/components/sheet/slide-panel/slide-panel-field.tsx @@ -32,12 +32,12 @@ const SlidePanelField = ({ const context = useContext(SlidePanelContext); if (!context) { - throw new Error('SlidePanelField must be used within a SlidePanel'); + throw new Error("SlidePanelField must be used within a SlidePanel"); } const handleClick = () => { if (!onChange) return; - + context.openPanel({ title, Component, @@ -64,26 +64,51 @@ const SlidePanelField = ({ - {label}{withAsterisk && *} - {displayValue()} + + {label} + {withAsterisk && ( + + * + + )} + + + {displayValue()} + - + - {error && {error}} + {error && ( + + {error} + + )} ); }; -export { SlidePanelField }; \ No newline at end of file +export { SlidePanelField }; diff --git a/src/components/sheet/slide-panel/slide-panel.tsx b/src/components/sheet/slide-panel/slide-panel.tsx index 69c64f1..2507278 100644 --- a/src/components/sheet/slide-panel/slide-panel.tsx +++ b/src/components/sheet/slide-panel/slide-panel.tsx @@ -1,6 +1,13 @@ -import { Box, Text, Group, ActionIcon, ScrollArea, Divider } from "@mantine/core"; +import { + Box, + Text, + Group, + ActionIcon, + ScrollArea, + Divider, +} from "@mantine/core"; import { ArrowLeftIcon, CheckIcon } from "@phosphor-icons/react"; -import { useState, ReactNode} from "react"; +import { useState, ReactNode } from "react"; import { SlidePanelContext, type PanelConfig } from "./slide-panel-context"; import Button from "@/components/button"; @@ -15,20 +22,15 @@ interface SlidePanelProps { loading?: boolean; } -/** - * SlidePanel is a form component meant to be used inside a drawer/modal - * It is used to create a form with multiple views/panels that slides in from the side - * Use with SlidePanelField for an extra panel - */ -const SlidePanel = ({ - children, +const SlidePanel = ({ + children, onSubmit, onCancel, submitText = "Submit", cancelText = "Cancel", maxHeight = "70vh", formProps = {}, - loading = false + loading = false, }: SlidePanelProps) => { const [isOpen, setIsOpen] = useState(false); const [panelConfig, setPanelConfig] = useState(null); @@ -58,54 +60,62 @@ const SlidePanel = ({ return ( -
- - - {children} - + + {children} - {onCancel && ( -