199 lines
9.7 KiB
Markdown
199 lines
9.7 KiB
Markdown
---
|
|
phase: 18-global-items-public-profiles
|
|
plan: 04
|
|
type: execute
|
|
wave: 3
|
|
depends_on: ["18-02"]
|
|
files_modified:
|
|
- src/client/hooks/useGlobalItems.ts
|
|
- src/client/routes/global-items/index.tsx
|
|
- src/client/routes/global-items/$globalItemId.tsx
|
|
- src/client/components/GlobalItemCard.tsx
|
|
- src/client/components/LinkToGlobalItem.tsx
|
|
- src/client/lib/api.ts
|
|
autonomous: false
|
|
requirements: [GLOB-03, GLOB-04, GLOB-05]
|
|
|
|
must_haves:
|
|
truths:
|
|
- "User can browse a global catalog page listing all global items with brand, model, category"
|
|
- "User can search the global catalog by typing a query and results filter in real-time"
|
|
- "User can click a global item to see its detail page with specs, image, description, and owner count"
|
|
- "User can link a personal collection item to a global item via a UI control"
|
|
artifacts:
|
|
- path: "src/client/hooks/useGlobalItems.ts"
|
|
provides: "useGlobalItems, useGlobalItem, useLinkItem, useUnlinkItem hooks"
|
|
exports: ["useGlobalItems", "useGlobalItem"]
|
|
- path: "src/client/routes/global-items/index.tsx"
|
|
provides: "Global catalog browse/search page"
|
|
min_lines: 40
|
|
- path: "src/client/routes/global-items/$globalItemId.tsx"
|
|
provides: "Global item detail page with owner count"
|
|
min_lines: 30
|
|
- path: "src/client/components/GlobalItemCard.tsx"
|
|
provides: "Card component for global item in list"
|
|
min_lines: 20
|
|
key_links:
|
|
- from: "src/client/routes/global-items/index.tsx"
|
|
to: "src/client/hooks/useGlobalItems.ts"
|
|
via: "useGlobalItems hook"
|
|
pattern: "useGlobalItems"
|
|
- from: "src/client/hooks/useGlobalItems.ts"
|
|
to: "/api/global-items"
|
|
via: "apiGet fetch"
|
|
pattern: "apiGet.*global-items"
|
|
---
|
|
|
|
<objective>
|
|
Build the global item catalog client: search/browse page, detail page with owner count, item cards, and link-to-global-item UI. Users can discover gear and connect their personal items to the shared catalog.
|
|
|
|
Purpose: Delivers the client-side experience for GLOB-03 (search), GLOB-04 (linking), and GLOB-05 (detail with owner count).
|
|
Output: useGlobalItems hook, catalog browse page, detail page, GlobalItemCard, LinkToGlobalItem component
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/phases/18-global-items-public-profiles/18-CONTEXT.md
|
|
@.planning/phases/18-global-items-public-profiles/18-RESEARCH.md
|
|
@.planning/phases/18-global-items-public-profiles/18-02-SUMMARY.md
|
|
|
|
@src/client/hooks/useItems.ts
|
|
@src/client/lib/api.ts
|
|
@src/client/routes/collection/index.tsx
|
|
|
|
<interfaces>
|
|
<!-- API endpoints from Plan 02: -->
|
|
GET /api/global-items?q=string -> GlobalItem[]
|
|
GET /api/global-items/:id -> { ...GlobalItem, ownerCount: number }
|
|
POST /api/items/:id/link { globalItemId: number } -> ItemGlobalLink (201)
|
|
DELETE /api/items/:id/link -> 200
|
|
|
|
<!-- Types from Plan 01: -->
|
|
type GlobalItem = { id, brand, model, category, weightGrams, priceCents, imageUrl, description, createdAt }
|
|
type GlobalItemWithOwnerCount = GlobalItem & { ownerCount: number }
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Global item hooks and catalog pages</name>
|
|
<files>src/client/hooks/useGlobalItems.ts, src/client/routes/global-items/index.tsx, src/client/routes/global-items/$globalItemId.tsx, src/client/components/GlobalItemCard.tsx</files>
|
|
<read_first>src/client/hooks/useItems.ts, src/client/lib/api.ts, src/client/routes/collection/index.tsx</read_first>
|
|
<action>
|
|
**useGlobalItems.ts** hook: Create at `src/client/hooks/useGlobalItems.ts`. Follow existing hook pattern from useItems.ts.
|
|
|
|
1. `useGlobalItems(query?: string)` — `useQuery` with key `["global-items", query]`, fetches `apiGet<GlobalItem[]>("/api/global-items" + (query ? "?q=" + encodeURIComponent(query) : ""))`. Use 300ms debounced query value for search (or accept debounce at the component level).
|
|
|
|
2. `useGlobalItem(id: number | null)` — `useQuery` with key `["global-items", id]`, fetches `apiGet<GlobalItemWithOwnerCount>("/api/global-items/${id}")`, `enabled: id != null`.
|
|
|
|
3. `useLinkItem()` — `useMutation` calling `apiPost("/api/items/${itemId}/link", { globalItemId })`. On success, invalidate `["items"]` and `["global-items"]` query keys.
|
|
|
|
4. `useUnlinkItem()` — `useMutation` calling `apiDelete("/api/items/${itemId}/link")`. On success, invalidate same keys.
|
|
|
|
**GlobalItemCard.tsx**: Create at `src/client/components/GlobalItemCard.tsx`. Card displaying brand, model, category badge, weight (formatted as g/kg), price (formatted from cents). Links to `/global-items/${item.id}` detail page. Show image thumbnail if imageUrl exists. Light/airy Tailwind styling matching existing collection cards.
|
|
|
|
**global-items/index.tsx**: Catalog browse/search page.
|
|
- Search input at top with placeholder "Search gear by brand or model..."
|
|
- Debounce input by 300ms before passing to `useGlobalItems(debouncedQuery)`
|
|
- Grid of GlobalItemCard components (responsive: 1 col mobile, 2 cols md, 3 cols lg)
|
|
- Loading skeleton while fetching
|
|
- Empty state: "No items found" or "Search the global gear catalog"
|
|
- TanStack Router: `createFileRoute("/global-items/")` with component export
|
|
|
|
**global-items/$globalItemId.tsx**: Detail page.
|
|
- TanStack Router: `createFileRoute("/global-items/$globalItemId")` with params loader
|
|
- Fetch single item with `useGlobalItem(Number(globalItemId))`
|
|
- Display: brand, model, category, weight, price, description, image (full size)
|
|
- Show owner count badge: "{N} users own this" or "Be the first to add this"
|
|
- Back link to catalog
|
|
</action>
|
|
<verify>
|
|
<automated>bun run lint 2>&1 | tail -5</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- grep -q "useGlobalItems" src/client/hooks/useGlobalItems.ts
|
|
- grep -q "useGlobalItem" src/client/hooks/useGlobalItems.ts
|
|
- grep -q "useLinkItem" src/client/hooks/useGlobalItems.ts
|
|
- test -f src/client/routes/global-items/index.tsx
|
|
- test -f "src/client/routes/global-items/\$globalItemId.tsx"
|
|
- test -f src/client/components/GlobalItemCard.tsx
|
|
</acceptance_criteria>
|
|
<done>Global catalog page shows searchable grid of items. Detail page shows specs, image, and owner count. Hooks handle all data fetching and mutations. Lint passes.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Link-to-global-item UI in collection</name>
|
|
<files>src/client/components/LinkToGlobalItem.tsx</files>
|
|
<read_first>src/client/routes/collection/index.tsx, src/client/hooks/useGlobalItems.ts</read_first>
|
|
<action>
|
|
**LinkToGlobalItem.tsx**: Create at `src/client/components/LinkToGlobalItem.tsx`. Per D-04 and D-18.
|
|
|
|
A component that allows linking a user's personal item to a global catalog entry. Design as a small modal/popover or inline search:
|
|
|
|
1. Trigger: A "Link to catalog" button shown on item detail or edit view. If already linked, show "Linked to {brand} {model}" with an unlink option.
|
|
2. When triggered, show a search input that calls `useGlobalItems(query)` with debounce.
|
|
3. Display matching global items as clickable options.
|
|
4. On select, call `useLinkItem()` mutation with itemId and globalItemId.
|
|
5. Show success state: linked item name with a link to the global item detail page.
|
|
6. Unlink: If already linked, show the linked global item with an "Unlink" button that calls `useUnlinkItem()`.
|
|
|
|
Keep it simple — a dropdown/combobox pattern works well. Use Tailwind for styling. Match the light/airy aesthetic of existing components.
|
|
|
|
Wire this component into the item edit form or item detail view at the appropriate place (after the existing form fields, or as a separate section below item details).
|
|
</action>
|
|
<verify>
|
|
<automated>bun run lint 2>&1 | tail -5</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- grep -q "LinkToGlobalItem" src/client/components/LinkToGlobalItem.tsx
|
|
- grep -q "useLinkItem\|linkItem" src/client/components/LinkToGlobalItem.tsx
|
|
- grep -q "useUnlinkItem\|unlinkItem" src/client/components/LinkToGlobalItem.tsx
|
|
</acceptance_criteria>
|
|
<done>Users can search the global catalog from within their item view, link/unlink their item, and see the current link status. Lint passes.</done>
|
|
</task>
|
|
|
|
<task type="checkpoint:human-verify" gate="blocking">
|
|
<name>Task 3: Verify global catalog UI</name>
|
|
<files>none</files>
|
|
<action>
|
|
Human verification of the global item catalog UI. Review what was built: browse page with search, detail page with owner count, and link/unlink from collection items.
|
|
|
|
Steps to verify:
|
|
1. Start dev server: `bun run dev`
|
|
2. Navigate to `/global-items` — should see catalog with seed items in a grid
|
|
3. Type "revelate" in search — should filter to matching items
|
|
4. Click a global item — detail page shows brand, model, specs, "0 users own this"
|
|
5. Go to your collection, open an item, find "Link to catalog" control
|
|
6. Search for a global item and link it — should show linked status
|
|
7. Return to global item detail — owner count should now show "1 user owns this"
|
|
8. Unlink the item — owner count returns to 0
|
|
</action>
|
|
<verify>
|
|
<automated>bun run build 2>&1 | tail -3</automated>
|
|
</verify>
|
|
<done>User approves global catalog UI: search works, detail page shows owner count, link/unlink flow is functional.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- `bun run lint` passes
|
|
- `bun run build` succeeds (client builds with new routes)
|
|
- Visual verification of catalog page, search, detail page, and link/unlink flow
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
Global catalog is browsable and searchable. Item detail shows owner count. Users can link/unlink personal items to global entries. All pages render correctly with Tailwind styling.
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/18-global-items-public-profiles/18-04-SUMMARY.md`
|
|
</output>
|