diff --git a/src/lib/pocketbase.ts b/src/lib/pocketbase.ts index 12bb3f4..5c20157 100644 --- a/src/lib/pocketbase.ts +++ b/src/lib/pocketbase.ts @@ -7,3 +7,7 @@ import PocketBase from "pocketbase"; import { POCKETBASE_URL } from "@/config"; export const pb = new PocketBase(POCKETBASE_URL); + +export function isAuthenticated(): boolean { + return pb.authStore.isValid; +} diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 6eb4577..61aad3a 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -13,10 +13,11 @@ import { Route as rootRoute } from './routes/__root' import { Route as LoginImport } from './routes/login' import { Route as AboutImport } from './routes/about' +import { Route as AuthenticatedImport } from './routes/_authenticated' import { Route as IndexImport } from './routes/index' -import { Route as SessionsIndexImport } from './routes/sessions.index' -import { Route as CampaignsIndexImport } from './routes/campaigns.index' -import { Route as CampaignsCampaignIdImport } from './routes/campaigns.$campaignId' +import { Route as AuthenticatedSessionsIndexImport } from './routes/_authenticated/sessions.index' +import { Route as AuthenticatedCampaignsIndexImport } from './routes/_authenticated/campaigns.index' +import { Route as AuthenticatedCampaignsCampaignIdImport } from './routes/_authenticated/campaigns.$campaignId' // Create/Update Routes @@ -32,29 +33,38 @@ const AboutRoute = AboutImport.update({ getParentRoute: () => rootRoute, } as any) +const AuthenticatedRoute = AuthenticatedImport.update({ + id: '/_authenticated', + getParentRoute: () => rootRoute, +} as any) + const IndexRoute = IndexImport.update({ id: '/', path: '/', getParentRoute: () => rootRoute, } as any) -const SessionsIndexRoute = SessionsIndexImport.update({ - id: '/sessions/', - path: '/sessions/', - getParentRoute: () => rootRoute, -} as any) +const AuthenticatedSessionsIndexRoute = AuthenticatedSessionsIndexImport.update( + { + id: '/sessions/', + path: '/sessions/', + getParentRoute: () => AuthenticatedRoute, + } as any, +) -const CampaignsIndexRoute = CampaignsIndexImport.update({ - id: '/campaigns/', - path: '/campaigns/', - getParentRoute: () => rootRoute, -} as any) +const AuthenticatedCampaignsIndexRoute = + AuthenticatedCampaignsIndexImport.update({ + id: '/campaigns/', + path: '/campaigns/', + getParentRoute: () => AuthenticatedRoute, + } as any) -const CampaignsCampaignIdRoute = CampaignsCampaignIdImport.update({ - id: '/campaigns/$campaignId', - path: '/campaigns/$campaignId', - getParentRoute: () => rootRoute, -} as any) +const AuthenticatedCampaignsCampaignIdRoute = + AuthenticatedCampaignsCampaignIdImport.update({ + id: '/campaigns/$campaignId', + path: '/campaigns/$campaignId', + getParentRoute: () => AuthenticatedRoute, + } as any) // Populate the FileRoutesByPath interface @@ -67,6 +77,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexImport parentRoute: typeof rootRoute } + '/_authenticated': { + id: '/_authenticated' + path: '' + fullPath: '' + preLoaderRoute: typeof AuthenticatedImport + parentRoute: typeof rootRoute + } '/about': { id: '/about' path: '/about' @@ -81,64 +98,84 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LoginImport parentRoute: typeof rootRoute } - '/campaigns/$campaignId': { - id: '/campaigns/$campaignId' + '/_authenticated/campaigns/$campaignId': { + id: '/_authenticated/campaigns/$campaignId' path: '/campaigns/$campaignId' fullPath: '/campaigns/$campaignId' - preLoaderRoute: typeof CampaignsCampaignIdImport - parentRoute: typeof rootRoute + preLoaderRoute: typeof AuthenticatedCampaignsCampaignIdImport + parentRoute: typeof AuthenticatedImport } - '/campaigns/': { - id: '/campaigns/' + '/_authenticated/campaigns/': { + id: '/_authenticated/campaigns/' path: '/campaigns' fullPath: '/campaigns' - preLoaderRoute: typeof CampaignsIndexImport - parentRoute: typeof rootRoute + preLoaderRoute: typeof AuthenticatedCampaignsIndexImport + parentRoute: typeof AuthenticatedImport } - '/sessions/': { - id: '/sessions/' + '/_authenticated/sessions/': { + id: '/_authenticated/sessions/' path: '/sessions' fullPath: '/sessions' - preLoaderRoute: typeof SessionsIndexImport - parentRoute: typeof rootRoute + preLoaderRoute: typeof AuthenticatedSessionsIndexImport + parentRoute: typeof AuthenticatedImport } } } // Create and export the route tree +interface AuthenticatedRouteChildren { + AuthenticatedCampaignsCampaignIdRoute: typeof AuthenticatedCampaignsCampaignIdRoute + AuthenticatedCampaignsIndexRoute: typeof AuthenticatedCampaignsIndexRoute + AuthenticatedSessionsIndexRoute: typeof AuthenticatedSessionsIndexRoute +} + +const AuthenticatedRouteChildren: AuthenticatedRouteChildren = { + AuthenticatedCampaignsCampaignIdRoute: AuthenticatedCampaignsCampaignIdRoute, + AuthenticatedCampaignsIndexRoute: AuthenticatedCampaignsIndexRoute, + AuthenticatedSessionsIndexRoute: AuthenticatedSessionsIndexRoute, +} + +const AuthenticatedRouteWithChildren = AuthenticatedRoute._addFileChildren( + AuthenticatedRouteChildren, +) + export interface FileRoutesByFullPath { '/': typeof IndexRoute + '': typeof AuthenticatedRouteWithChildren '/about': typeof AboutRoute '/login': typeof LoginRoute - '/campaigns/$campaignId': typeof CampaignsCampaignIdRoute - '/campaigns': typeof CampaignsIndexRoute - '/sessions': typeof SessionsIndexRoute + '/campaigns/$campaignId': typeof AuthenticatedCampaignsCampaignIdRoute + '/campaigns': typeof AuthenticatedCampaignsIndexRoute + '/sessions': typeof AuthenticatedSessionsIndexRoute } export interface FileRoutesByTo { '/': typeof IndexRoute + '': typeof AuthenticatedRouteWithChildren '/about': typeof AboutRoute '/login': typeof LoginRoute - '/campaigns/$campaignId': typeof CampaignsCampaignIdRoute - '/campaigns': typeof CampaignsIndexRoute - '/sessions': typeof SessionsIndexRoute + '/campaigns/$campaignId': typeof AuthenticatedCampaignsCampaignIdRoute + '/campaigns': typeof AuthenticatedCampaignsIndexRoute + '/sessions': typeof AuthenticatedSessionsIndexRoute } export interface FileRoutesById { __root__: typeof rootRoute '/': typeof IndexRoute + '/_authenticated': typeof AuthenticatedRouteWithChildren '/about': typeof AboutRoute '/login': typeof LoginRoute - '/campaigns/$campaignId': typeof CampaignsCampaignIdRoute - '/campaigns/': typeof CampaignsIndexRoute - '/sessions/': typeof SessionsIndexRoute + '/_authenticated/campaigns/$campaignId': typeof AuthenticatedCampaignsCampaignIdRoute + '/_authenticated/campaigns/': typeof AuthenticatedCampaignsIndexRoute + '/_authenticated/sessions/': typeof AuthenticatedSessionsIndexRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: | '/' + | '' | '/about' | '/login' | '/campaigns/$campaignId' @@ -147,6 +184,7 @@ export interface FileRouteTypes { fileRoutesByTo: FileRoutesByTo to: | '/' + | '' | '/about' | '/login' | '/campaigns/$campaignId' @@ -155,30 +193,27 @@ export interface FileRouteTypes { id: | '__root__' | '/' + | '/_authenticated' | '/about' | '/login' - | '/campaigns/$campaignId' - | '/campaigns/' - | '/sessions/' + | '/_authenticated/campaigns/$campaignId' + | '/_authenticated/campaigns/' + | '/_authenticated/sessions/' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute + AuthenticatedRoute: typeof AuthenticatedRouteWithChildren AboutRoute: typeof AboutRoute LoginRoute: typeof LoginRoute - CampaignsCampaignIdRoute: typeof CampaignsCampaignIdRoute - CampaignsIndexRoute: typeof CampaignsIndexRoute - SessionsIndexRoute: typeof SessionsIndexRoute } const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, + AuthenticatedRoute: AuthenticatedRouteWithChildren, AboutRoute: AboutRoute, LoginRoute: LoginRoute, - CampaignsCampaignIdRoute: CampaignsCampaignIdRoute, - CampaignsIndexRoute: CampaignsIndexRoute, - SessionsIndexRoute: SessionsIndexRoute, } export const routeTree = rootRoute @@ -192,30 +227,39 @@ export const routeTree = rootRoute "filePath": "__root.tsx", "children": [ "/", + "/_authenticated", "/about", - "/login", - "/campaigns/$campaignId", - "/campaigns/", - "/sessions/" + "/login" ] }, "/": { "filePath": "index.tsx" }, + "/_authenticated": { + "filePath": "_authenticated.tsx", + "children": [ + "/_authenticated/campaigns/$campaignId", + "/_authenticated/campaigns/", + "/_authenticated/sessions/" + ] + }, "/about": { "filePath": "about.tsx" }, "/login": { "filePath": "login.tsx" }, - "/campaigns/$campaignId": { - "filePath": "campaigns.$campaignId.tsx" + "/_authenticated/campaigns/$campaignId": { + "filePath": "_authenticated/campaigns.$campaignId.tsx", + "parent": "/_authenticated" }, - "/campaigns/": { - "filePath": "campaigns.index.tsx" + "/_authenticated/campaigns/": { + "filePath": "_authenticated/campaigns.index.tsx", + "parent": "/_authenticated" }, - "/sessions/": { - "filePath": "sessions.index.tsx" + "/_authenticated/sessions/": { + "filePath": "_authenticated/sessions.index.tsx", + "parent": "/_authenticated" } } } diff --git a/src/routes/_authenticated.tsx b/src/routes/_authenticated.tsx new file mode 100644 index 0000000..f123cd7 --- /dev/null +++ b/src/routes/_authenticated.tsx @@ -0,0 +1,12 @@ +import { isAuthenticated } from "@/lib/pocketbase"; +import { createFileRoute, redirect } from "@tanstack/react-router"; + +export const Route = createFileRoute("/_authenticated")({ + beforeLoad: () => { + if (!isAuthenticated()) { + throw redirect({ + to: "/login", + }); + } + }, +}); diff --git a/src/routes/campaigns.$campaignId.tsx b/src/routes/_authenticated/campaigns.$campaignId.tsx similarity index 73% rename from src/routes/campaigns.$campaignId.tsx rename to src/routes/_authenticated/campaigns.$campaignId.tsx index 2b68141..87eadf2 100644 --- a/src/routes/campaigns.$campaignId.tsx +++ b/src/routes/_authenticated/campaigns.$campaignId.tsx @@ -1,6 +1,6 @@ import { createFileRoute } from "@tanstack/react-router"; -export const Route = createFileRoute("/campaigns/$campaignId")({ +export const Route = createFileRoute("/_authenticated/campaigns/$campaignId")({ component: RouteComponent, }); diff --git a/src/routes/campaigns.index.tsx b/src/routes/_authenticated/campaigns.index.tsx similarity index 70% rename from src/routes/campaigns.index.tsx rename to src/routes/_authenticated/campaigns.index.tsx index 6aa218f..3c216a6 100644 --- a/src/routes/campaigns.index.tsx +++ b/src/routes/_authenticated/campaigns.index.tsx @@ -1,6 +1,6 @@ import { createFileRoute } from "@tanstack/react-router"; -export const Route = createFileRoute("/campaigns/")({ +export const Route = createFileRoute("/_authenticated/campaigns/")({ component: RouteComponent, }); diff --git a/src/routes/sessions.index.tsx b/src/routes/_authenticated/sessions.index.tsx similarity index 70% rename from src/routes/sessions.index.tsx rename to src/routes/_authenticated/sessions.index.tsx index d473d16..ad22a3d 100644 --- a/src/routes/sessions.index.tsx +++ b/src/routes/_authenticated/sessions.index.tsx @@ -1,6 +1,6 @@ import { createFileRoute } from '@tanstack/react-router' -export const Route = createFileRoute('/sessions/')({ +export const Route = createFileRoute('/_authenticated/sessions/')({ component: RouteComponent, }) diff --git a/src/routes/index.tsx b/src/routes/index.tsx index bea26e8..a0a2dfa 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -1,4 +1,4 @@ -import { createFileRoute, Link } from "@tanstack/react-router"; +import { createFileRoute } from "@tanstack/react-router"; export const Route = createFileRoute("/")({ component: App, @@ -7,14 +7,7 @@ export const Route = createFileRoute("/")({ function App() { return (
-
-

