chore(seed): demo client, project, users and cards

This commit is contained in:
Dave 2026-09-16 09:44:19 +02:00
parent 76438f6e90
commit 857ef1a994
2 changed files with 162 additions and 1 deletions

View file

@ -42,6 +42,7 @@ src/
│ ├── globals.css # REWRITE — token system
│ ├── layout.tsx # REWRITE — font, theme, no-flash
│ ├── page.tsx # REWRITE — role-aware home
│ ├── api/owner/cards/[id]/move/route.ts # owner drag persistence
│ └── p/[clientSlug]/[projectSlug]/
│ └── page.tsx # board route (server component)
├── components/
@ -2200,6 +2201,8 @@ git add src/components/board
git commit -m "feat(board): refresh the board when the window regains focus"
```
---
## Task 13: Full verification
- [ ] **Step 1: Run every test**
@ -2216,7 +2219,7 @@ Expected: PASS. Unit tests now total 68 — the 47 from Plan 1a plus 10 in `date
npm run build
```
Expected: build succeeds with no type errors. The route list includes `/`, `/login`, and `/p/[clientSlug]/[projectSlug]`.
Expected: build succeeds with no type errors. The route list includes `/`, `/login`, `/p/[clientSlug]/[projectSlug]`, and `/api/owner/cards/[id]/move`.
- [ ] **Step 3: Check both themes at both roles**
@ -2249,4 +2252,6 @@ git commit -m "fix(theme): resolve contrast and token issues found in review"
Plan 1c: card detail view with description, subtasks, comments and activity timeline; admin pages for clients, projects and users; the Inbox accept flow that sets `accepted = true` and `start_date`; card creation UI for both roles; archive.
Also deferred to Plan 1c: **true PocketBase realtime**. It needs an authenticated PocketBase connection in the browser, which means exposing the auth token to JavaScript. That is a security tradeoff worth making deliberately, with the alternatives compared, rather than acquiring by accident while building a board. Task 12's refresh-on-focus covers the case that actually matters in the meantime.
Phases 2–5 per the spec: Forgejo ingestion and the effort dashboard — which is where the KPI row with sparklines from the second design reference belongs — AI humanization, the client intake wizard, and the roadmap view.

156
scripts/seed-demo.ts Normal file
View file

@ -0,0 +1,156 @@
/**
* Seed a demo dataset: one client, one project with default columns, an owner,
* a client manager, and a few cards.
*
* Idempotent — re-running updates the existing records rather than duplicating
* them, so it is safe to run between test runs.
*
* Run with: npm run seed (PocketBase must be running)
*/
import PocketBase from "pocketbase";
import { buildColumnRecords, assertColumnInvariants } from "../src/lib/columns/seed";
import { SPACING } from "../src/lib/ordering/fractional";
const PB_URL = process.env.NEXT_PUBLIC_PB_URL ?? "http://127.0.0.1:8090";
export const DEMO = {
clientSlug: "wf",
projectSlug: "wfdash-webapp",
ownerEmail: "owner@example.com",
ownerPassword: "owner-password-123",
managerEmail: "manager@wf.example.com",
managerPassword: "manager-password-123",
};
async function upsert<T extends { id: string }>(
pb: PocketBase,
collection: string,
filter: string,
data: Record<string, unknown>
): Promise<T> {
const existing = await pb
.collection(collection)
.getFirstListItem<T>(filter)
.catch(() => null);
if (existing) {
return pb.collection(collection).update<T>(existing.id, data);
}
return pb.collection(collection).create<T>(data);
}
async function main() {
const email = process.env.PB_SUPERUSER_EMAIL;
const password = process.env.PB_SUPERUSER_PASSWORD;
if (!email || !password) {
throw new Error(
"PB_SUPERUSER_EMAIL and PB_SUPERUSER_PASSWORD must be set. Copy .env.example to .env.local and export them."
);
}
const pb = new PocketBase(PB_URL);
pb.autoCancellation(false);
await pb.collection("_superusers").authWithPassword(email, password);
const client = await upsert<{ id: string }>(
pb,
"clients",
`slug = "${DEMO.clientSlug}"`,
{
name: "WF",
slug: DEMO.clientSlug,
brand_color: "#3b82f6",
active: true,
}
);
const project = await upsert<{ id: string }>(
pb,
"projects",
`slug = "${DEMO.projectSlug}"`,
{
client: client.id,
name: "WFDash Web App",
slug: DEMO.projectSlug,
description: "Operations web application.",
status: "active",
color: "#3b82f6",
repo_owner: "David",
repo_name: "WFDASH-WEBAPP",
}
);
const columnInputs = buildColumnRecords(project.id);
assertColumnInvariants(columnInputs);
const columns: Record<string, string> = {};
for (const input of columnInputs) {
const record = await upsert<{ id: string }>(
pb,
"columns",
`project = "${project.id}" && name = "${input.name}"`,
input
);
columns[input.name] = record.id;
}
await upsert(pb, "users", `email = "${DEMO.ownerEmail}"`, {
email: DEMO.ownerEmail,
password: DEMO.ownerPassword,
passwordConfirm: DEMO.ownerPassword,
name: "Dave",
role: "owner",
verified: true,
});
await upsert(pb, "users", `email = "${DEMO.managerEmail}"`, {
email: DEMO.managerEmail,
password: DEMO.managerPassword,
passwordConfirm: DEMO.managerPassword,
name: "WF Manager",
role: "client_manager",
client: client.id,
verified: true,
});
const cards = [
{ title: "Quote attachments split", column: "In Progress", priority: "high" },
{ title: "Stock inline quantity editing", column: "To Do", priority: "medium" },
{ title: "Delivery note grouping", column: "Done", priority: "low" },
];
let position = SPACING;
for (const card of cards) {
await upsert(
pb,
"cards",
`project = "${project.id}" && title = "${card.title}"`,
{
project: project.id,
column: columns[card.column],
title: card.title,
description: "",
position,
priority: card.priority,
created_by_role: "owner",
accepted: true,
archived: false,
}
);
position += SPACING;
}
console.log("Seeded:");
console.log(` client ${client.id} (${DEMO.clientSlug})`);
console.log(` project ${project.id} (${DEMO.projectSlug})`);
console.log(` owner ${DEMO.ownerEmail} / ${DEMO.ownerPassword}`);
console.log(` manager ${DEMO.managerEmail} / ${DEMO.managerPassword}`);
}
main().catch((error) => {
console.error(error);
process.exit(1);
});