Moves routes inside an _app for the header and builds a print route
This commit is contained in:
@@ -1,75 +1,12 @@
|
||||
import { Link, Outlet, createRootRoute } from "@tanstack/react-router";
|
||||
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
|
||||
import { AuthProvider, useAuth } from "@/context/auth/AuthContext";
|
||||
import { AuthProvider } from "@/context/auth/AuthContext";
|
||||
import { ReactQueryDevtools } from "@tanstack/react-query-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>
|
||||
);
|
||||
}
|
||||
import { Outlet, createRootRoute } from "@tanstack/react-router";
|
||||
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
|
||||
|
||||
export const Route = createRootRoute({
|
||||
component: () => (
|
||||
<>
|
||||
<AuthProvider>
|
||||
<RootHeader />
|
||||
<Outlet />
|
||||
</AuthProvider>
|
||||
<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 { createFileRoute, redirect } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/_authenticated")({
|
||||
export const Route = createFileRoute("/_app/_authenticated")({
|
||||
beforeLoad: () => {
|
||||
if (!isAuthenticated()) {
|
||||
throw redirect({
|
||||
@@ -6,7 +6,7 @@ import { Button } from "@headlessui/react";
|
||||
import { useQueryClient, useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { Loader } from "@/components/Loader";
|
||||
|
||||
export const Route = createFileRoute("/_authenticated/campaigns/$campaignId")({
|
||||
export const Route = createFileRoute("/_app/_authenticated/campaigns/$campaignId")({
|
||||
component: RouteComponent,
|
||||
pendingComponent: Loader,
|
||||
});
|
||||
@@ -6,7 +6,7 @@ import { Loader } from "@/components/Loader";
|
||||
import { CreateCampaignButton } from "@/components/CreateCampaignButton";
|
||||
import { useRouter } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/_authenticated/campaigns/")({
|
||||
export const Route = createFileRoute("/_app/_authenticated/campaigns/")({
|
||||
loader: async () => {
|
||||
const records = await pb.collection("campaigns").getFullList();
|
||||
return {
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import { RelationshipList } from "@/components/RelationshipList";
|
||||
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 }) => {
|
||||
const doc = await pb.collection("documents").getOne(params.documentId);
|
||||
const relationships: Relationship[] = await pb
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/about')({
|
||||
export const Route = createFileRoute('/_app/about')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/")({
|
||||
export const Route = createFileRoute("/_app/")({
|
||||
component: App,
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ 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")({
|
||||
export const Route = createFileRoute("/_app/login")({
|
||||
beforeLoad: () => {
|
||||
if (isAuthenticated()) {
|
||||
throw redirect({
|
||||
@@ -0,0 +1,73 @@
|
||||
import { DocumentPrintRow } from "@/components/documents/DocumentPrintRow";
|
||||
import { SessionPrintRow } from "@/components/documents/session/SessionPrintRow";
|
||||
import { Loader } from "@/components/Loader";
|
||||
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