Adds secret check-offs

This commit is contained in:
2025-05-28 16:18:49 -07:00
parent 1ec4d0ac35
commit 3c989cf285
2 changed files with 92 additions and 23 deletions

View File

@@ -37,7 +37,7 @@ export type Secret = Document &
"secret", "secret",
{ {
text: string; text: string;
discoveredOn: ISO8601Date | null; discovered: boolean;
} }
>; >;

View File

@@ -1,11 +1,10 @@
import { createFileRoute } from '@tanstack/react-router' import { createFileRoute } from "@tanstack/react-router";
import { pb } from "@/lib/pocketbase"; import { pb } from "@/lib/pocketbase";
import { AutoSaveTextarea } from "@/components/AutoSaveTextarea"; import { AutoSaveTextarea } from "@/components/AutoSaveTextarea";
import { useState } from "react"; import { useState } from "react";
import type { Secret } from "@/lib/types";
export const Route = createFileRoute( export const Route = createFileRoute("/_authenticated/document/$documentId")({
'/_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);
// Fetch the unique relationship where this document is the primary and type is "plannedSecrets" // Fetch the unique relationship where this document is the primary and type is "plannedSecrets"
@@ -13,12 +12,13 @@ export const Route = createFileRoute(
filter: `primary = "${params.documentId}" && type = "plannedSecrets"`, filter: `primary = "${params.documentId}" && type = "plannedSecrets"`,
}); });
// Get all related secret document IDs from the secondary field // Get all related secret document IDs from the secondary field
const secretIds = relationships.items.length > 0 ? relationships.items[0].secondary : []; const secretIds =
relationships.items.length > 0 ? relationships.items[0].secondary : [];
// Fetch all related secret documents // Fetch all related secret documents
let secrets: any[] = []; let secrets: any[] = [];
if (Array.isArray(secretIds) && secretIds.length > 0) { if (Array.isArray(secretIds) && secretIds.length > 0) {
secrets = await pb.collection("documents").getFullList({ secrets = await pb.collection("documents").getFullList({
filter: secretIds.map(id => `id = "${id}"`).join(" || "), filter: secretIds.map((id) => `id = "${id}"`).join(" || "),
}); });
} }
return { document: doc, secrets }; return { document: doc, secrets };
@@ -27,19 +27,19 @@ export const Route = createFileRoute(
}); });
function RouteComponent() { function RouteComponent() {
const { document, secrets } = Route.useLoaderData(); const { document: session, secrets } = Route.useLoaderData();
const strongStart = document?.data?.session?.strongStart || ""; const strongStart = session?.data?.session?.strongStart || "";
const [newSecret, setNewSecret] = useState(""); const [newSecret, setNewSecret] = useState("");
const [adding, setAdding] = useState(false); const [adding, setAdding] = useState(false);
const [secretList, setSecretList] = useState(secrets); const [secretList, setSecretList] = useState(secrets);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
async function handleSaveStrongStart(newValue: string) { async function handleSaveStrongStart(newValue: string) {
await pb.collection("documents").update(document.id, { await pb.collection("documents").update(session.id, {
data: { data: {
...document.data, ...session.data,
session: { session: {
...document.data.session, ...session.data.session,
strongStart: newValue, strongStart: newValue,
}, },
}, },
@@ -53,7 +53,7 @@ function RouteComponent() {
try { try {
// 1. Create the secret document // 1. Create the secret document
const secretDoc = await pb.collection("documents").create({ const secretDoc = await pb.collection("documents").create({
campaign: document.campaign, // assuming campaign is an id or object campaign: session.campaign, // assuming campaign is an id or object
data: { data: {
secret: { secret: {
text: newSecret, text: newSecret,
@@ -62,8 +62,8 @@ function RouteComponent() {
}, },
}); });
// 2. Check for existing relationship // 2. Check for existing relationship
const existing = await pb.collection("relationships").getOne({ const existing = await pb.collection("relationships").getFullList({
filter: `primary = "${document.id}" && type = "plannedSecrets"`, filter: `primary = "${session.id}" && type = "plannedSecrets"`,
}); });
if (existing.length > 0) { if (existing.length > 0) {
// Update existing relationship to add new secret to secondary array // Update existing relationship to add new secret to secondary array
@@ -73,7 +73,7 @@ function RouteComponent() {
} else { } else {
// Create new relationship // Create new relationship
await pb.collection("relationships").create({ await pb.collection("relationships").create({
primary: document.id, primary: session.id,
secondary: [secretDoc.id], secondary: [secretDoc.id],
type: "plannedSecrets", type: "plannedSecrets",
}); });
@@ -87,37 +87,106 @@ function RouteComponent() {
} }
} }
async function handleToggleDiscovered(secret: Secret, checked: boolean) {
// 1. Update the discovered field in the secret document
await pb.collection("documents").update(secret.id, {
data: {
...secret.data,
secret: {
text: secret.data.secret.text,
discovered: checked,
},
},
});
// 2. Remove any existing discoveredIn relationship
const rels = await pb.collection("relationships").getList(1, 1, {
filter: `primary = "${secret.id}" && type = "discoveredIn"`,
});
if (rels.items.length > 0) {
await pb.collection("relationships").delete(rels.items[0].id);
}
// 3. If marking as discovered, add a new discoveredIn relationship
if (checked) {
await pb.collection("relationships").create({
primary: secret.id,
secondary: [session.id],
type: "discoveredIn",
});
}
// 4. Update local state
setSecretList(
secretList.map((s: any) =>
s.id === secret.id
? {
...s,
data: {
...s.data,
secret: { ...s.data.secret, discovered: checked },
},
}
: s,
),
);
}
return ( return (
<div className="max-w-xl mx-auto py-8"> <div className="max-w-xl mx-auto py-8">
<h2 className="text-2xl font-bold mb-4 text-slate-100">Session Strong Start</h2> <h2 className="text-2xl font-bold mb-4 text-slate-100">
Session Strong Start
</h2>
<AutoSaveTextarea <AutoSaveTextarea
value={strongStart} value={strongStart}
onSave={handleSaveStrongStart} onSave={handleSaveStrongStart}
placeholder="Enter a strong start for this session..." placeholder="Enter a strong start for this session..."
aria-label="Strong Start" aria-label="Strong Start"
/> />
<h3 className="text-lg font-semibold mt-8 mb-2 text-slate-200">Planned Secrets</h3> <h3 className="text-lg font-semibold mt-8 mb-2 text-slate-200">
Planned Secrets
</h3>
{secretList && secretList.length > 0 ? ( {secretList && secretList.length > 0 ? (
<ul className="space-y-2"> <ul className="space-y-2">
{secretList.map((secret: any) => ( {secretList.map((secret: any) => (
<li key={secret.id} className="bg-slate-800 rounded p-4 text-slate-100"> <li
{secret.data?.secret?.text || <span className="italic text-slate-400">(No secret text)</span>} key={secret.id}
className="bg-slate-800 rounded p-4 text-slate-100 flex items-center gap-3"
>
<input
type="checkbox"
checked={!!secret.data?.secret?.discovered}
onChange={(e) =>
handleToggleDiscovered(secret, e.target.checked)
}
className="accent-emerald-500 w-5 h-5"
aria-label="Discovered"
/>
<span>
{secret.data?.secret?.text || (
<span className="italic text-slate-400">
(No secret text)
</span>
)}
</span>
</li> </li>
))} ))}
</ul> </ul>
) : ( ) : (
<div className="text-slate-400">No planned secrets for this session.</div> <div className="text-slate-400">
No planned secrets for this session.
</div>
)} )}
<form <form
className="flex items-center gap-2 mt-4" className="flex items-center gap-2 mt-4"
onSubmit={e => { e.preventDefault(); handleAddSecret(); }} onSubmit={(e) => {
e.preventDefault();
handleAddSecret();
}}
> >
<input <input
type="text" type="text"
className="flex-1 px-3 py-2 rounded bg-slate-800 text-slate-100 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-violet-500" className="flex-1 px-3 py-2 rounded bg-slate-800 text-slate-100 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-violet-500"
placeholder="Add a new secret..." placeholder="Add a new secret..."
value={newSecret} value={newSecret}
onChange={e => setNewSecret(e.target.value)} onChange={(e) => setNewSecret(e.target.value)}
disabled={adding} disabled={adding}
/> />
<button <button