Compare commits
2 Commits
9c0d9326e0
...
99236a36f8
| Author | SHA1 | Date | |
|---|---|---|---|
| 99236a36f8 | |||
| 9cfdfbaf23 |
@@ -11,10 +11,7 @@ RUN apk add --no-cache \
|
|||||||
ADD https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/pocketbase_${PB_VERSION}_linux_amd64.zip /tmp/pb.zip
|
ADD https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/pocketbase_${PB_VERSION}_linux_amd64.zip /tmp/pb.zip
|
||||||
RUN unzip /tmp/pb.zip -d /pb/
|
RUN unzip /tmp/pb.zip -d /pb/
|
||||||
|
|
||||||
# uncomment to copy the local pb_migrations dir into the image
|
|
||||||
COPY ./pb_migrations /pb/pb_migrations
|
COPY ./pb_migrations /pb/pb_migrations
|
||||||
|
|
||||||
# uncomment to copy the local pb_hooks dir into the image
|
|
||||||
COPY ./pb_hooks /pb/pb_hooks
|
COPY ./pb_hooks /pb/pb_hooks
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "mprocs \"npm run start\" \"pocketbase serve\"",
|
"dev": "mprocs \"npm run start\" \"pocketbase serve\"",
|
||||||
"start": "vite --port 3000",
|
"start": "VITE_POCKETBASE_URL=http://localhost:8090 vite --port 3000",
|
||||||
"build": "vite build && tsc",
|
"build": "vite build && tsc",
|
||||||
"serve": "vite preview",
|
"serve": "vite preview",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"docker:build:app": "docker build -t docker.havenisms.com/lazy-dm/app -f docker/app.dockerfile .",
|
"docker:build:app": "docker build -t docker.havenisms.com/lazy-dm/app -f docker/app.dockerfile --build-arg VITE_POCKETBASE_URL=/api .",
|
||||||
"docker:build:pocketbase": "docker build -t docker.havenisms.com/lazy-dm/pocketbase -f docker/pocketsbase.dockerfile .",
|
"docker:build:pocketbase": "docker build -t docker.havenisms.com/lazy-dm/pocketbase -f docker/pocketbase.dockerfile .",
|
||||||
"docker:build": "npm run docker:build:app && npm run docker:build:pocketbase"
|
"docker:build": "npm run docker:build:app && npm run docker:build:pocketbase"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
64
src/components/documents/DocumentPrintRow.tsx
Normal file
64
src/components/documents/DocumentPrintRow.tsx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
// DocumentRow.tsx
|
||||||
|
// Generic row component for displaying any document type.
|
||||||
|
import {
|
||||||
|
isLocation,
|
||||||
|
isMonster,
|
||||||
|
isNpc,
|
||||||
|
isScene,
|
||||||
|
isSecret,
|
||||||
|
isSession,
|
||||||
|
isTreasure,
|
||||||
|
type Document,
|
||||||
|
} from "@/lib/types";
|
||||||
|
import { LocationPrintRow } from "./location/LocationPrintRow";
|
||||||
|
import { MonsterPrintRow } from "./monsters/MonsterPrintRow";
|
||||||
|
import { TreasurePrintRow } from "./treasure/TreasurePrintRow";
|
||||||
|
import { SecretPrintRow } from "./secret/SecretPrintRow";
|
||||||
|
import { NpcPrintRow } from "./npc/NpcPrintRow";
|
||||||
|
import { ScenePrintRow } from "./scene/ScenePrintRow";
|
||||||
|
import { SessionPrintRow } from "./session/SessionPrintRow";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a row for any document type. Prioritizes Session, then Secret, then falls back to ID and creation time.
|
||||||
|
* If rendering a SecretRow, uses the provided session prop if available.
|
||||||
|
*/
|
||||||
|
export const DocumentPrintRow = ({ document }: { document: Document }) => {
|
||||||
|
if (isLocation(document)) {
|
||||||
|
return <LocationPrintRow location={document} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMonster(document)) {
|
||||||
|
return <MonsterPrintRow monster={document} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNpc(document)) {
|
||||||
|
return <NpcPrintRow npc={document} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSession(document)) {
|
||||||
|
return <SessionPrintRow session={document} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSecret(document)) {
|
||||||
|
return <SecretPrintRow secret={document} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isScene(document)) {
|
||||||
|
return <ScenePrintRow scene={document} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTreasure(document)) {
|
||||||
|
return <TreasurePrintRow treasure={document} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: show ID and creation time
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold text-lg text-slate-300">
|
||||||
|
Unrecognized Document
|
||||||
|
</div>
|
||||||
|
<div className="text-slate-400 text-sm">ID: {document.id}</div>
|
||||||
|
<div className="text-slate-400 text-sm">Created: {document.created}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
13
src/components/documents/location/LocationPrintRow.tsx
Normal file
13
src/components/documents/location/LocationPrintRow.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import type { Location } from "@/lib/types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders an print-friendly location row
|
||||||
|
*/
|
||||||
|
export const LocationPrintRow = ({ location }: { location: Location }) => {
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
<h4>{location.data.location.name}</h4>
|
||||||
|
<p>{location.data.location.description}</p>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
8
src/components/documents/monsters/MonsterPrintRow.tsx
Normal file
8
src/components/documents/monsters/MonsterPrintRow.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import type { Monster } from "@/lib/types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders an editable monster row
|
||||||
|
*/
|
||||||
|
export const MonsterPrintRow = ({ monster }: { monster: Monster }) => {
|
||||||
|
return <li>{monster.data.monster.name}</li>;
|
||||||
|
};
|
||||||
13
src/components/documents/npc/NpcPrintRow.tsx
Normal file
13
src/components/documents/npc/NpcPrintRow.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import type { Npc } from "@/lib/types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders an editable npc row
|
||||||
|
*/
|
||||||
|
export const NpcPrintRow = ({ npc }: { npc: Npc }) => {
|
||||||
|
return (
|
||||||
|
<li className="">
|
||||||
|
<h4>{npc.data.npc.name}</h4>
|
||||||
|
<p>{npc.data.npc.description}</p>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -42,7 +42,10 @@ export const SceneForm = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="flex items-center gap-2 mt-4" onSubmit={handleSubmit}>
|
<form
|
||||||
|
className="flex flex-col items-left gap-2 mt-4"
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
<h3>Create new scene</h3>
|
<h3>Create new scene</h3>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
8
src/components/documents/scene/ScenePrintRow.tsx
Normal file
8
src/components/documents/scene/ScenePrintRow.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import type { Scene } from "@/lib/types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders an editable scene row
|
||||||
|
*/
|
||||||
|
export const ScenePrintRow = ({ scene }: { scene: Scene }) => {
|
||||||
|
return <li className="">{scene.data.scene.text}</li>;
|
||||||
|
};
|
||||||
26
src/components/documents/secret/SecretPrintRow.tsx
Normal file
26
src/components/documents/secret/SecretPrintRow.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// SecretRow.tsx
|
||||||
|
// Displays a single secret with discovered checkbox and text.
|
||||||
|
import type { Secret, Session } from "@/lib/types";
|
||||||
|
import { pb } from "@/lib/pocketbase";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a secret row with a discovered checkbox and secret text.
|
||||||
|
* Handles updating the discovered state and discoveredIn relationship.
|
||||||
|
*/
|
||||||
|
export const SecretPrintRow = ({ secret }: { secret: Secret }) => {
|
||||||
|
return (
|
||||||
|
<li className="flex items-center gap-3">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="flex-none accent-emerald-500 w-5 h-5"
|
||||||
|
aria-label="Discovered"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
{(secret.data as any)?.secret?.text || (
|
||||||
|
<span className="italic text-slate-400">(No secret text)</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
10
src/components/documents/session/SessionPrintRow.tsx
Normal file
10
src/components/documents/session/SessionPrintRow.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import type { Session } from "@/lib/types";
|
||||||
|
|
||||||
|
export const SessionPrintRow = ({ session }: { session: Session }) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-bold text-slate-600">StrongStart</h3>
|
||||||
|
<div className="">{session.data.session.strongStart}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
26
src/components/documents/treasure/TreasurePrintRow.tsx
Normal file
26
src/components/documents/treasure/TreasurePrintRow.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// TreasureRow.tsx
|
||||||
|
// Displays a single treasure with discovered checkbox and text.
|
||||||
|
import type { Treasure, Session } from "@/lib/types";
|
||||||
|
import { pb } from "@/lib/pocketbase";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a treasure row with a discovered checkbox and treasure text.
|
||||||
|
* Handles updating the discovered state and discoveredIn relationship.
|
||||||
|
*/
|
||||||
|
export const TreasurePrintRow = ({ treasure }: { treasure: Treasure }) => {
|
||||||
|
return (
|
||||||
|
<li className="flex items-center gap-3">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="flex-none accent-emerald-500 w-5 h-5"
|
||||||
|
aria-label="Discovered"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
{(treasure.data as any)?.treasure?.text || (
|
||||||
|
<span className="italic text-slate-400">(No treasure text)</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -3,4 +3,5 @@
|
|||||||
*
|
*
|
||||||
* This includes endpoints and other environment-specific settings.
|
* This includes endpoints and other environment-specific settings.
|
||||||
*/
|
*/
|
||||||
export const POCKETBASE_URL: string = import.meta.env.VITE_POCKETBASE_URL || "http://127.0.0.1:8090"; // Update as needed for deployment
|
export const POCKETBASE_URL: string =
|
||||||
|
import.meta.env.VITE_POCKETBASE_URL || "/"; // Update as needed for deployment
|
||||||
|
|||||||
@@ -11,208 +11,258 @@
|
|||||||
// Import Routes
|
// Import Routes
|
||||||
|
|
||||||
import { Route as rootRoute } from './routes/__root'
|
import { Route as rootRoute } from './routes/__root'
|
||||||
import { Route as LoginImport } from './routes/login'
|
import { Route as AppImport } from './routes/_app'
|
||||||
import { Route as AboutImport } from './routes/about'
|
import { Route as AppIndexImport } from './routes/_app/index'
|
||||||
import { Route as AuthenticatedImport } from './routes/_authenticated'
|
import { Route as AppLoginImport } from './routes/_app/login'
|
||||||
import { Route as IndexImport } from './routes/index'
|
import { Route as AppAboutImport } from './routes/_app/about'
|
||||||
import { Route as AuthenticatedCampaignsIndexImport } from './routes/_authenticated/campaigns.index'
|
import { Route as AppAuthenticatedImport } from './routes/_app/_authenticated'
|
||||||
import { Route as AuthenticatedDocumentDocumentIdImport } from './routes/_authenticated/document.$documentId'
|
import { Route as AppAuthenticatedCampaignsIndexImport } from './routes/_app/_authenticated/campaigns.index'
|
||||||
import { Route as AuthenticatedCampaignsCampaignIdImport } from './routes/_authenticated/campaigns.$campaignId'
|
import { Route as AppAuthenticatedDocumentDocumentIdImport } from './routes/_app/_authenticated/document.$documentId'
|
||||||
|
import { Route as AppAuthenticatedCampaignsCampaignIdImport } from './routes/_app/_authenticated/campaigns.$campaignId'
|
||||||
|
import { Route as AppauthenticatedDocumentDocumentIdPrintImport } from './routes/_app_._authenticated.document_.$documentId.print'
|
||||||
|
|
||||||
// Create/Update Routes
|
// Create/Update Routes
|
||||||
|
|
||||||
const LoginRoute = LoginImport.update({
|
const AppRoute = AppImport.update({
|
||||||
id: '/login',
|
id: '/_app',
|
||||||
path: '/login',
|
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
const AboutRoute = AboutImport.update({
|
const AppIndexRoute = AppIndexImport.update({
|
||||||
id: '/about',
|
|
||||||
path: '/about',
|
|
||||||
getParentRoute: () => rootRoute,
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const AuthenticatedRoute = AuthenticatedImport.update({
|
|
||||||
id: '/_authenticated',
|
|
||||||
getParentRoute: () => rootRoute,
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const IndexRoute = IndexImport.update({
|
|
||||||
id: '/',
|
id: '/',
|
||||||
path: '/',
|
path: '/',
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => AppRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
const AuthenticatedCampaignsIndexRoute =
|
const AppLoginRoute = AppLoginImport.update({
|
||||||
AuthenticatedCampaignsIndexImport.update({
|
id: '/login',
|
||||||
|
path: '/login',
|
||||||
|
getParentRoute: () => AppRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const AppAboutRoute = AppAboutImport.update({
|
||||||
|
id: '/about',
|
||||||
|
path: '/about',
|
||||||
|
getParentRoute: () => AppRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const AppAuthenticatedRoute = AppAuthenticatedImport.update({
|
||||||
|
id: '/_authenticated',
|
||||||
|
getParentRoute: () => AppRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const AppAuthenticatedCampaignsIndexRoute =
|
||||||
|
AppAuthenticatedCampaignsIndexImport.update({
|
||||||
id: '/campaigns/',
|
id: '/campaigns/',
|
||||||
path: '/campaigns/',
|
path: '/campaigns/',
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
getParentRoute: () => AppAuthenticatedRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
const AuthenticatedDocumentDocumentIdRoute =
|
const AppAuthenticatedDocumentDocumentIdRoute =
|
||||||
AuthenticatedDocumentDocumentIdImport.update({
|
AppAuthenticatedDocumentDocumentIdImport.update({
|
||||||
id: '/document/$documentId',
|
id: '/document/$documentId',
|
||||||
path: '/document/$documentId',
|
path: '/document/$documentId',
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
getParentRoute: () => AppAuthenticatedRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
const AuthenticatedCampaignsCampaignIdRoute =
|
const AppAuthenticatedCampaignsCampaignIdRoute =
|
||||||
AuthenticatedCampaignsCampaignIdImport.update({
|
AppAuthenticatedCampaignsCampaignIdImport.update({
|
||||||
id: '/campaigns/$campaignId',
|
id: '/campaigns/$campaignId',
|
||||||
path: '/campaigns/$campaignId',
|
path: '/campaigns/$campaignId',
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
getParentRoute: () => AppAuthenticatedRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const AppauthenticatedDocumentDocumentIdPrintRoute =
|
||||||
|
AppauthenticatedDocumentDocumentIdPrintImport.update({
|
||||||
|
id: '/_app_/_authenticated/document_/$documentId/print',
|
||||||
|
path: '/document/$documentId/print',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
// Populate the FileRoutesByPath interface
|
// Populate the FileRoutesByPath interface
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
declare module '@tanstack/react-router' {
|
||||||
interface FileRoutesByPath {
|
interface FileRoutesByPath {
|
||||||
'/': {
|
'/_app': {
|
||||||
id: '/'
|
id: '/_app'
|
||||||
path: '/'
|
|
||||||
fullPath: '/'
|
|
||||||
preLoaderRoute: typeof IndexImport
|
|
||||||
parentRoute: typeof rootRoute
|
|
||||||
}
|
|
||||||
'/_authenticated': {
|
|
||||||
id: '/_authenticated'
|
|
||||||
path: ''
|
path: ''
|
||||||
fullPath: ''
|
fullPath: ''
|
||||||
preLoaderRoute: typeof AuthenticatedImport
|
preLoaderRoute: typeof AppImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
'/about': {
|
'/_app/_authenticated': {
|
||||||
id: '/about'
|
id: '/_app/_authenticated'
|
||||||
|
path: ''
|
||||||
|
fullPath: ''
|
||||||
|
preLoaderRoute: typeof AppAuthenticatedImport
|
||||||
|
parentRoute: typeof AppImport
|
||||||
|
}
|
||||||
|
'/_app/about': {
|
||||||
|
id: '/_app/about'
|
||||||
path: '/about'
|
path: '/about'
|
||||||
fullPath: '/about'
|
fullPath: '/about'
|
||||||
preLoaderRoute: typeof AboutImport
|
preLoaderRoute: typeof AppAboutImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof AppImport
|
||||||
}
|
}
|
||||||
'/login': {
|
'/_app/login': {
|
||||||
id: '/login'
|
id: '/_app/login'
|
||||||
path: '/login'
|
path: '/login'
|
||||||
fullPath: '/login'
|
fullPath: '/login'
|
||||||
preLoaderRoute: typeof LoginImport
|
preLoaderRoute: typeof AppLoginImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof AppImport
|
||||||
}
|
}
|
||||||
'/_authenticated/campaigns/$campaignId': {
|
'/_app/': {
|
||||||
id: '/_authenticated/campaigns/$campaignId'
|
id: '/_app/'
|
||||||
|
path: '/'
|
||||||
|
fullPath: '/'
|
||||||
|
preLoaderRoute: typeof AppIndexImport
|
||||||
|
parentRoute: typeof AppImport
|
||||||
|
}
|
||||||
|
'/_app/_authenticated/campaigns/$campaignId': {
|
||||||
|
id: '/_app/_authenticated/campaigns/$campaignId'
|
||||||
path: '/campaigns/$campaignId'
|
path: '/campaigns/$campaignId'
|
||||||
fullPath: '/campaigns/$campaignId'
|
fullPath: '/campaigns/$campaignId'
|
||||||
preLoaderRoute: typeof AuthenticatedCampaignsCampaignIdImport
|
preLoaderRoute: typeof AppAuthenticatedCampaignsCampaignIdImport
|
||||||
parentRoute: typeof AuthenticatedImport
|
parentRoute: typeof AppAuthenticatedImport
|
||||||
}
|
}
|
||||||
'/_authenticated/document/$documentId': {
|
'/_app/_authenticated/document/$documentId': {
|
||||||
id: '/_authenticated/document/$documentId'
|
id: '/_app/_authenticated/document/$documentId'
|
||||||
path: '/document/$documentId'
|
path: '/document/$documentId'
|
||||||
fullPath: '/document/$documentId'
|
fullPath: '/document/$documentId'
|
||||||
preLoaderRoute: typeof AuthenticatedDocumentDocumentIdImport
|
preLoaderRoute: typeof AppAuthenticatedDocumentDocumentIdImport
|
||||||
parentRoute: typeof AuthenticatedImport
|
parentRoute: typeof AppAuthenticatedImport
|
||||||
}
|
}
|
||||||
'/_authenticated/campaigns/': {
|
'/_app/_authenticated/campaigns/': {
|
||||||
id: '/_authenticated/campaigns/'
|
id: '/_app/_authenticated/campaigns/'
|
||||||
path: '/campaigns'
|
path: '/campaigns'
|
||||||
fullPath: '/campaigns'
|
fullPath: '/campaigns'
|
||||||
preLoaderRoute: typeof AuthenticatedCampaignsIndexImport
|
preLoaderRoute: typeof AppAuthenticatedCampaignsIndexImport
|
||||||
parentRoute: typeof AuthenticatedImport
|
parentRoute: typeof AppAuthenticatedImport
|
||||||
|
}
|
||||||
|
'/_app_/_authenticated/document_/$documentId/print': {
|
||||||
|
id: '/_app_/_authenticated/document_/$documentId/print'
|
||||||
|
path: '/document/$documentId/print'
|
||||||
|
fullPath: '/document/$documentId/print'
|
||||||
|
preLoaderRoute: typeof AppauthenticatedDocumentDocumentIdPrintImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and export the route tree
|
// Create and export the route tree
|
||||||
|
|
||||||
interface AuthenticatedRouteChildren {
|
interface AppAuthenticatedRouteChildren {
|
||||||
AuthenticatedCampaignsCampaignIdRoute: typeof AuthenticatedCampaignsCampaignIdRoute
|
AppAuthenticatedCampaignsCampaignIdRoute: typeof AppAuthenticatedCampaignsCampaignIdRoute
|
||||||
AuthenticatedDocumentDocumentIdRoute: typeof AuthenticatedDocumentDocumentIdRoute
|
AppAuthenticatedDocumentDocumentIdRoute: typeof AppAuthenticatedDocumentDocumentIdRoute
|
||||||
AuthenticatedCampaignsIndexRoute: typeof AuthenticatedCampaignsIndexRoute
|
AppAuthenticatedCampaignsIndexRoute: typeof AppAuthenticatedCampaignsIndexRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthenticatedRouteChildren: AuthenticatedRouteChildren = {
|
const AppAuthenticatedRouteChildren: AppAuthenticatedRouteChildren = {
|
||||||
AuthenticatedCampaignsCampaignIdRoute: AuthenticatedCampaignsCampaignIdRoute,
|
AppAuthenticatedCampaignsCampaignIdRoute:
|
||||||
AuthenticatedDocumentDocumentIdRoute: AuthenticatedDocumentDocumentIdRoute,
|
AppAuthenticatedCampaignsCampaignIdRoute,
|
||||||
AuthenticatedCampaignsIndexRoute: AuthenticatedCampaignsIndexRoute,
|
AppAuthenticatedDocumentDocumentIdRoute:
|
||||||
|
AppAuthenticatedDocumentDocumentIdRoute,
|
||||||
|
AppAuthenticatedCampaignsIndexRoute: AppAuthenticatedCampaignsIndexRoute,
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthenticatedRouteWithChildren = AuthenticatedRoute._addFileChildren(
|
const AppAuthenticatedRouteWithChildren =
|
||||||
AuthenticatedRouteChildren,
|
AppAuthenticatedRoute._addFileChildren(AppAuthenticatedRouteChildren)
|
||||||
)
|
|
||||||
|
interface AppRouteChildren {
|
||||||
|
AppAuthenticatedRoute: typeof AppAuthenticatedRouteWithChildren
|
||||||
|
AppAboutRoute: typeof AppAboutRoute
|
||||||
|
AppLoginRoute: typeof AppLoginRoute
|
||||||
|
AppIndexRoute: typeof AppIndexRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
const AppRouteChildren: AppRouteChildren = {
|
||||||
|
AppAuthenticatedRoute: AppAuthenticatedRouteWithChildren,
|
||||||
|
AppAboutRoute: AppAboutRoute,
|
||||||
|
AppLoginRoute: AppLoginRoute,
|
||||||
|
AppIndexRoute: AppIndexRoute,
|
||||||
|
}
|
||||||
|
|
||||||
|
const AppRouteWithChildren = AppRoute._addFileChildren(AppRouteChildren)
|
||||||
|
|
||||||
export interface FileRoutesByFullPath {
|
export interface FileRoutesByFullPath {
|
||||||
'/': typeof IndexRoute
|
'': typeof AppAuthenticatedRouteWithChildren
|
||||||
'': typeof AuthenticatedRouteWithChildren
|
'/about': typeof AppAboutRoute
|
||||||
'/about': typeof AboutRoute
|
'/login': typeof AppLoginRoute
|
||||||
'/login': typeof LoginRoute
|
'/': typeof AppIndexRoute
|
||||||
'/campaigns/$campaignId': typeof AuthenticatedCampaignsCampaignIdRoute
|
'/campaigns/$campaignId': typeof AppAuthenticatedCampaignsCampaignIdRoute
|
||||||
'/document/$documentId': typeof AuthenticatedDocumentDocumentIdRoute
|
'/document/$documentId': typeof AppAuthenticatedDocumentDocumentIdRoute
|
||||||
'/campaigns': typeof AuthenticatedCampaignsIndexRoute
|
'/campaigns': typeof AppAuthenticatedCampaignsIndexRoute
|
||||||
|
'/document/$documentId/print': typeof AppauthenticatedDocumentDocumentIdPrintRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
'/': typeof IndexRoute
|
'': typeof AppAuthenticatedRouteWithChildren
|
||||||
'': typeof AuthenticatedRouteWithChildren
|
'/about': typeof AppAboutRoute
|
||||||
'/about': typeof AboutRoute
|
'/login': typeof AppLoginRoute
|
||||||
'/login': typeof LoginRoute
|
'/': typeof AppIndexRoute
|
||||||
'/campaigns/$campaignId': typeof AuthenticatedCampaignsCampaignIdRoute
|
'/campaigns/$campaignId': typeof AppAuthenticatedCampaignsCampaignIdRoute
|
||||||
'/document/$documentId': typeof AuthenticatedDocumentDocumentIdRoute
|
'/document/$documentId': typeof AppAuthenticatedDocumentDocumentIdRoute
|
||||||
'/campaigns': typeof AuthenticatedCampaignsIndexRoute
|
'/campaigns': typeof AppAuthenticatedCampaignsIndexRoute
|
||||||
|
'/document/$documentId/print': typeof AppauthenticatedDocumentDocumentIdPrintRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileRoutesById {
|
export interface FileRoutesById {
|
||||||
__root__: typeof rootRoute
|
__root__: typeof rootRoute
|
||||||
'/': typeof IndexRoute
|
'/_app': typeof AppRouteWithChildren
|
||||||
'/_authenticated': typeof AuthenticatedRouteWithChildren
|
'/_app/_authenticated': typeof AppAuthenticatedRouteWithChildren
|
||||||
'/about': typeof AboutRoute
|
'/_app/about': typeof AppAboutRoute
|
||||||
'/login': typeof LoginRoute
|
'/_app/login': typeof AppLoginRoute
|
||||||
'/_authenticated/campaigns/$campaignId': typeof AuthenticatedCampaignsCampaignIdRoute
|
'/_app/': typeof AppIndexRoute
|
||||||
'/_authenticated/document/$documentId': typeof AuthenticatedDocumentDocumentIdRoute
|
'/_app/_authenticated/campaigns/$campaignId': typeof AppAuthenticatedCampaignsCampaignIdRoute
|
||||||
'/_authenticated/campaigns/': typeof AuthenticatedCampaignsIndexRoute
|
'/_app/_authenticated/document/$documentId': typeof AppAuthenticatedDocumentDocumentIdRoute
|
||||||
|
'/_app/_authenticated/campaigns/': typeof AppAuthenticatedCampaignsIndexRoute
|
||||||
|
'/_app_/_authenticated/document_/$documentId/print': typeof AppauthenticatedDocumentDocumentIdPrintRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileRouteTypes {
|
export interface FileRouteTypes {
|
||||||
fileRoutesByFullPath: FileRoutesByFullPath
|
fileRoutesByFullPath: FileRoutesByFullPath
|
||||||
fullPaths:
|
fullPaths:
|
||||||
| '/'
|
|
||||||
| ''
|
| ''
|
||||||
| '/about'
|
| '/about'
|
||||||
| '/login'
|
| '/login'
|
||||||
|
| '/'
|
||||||
| '/campaigns/$campaignId'
|
| '/campaigns/$campaignId'
|
||||||
| '/document/$documentId'
|
| '/document/$documentId'
|
||||||
| '/campaigns'
|
| '/campaigns'
|
||||||
|
| '/document/$documentId/print'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to:
|
to:
|
||||||
| '/'
|
|
||||||
| ''
|
| ''
|
||||||
| '/about'
|
| '/about'
|
||||||
| '/login'
|
| '/login'
|
||||||
|
| '/'
|
||||||
| '/campaigns/$campaignId'
|
| '/campaigns/$campaignId'
|
||||||
| '/document/$documentId'
|
| '/document/$documentId'
|
||||||
| '/campaigns'
|
| '/campaigns'
|
||||||
|
| '/document/$documentId/print'
|
||||||
id:
|
id:
|
||||||
| '__root__'
|
| '__root__'
|
||||||
| '/'
|
| '/_app'
|
||||||
| '/_authenticated'
|
| '/_app/_authenticated'
|
||||||
| '/about'
|
| '/_app/about'
|
||||||
| '/login'
|
| '/_app/login'
|
||||||
| '/_authenticated/campaigns/$campaignId'
|
| '/_app/'
|
||||||
| '/_authenticated/document/$documentId'
|
| '/_app/_authenticated/campaigns/$campaignId'
|
||||||
| '/_authenticated/campaigns/'
|
| '/_app/_authenticated/document/$documentId'
|
||||||
|
| '/_app/_authenticated/campaigns/'
|
||||||
|
| '/_app_/_authenticated/document_/$documentId/print'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RootRouteChildren {
|
export interface RootRouteChildren {
|
||||||
IndexRoute: typeof IndexRoute
|
AppRoute: typeof AppRouteWithChildren
|
||||||
AuthenticatedRoute: typeof AuthenticatedRouteWithChildren
|
AppauthenticatedDocumentDocumentIdPrintRoute: typeof AppauthenticatedDocumentDocumentIdPrintRoute
|
||||||
AboutRoute: typeof AboutRoute
|
|
||||||
LoginRoute: typeof LoginRoute
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootRouteChildren: RootRouteChildren = {
|
const rootRouteChildren: RootRouteChildren = {
|
||||||
IndexRoute: IndexRoute,
|
AppRoute: AppRouteWithChildren,
|
||||||
AuthenticatedRoute: AuthenticatedRouteWithChildren,
|
AppauthenticatedDocumentDocumentIdPrintRoute:
|
||||||
AboutRoute: AboutRoute,
|
AppauthenticatedDocumentDocumentIdPrintRoute,
|
||||||
LoginRoute: LoginRoute,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const routeTree = rootRoute
|
export const routeTree = rootRoute
|
||||||
@@ -225,40 +275,54 @@ export const routeTree = rootRoute
|
|||||||
"__root__": {
|
"__root__": {
|
||||||
"filePath": "__root.tsx",
|
"filePath": "__root.tsx",
|
||||||
"children": [
|
"children": [
|
||||||
"/",
|
"/_app",
|
||||||
"/_authenticated",
|
"/_app_/_authenticated/document_/$documentId/print"
|
||||||
"/about",
|
|
||||||
"/login"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"/": {
|
"/_app": {
|
||||||
"filePath": "index.tsx"
|
"filePath": "_app.tsx",
|
||||||
},
|
|
||||||
"/_authenticated": {
|
|
||||||
"filePath": "_authenticated.tsx",
|
|
||||||
"children": [
|
"children": [
|
||||||
"/_authenticated/campaigns/$campaignId",
|
"/_app/_authenticated",
|
||||||
"/_authenticated/document/$documentId",
|
"/_app/about",
|
||||||
"/_authenticated/campaigns/"
|
"/_app/login",
|
||||||
|
"/_app/"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"/about": {
|
"/_app/_authenticated": {
|
||||||
"filePath": "about.tsx"
|
"filePath": "_app/_authenticated.tsx",
|
||||||
|
"parent": "/_app",
|
||||||
|
"children": [
|
||||||
|
"/_app/_authenticated/campaigns/$campaignId",
|
||||||
|
"/_app/_authenticated/document/$documentId",
|
||||||
|
"/_app/_authenticated/campaigns/"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"/login": {
|
"/_app/about": {
|
||||||
"filePath": "login.tsx"
|
"filePath": "_app/about.tsx",
|
||||||
|
"parent": "/_app"
|
||||||
},
|
},
|
||||||
"/_authenticated/campaigns/$campaignId": {
|
"/_app/login": {
|
||||||
"filePath": "_authenticated/campaigns.$campaignId.tsx",
|
"filePath": "_app/login.tsx",
|
||||||
"parent": "/_authenticated"
|
"parent": "/_app"
|
||||||
},
|
},
|
||||||
"/_authenticated/document/$documentId": {
|
"/_app/": {
|
||||||
"filePath": "_authenticated/document.$documentId.tsx",
|
"filePath": "_app/index.tsx",
|
||||||
"parent": "/_authenticated"
|
"parent": "/_app"
|
||||||
},
|
},
|
||||||
"/_authenticated/campaigns/": {
|
"/_app/_authenticated/campaigns/$campaignId": {
|
||||||
"filePath": "_authenticated/campaigns.index.tsx",
|
"filePath": "_app/_authenticated/campaigns.$campaignId.tsx",
|
||||||
"parent": "/_authenticated"
|
"parent": "/_app/_authenticated"
|
||||||
|
},
|
||||||
|
"/_app/_authenticated/document/$documentId": {
|
||||||
|
"filePath": "_app/_authenticated/document.$documentId.tsx",
|
||||||
|
"parent": "/_app/_authenticated"
|
||||||
|
},
|
||||||
|
"/_app/_authenticated/campaigns/": {
|
||||||
|
"filePath": "_app/_authenticated/campaigns.index.tsx",
|
||||||
|
"parent": "/_app/_authenticated"
|
||||||
|
},
|
||||||
|
"/_app_/_authenticated/document_/$documentId/print": {
|
||||||
|
"filePath": "_app_._authenticated.document_.$documentId.print.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,75 +1,12 @@
|
|||||||
import { Link, Outlet, createRootRoute } from "@tanstack/react-router";
|
import { AuthProvider } from "@/context/auth/AuthContext";
|
||||||
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
|
|
||||||
import { AuthProvider, useAuth } from "@/context/auth/AuthContext";
|
|
||||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||||
|
import { Outlet, createRootRoute } from "@tanstack/react-router";
|
||||||
/**
|
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
|
||||||
* Root header with navigation and user authentication controls.
|
|
||||||
*/
|
|
||||||
function RootHeader() {
|
|
||||||
const { user, logout, isLoading } = useAuth();
|
|
||||||
return (
|
|
||||||
<header className="flex items-center justify-between px-8 py-4 border-b border-slate-700 bg-slate-900">
|
|
||||||
<h1 className="text-2xl font-bold text-slate-100 m-0">
|
|
||||||
DM's Table Companion
|
|
||||||
</h1>
|
|
||||||
<nav aria-label="Main navigation" className="flex gap-6">
|
|
||||||
<Link
|
|
||||||
to="/campaigns"
|
|
||||||
className="no-underline text-slate-200 hover:text-violet-400 transition-colors font-medium border-b-2 border-transparent pb-1"
|
|
||||||
activeProps={{
|
|
||||||
className:
|
|
||||||
"no-underline text-violet-400 border-violet-400 border-b-2 pb-1",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Campaigns
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
to="/about"
|
|
||||||
className="no-underline text-slate-200 hover:text-violet-400 transition-colors font-medium border-b-2 border-transparent pb-1"
|
|
||||||
activeProps={{
|
|
||||||
className:
|
|
||||||
"no-underline text-violet-400 border-violet-400 border-b-2 pb-1",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
About
|
|
||||||
</Link>
|
|
||||||
</nav>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
{user ? (
|
|
||||||
<>
|
|
||||||
<span className="text-slate-200 text-sm" aria-label="User email">
|
|
||||||
{user.email}
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
onClick={logout}
|
|
||||||
disabled={isLoading}
|
|
||||||
className="bg-red-600 hover:bg-red-700 text-white text-sm font-semibold px-3 py-1 rounded transition-colors disabled:opacity-60"
|
|
||||||
aria-label="Log out"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
Log out
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Link
|
|
||||||
to="/login"
|
|
||||||
className="bg-violet-600 hover:bg-violet-700 text-white text-sm font-semibold px-3 py-1 rounded transition-colors"
|
|
||||||
aria-label="Log in"
|
|
||||||
>
|
|
||||||
Log in
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Route = createRootRoute({
|
export const Route = createRootRoute({
|
||||||
component: () => (
|
component: () => (
|
||||||
<>
|
<>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<RootHeader />
|
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
<TanStackRouterDevtools />
|
<TanStackRouterDevtools />
|
||||||
|
|||||||
77
src/routes/_app.tsx
Normal file
77
src/routes/_app.tsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { useAuth } from "@/context/auth/AuthContext";
|
||||||
|
import { Link, Outlet, createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/_app")({
|
||||||
|
component: RouteComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Root header with navigation and user authentication controls.
|
||||||
|
*/
|
||||||
|
function AppHeader() {
|
||||||
|
const { user, logout, isLoading } = useAuth();
|
||||||
|
return (
|
||||||
|
<header className="flex items-center justify-between px-8 py-4 border-b border-slate-700 bg-slate-900">
|
||||||
|
<h1 className="text-2xl font-bold text-slate-100 m-0">
|
||||||
|
DM's Table Companion
|
||||||
|
</h1>
|
||||||
|
<nav aria-label="Main navigation" className="flex gap-6">
|
||||||
|
<Link
|
||||||
|
to="/campaigns"
|
||||||
|
className="no-underline text-slate-200 hover:text-violet-400 transition-colors font-medium border-b-2 border-transparent pb-1"
|
||||||
|
activeProps={{
|
||||||
|
className:
|
||||||
|
"no-underline text-violet-400 border-violet-400 border-b-2 pb-1",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Campaigns
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/about"
|
||||||
|
className="no-underline text-slate-200 hover:text-violet-400 transition-colors font-medium border-b-2 border-transparent pb-1"
|
||||||
|
activeProps={{
|
||||||
|
className:
|
||||||
|
"no-underline text-violet-400 border-violet-400 border-b-2 pb-1",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
About
|
||||||
|
</Link>
|
||||||
|
</nav>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
{user ? (
|
||||||
|
<>
|
||||||
|
<span className="text-slate-200 text-sm" aria-label="User email">
|
||||||
|
{user.email}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={logout}
|
||||||
|
disabled={isLoading}
|
||||||
|
className="bg-red-600 hover:bg-red-700 text-white text-sm font-semibold px-3 py-1 rounded transition-colors disabled:opacity-60"
|
||||||
|
aria-label="Log out"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Log out
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Link
|
||||||
|
to="/login"
|
||||||
|
className="bg-violet-600 hover:bg-violet-700 text-white text-sm font-semibold px-3 py-1 rounded transition-colors"
|
||||||
|
aria-label="Log in"
|
||||||
|
>
|
||||||
|
Log in
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AppHeader />
|
||||||
|
<Outlet />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { isAuthenticated } from "@/lib/pocketbase";
|
import { isAuthenticated } from "@/lib/pocketbase";
|
||||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createFileRoute("/_authenticated")({
|
export const Route = createFileRoute("/_app/_authenticated")({
|
||||||
beforeLoad: () => {
|
beforeLoad: () => {
|
||||||
if (!isAuthenticated()) {
|
if (!isAuthenticated()) {
|
||||||
throw redirect({
|
throw redirect({
|
||||||
@@ -6,7 +6,7 @@ import { Button } from "@headlessui/react";
|
|||||||
import { useQueryClient, useSuspenseQuery } from "@tanstack/react-query";
|
import { useQueryClient, useSuspenseQuery } from "@tanstack/react-query";
|
||||||
import { Loader } from "@/components/Loader";
|
import { Loader } from "@/components/Loader";
|
||||||
|
|
||||||
export const Route = createFileRoute("/_authenticated/campaigns/$campaignId")({
|
export const Route = createFileRoute("/_app/_authenticated/campaigns/$campaignId")({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
pendingComponent: Loader,
|
pendingComponent: Loader,
|
||||||
});
|
});
|
||||||
@@ -6,7 +6,7 @@ import { Loader } from "@/components/Loader";
|
|||||||
import { CreateCampaignButton } from "@/components/CreateCampaignButton";
|
import { CreateCampaignButton } from "@/components/CreateCampaignButton";
|
||||||
import { useRouter } from "@tanstack/react-router";
|
import { useRouter } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createFileRoute("/_authenticated/campaigns/")({
|
export const Route = createFileRoute("/_app/_authenticated/campaigns/")({
|
||||||
loader: async () => {
|
loader: async () => {
|
||||||
const records = await pb.collection("campaigns").getFullList();
|
const records = await pb.collection("campaigns").getFullList();
|
||||||
return {
|
return {
|
||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
import { RelationshipList } from "@/components/RelationshipList";
|
import { RelationshipList } from "@/components/RelationshipList";
|
||||||
import { SessionForm } from "@/components/documents/session/SessionForm";
|
import { SessionForm } from "@/components/documents/session/SessionForm";
|
||||||
|
|
||||||
export const Route = createFileRoute("/_authenticated/document/$documentId")({
|
export const Route = createFileRoute("/_app/_authenticated/document/$documentId")({
|
||||||
loader: async ({ params }) => {
|
loader: async ({ params }) => {
|
||||||
const doc = await pb.collection("documents").getOne(params.documentId);
|
const doc = await pb.collection("documents").getOne(params.documentId);
|
||||||
const relationships: Relationship[] = await pb
|
const relationships: Relationship[] = await pb
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
export const Route = createFileRoute('/about')({
|
export const Route = createFileRoute('/_app/about')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createFileRoute("/")({
|
export const Route = createFileRoute("/_app/")({
|
||||||
component: App,
|
component: App,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ import { useState } from "react";
|
|||||||
* Login and signup page for authentication.
|
* Login and signup page for authentication.
|
||||||
* Allows users to log in or create a new account.
|
* Allows users to log in or create a new account.
|
||||||
*/
|
*/
|
||||||
export const Route = createFileRoute("/login")({
|
export const Route = createFileRoute("/_app/login")({
|
||||||
beforeLoad: () => {
|
beforeLoad: () => {
|
||||||
if (isAuthenticated()) {
|
if (isAuthenticated()) {
|
||||||
throw redirect({
|
throw redirect({
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { DocumentPrintRow } from "@/components/documents/DocumentPrintRow";
|
||||||
|
import { SessionPrintRow } from "@/components/documents/session/SessionPrintRow";
|
||||||
|
import { Loader } from "@/components/Loader";
|
||||||
|
import { RelationshipList } from "@/components/RelationshipList";
|
||||||
|
import { pb } from "@/lib/pocketbase";
|
||||||
|
import { RelationshipType, type Relationship, type Session } from "@/lib/types";
|
||||||
|
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
|
export const Route = createFileRoute(
|
||||||
|
"/_app_/_authenticated/document_/$documentId/print",
|
||||||
|
)({
|
||||||
|
component: RouteComponent,
|
||||||
|
pendingComponent: Loader,
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
const params = Route.useParams();
|
||||||
|
const {
|
||||||
|
data: { session, relationships },
|
||||||
|
} = useSuspenseQuery({
|
||||||
|
queryKey: ["session", "relationships"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const session = await pb
|
||||||
|
.collection("documents")
|
||||||
|
.getOne(params.documentId);
|
||||||
|
const relationships: Relationship[] = await pb
|
||||||
|
.collection("relationships")
|
||||||
|
.getFullList({
|
||||||
|
filter: `primary = "${params.documentId}"`,
|
||||||
|
expand: "secondary",
|
||||||
|
});
|
||||||
|
console.log("Fetched data: ", relationships);
|
||||||
|
return {
|
||||||
|
session: session as Session,
|
||||||
|
relationships: _.mapValues(
|
||||||
|
_.groupBy(relationships, (r) => r.type),
|
||||||
|
(rs: Relationship[]) => rs.flatMap((r) => r.expand?.secondary),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Parsed data: ", relationships);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fill-w py-8 columns-2 gap-8">
|
||||||
|
<SessionPrintRow session={session}></SessionPrintRow>
|
||||||
|
|
||||||
|
{[
|
||||||
|
RelationshipType.Scenes,
|
||||||
|
RelationshipType.Secrets,
|
||||||
|
RelationshipType.Locations,
|
||||||
|
RelationshipType.Npcs,
|
||||||
|
RelationshipType.Monsters,
|
||||||
|
RelationshipType.Treasures,
|
||||||
|
].map((relationshipType) => (
|
||||||
|
<div className="break-before-column">
|
||||||
|
<h3 className="text-lg font-bold text-slate-600">
|
||||||
|
{relationshipType.charAt(0).toUpperCase() +
|
||||||
|
relationshipType.slice(1)}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<ul className="list-disc pl-5">
|
||||||
|
{(relationships[relationshipType] ?? []).map((item) => (
|
||||||
|
<DocumentPrintRow document={item} />
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user