Hello, Tanstack

-
-
- - Campaigns - -
+

Welcome to the DM's Companion

); } diff --git a/src/routes/login.tsx b/src/routes/login.tsx index a432257..fb2547b 100644 --- a/src/routes/login.tsx +++ b/src/routes/login.tsx @@ -1,19 +1,26 @@ -import { useState, useEffect } from "react"; import { useAuth } from "@/context/auth/AuthContext"; -import { createFileRoute, useNavigate } from "@tanstack/react-router"; +import { isAuthenticated } from "@/lib/pocketbase"; +import { createFileRoute, redirect } from "@tanstack/react-router"; import { ClientResponseError } from "pocketbase"; +import { useState } from "react"; /** * Login and signup page for authentication. * Allows users to log in or create a new account. */ export const Route = createFileRoute("/login")({ + beforeLoad: () => { + if (isAuthenticated()) { + throw redirect({ + to: "/", + }); + } + }, component: LoginPage, }); function LoginPage() { - const { login, signup, user, isLoading } = useAuth(); - const navigate = useNavigate(); + const { login, signup, isLoading } = useAuth(); const [mode, setMode] = useState<"login" | "signup">("login"); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); @@ -26,12 +33,6 @@ function LoginPage() { }>({}); const [submitting, setSubmitting] = useState(false); - useEffect(() => { - if (user) { - navigate({ to: "/" }); - } - }, [user, navigate]); - const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(null); @@ -55,9 +56,8 @@ function LoginPage() { password: err.response.data.password?.message, passwordConfirm: err.response.data.passwordConfirm?.message, }); - } else { - setError((err as Error)?.message || "Authentication failed"); } + setError((err as Error)?.message || "Authentication failed"); } finally { setSubmitting(false); }