Files
GearBox/.planning/phases/18-global-items-public-profiles/18-05-PLAN.md

206 lines
9.5 KiB
Markdown

---
phase: 18-global-items-public-profiles
plan: 05
type: execute
wave: 3
depends_on: ["18-03"]
files_modified:
- src/client/hooks/useProfile.ts
- src/client/routes/users/$userId.tsx
- src/client/routes/settings.tsx
- src/client/routes/setups/index.tsx
- src/client/components/ProfileSection.tsx
- src/client/components/PublicSetupCard.tsx
autonomous: false
requirements: [PROF-01, PROF-02, PROF-03, PROF-04, PROF-05]
must_haves:
truths:
- "User can edit display name, avatar, and bio in settings page"
- "Public profile page at /users/:id shows display name, avatar, bio, and public setups"
- "Public profile page works without login (no auth required)"
- "User can toggle a setup between public and private in the setup detail/edit view"
- "Public setups appear on the owner's profile page; private ones do not"
artifacts:
- path: "src/client/hooks/useProfile.ts"
provides: "usePublicProfile, useUpdateProfile hooks"
exports: ["usePublicProfile", "useUpdateProfile"]
- path: "src/client/routes/users/$userId.tsx"
provides: "Public profile page"
min_lines: 40
- path: "src/client/components/ProfileSection.tsx"
provides: "Profile edit form within settings"
min_lines: 30
- path: "src/client/components/PublicSetupCard.tsx"
provides: "Card for setup shown on public profile"
min_lines: 15
key_links:
- from: "src/client/routes/users/$userId.tsx"
to: "src/client/hooks/useProfile.ts"
via: "usePublicProfile hook"
pattern: "usePublicProfile"
- from: "src/client/hooks/useProfile.ts"
to: "/api/users/:id/profile"
via: "apiGet fetch"
pattern: "apiGet.*users.*profile"
- from: "src/client/routes/settings.tsx"
to: "src/client/components/ProfileSection.tsx"
via: "component import"
pattern: "ProfileSection"
---
<objective>
Build the user profile and public sharing client: profile edit section in settings, public profile page, setup visibility toggle, and public setup cards.
Purpose: Delivers the client-side experience for PROF-01 (profile edit), PROF-02 (public profile), PROF-03 (setup toggle), PROF-04 (public setup view), PROF-05 (profile lists public setups).
Output: useProfile hook, public profile page, ProfileSection component, PublicSetupCard, updated settings and setup views
</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-03-SUMMARY.md
@src/client/routes/settings.tsx
@src/client/routes/setups/index.tsx
@src/client/hooks/useItems.ts
@src/client/lib/api.ts
<interfaces>
<!-- API endpoints from Plan 03: -->
PUT /api/auth/profile { displayName?, avatarUrl?, bio? } -> updated user (auth required)
GET /api/users/:id/profile -> { id, displayName, avatarUrl, bio, setups: [{ id, name, createdAt }] } (no auth)
GET /api/setups/:id/public -> { id, name, isPublic, items: [...], totalWeight, totalCost } (no auth, 404 if private)
<!-- Setup now includes isPublic in responses from Plan 03 -->
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Profile hooks and profile edit UI</name>
<files>src/client/hooks/useProfile.ts, src/client/components/ProfileSection.tsx, src/client/routes/settings.tsx</files>
<read_first>src/client/routes/settings.tsx, src/client/hooks/useItems.ts, src/client/lib/api.ts</read_first>
<action>
**useProfile.ts** hook: Create at `src/client/hooks/useProfile.ts`.
1. `usePublicProfile(userId: number | null)``useQuery` with key `["profiles", userId]`, fetches `apiGet("/api/users/${userId}/profile")`, `enabled: userId != null`.
2. `useUpdateProfile()``useMutation` calling `apiPut("/api/auth/profile", data)`. On success, invalidate `["profiles"]` query key. Return mutation.
**ProfileSection.tsx**: Create at `src/client/components/ProfileSection.tsx`. Per D-09.
A form section that contains:
- Display name text input (max 100 chars) with label
- Bio textarea (max 500 chars) with character counter
- Avatar: Show current avatar if set, with a "Change avatar" button that opens the existing ImageUpload component (per D-11, reuse existing image upload + MinIO storage). After upload, set avatarUrl to the returned filename (the route will handle presigned URL generation).
- Save button calling `useUpdateProfile()` mutation
- Success/error toast feedback (use existing toast pattern if available, otherwise simple inline message)
Pre-populate form with current profile data. On mount, fetch current user profile via an appropriate mechanism (could be from auth context or a dedicated endpoint).
**settings.tsx**: Read the existing settings page. Add a "Profile" section at the top (before API Keys and other settings). Import and render `<ProfileSection />`. The section should have a heading "Profile" with a brief description "Your public profile information."
</action>
<verify>
<automated>bun run lint 2>&1 | tail -5</automated>
</verify>
<acceptance_criteria>
- grep -q "usePublicProfile" src/client/hooks/useProfile.ts
- grep -q "useUpdateProfile" src/client/hooks/useProfile.ts
- grep -q "ProfileSection" src/client/components/ProfileSection.tsx
- grep -q "ProfileSection" src/client/routes/settings.tsx
</acceptance_criteria>
<done>Profile edit section in settings page with display name, bio, and avatar upload. Hooks handle fetch and mutation. Form saves correctly.</done>
</task>
<task type="auto">
<name>Task 2: Public profile page and setup visibility toggle</name>
<files>src/client/routes/users/$userId.tsx, src/client/components/PublicSetupCard.tsx, src/client/routes/setups/index.tsx</files>
<read_first>src/client/routes/setups/index.tsx, src/client/hooks/useProfile.ts</read_first>
<action>
**users/$userId.tsx**: Create at `src/client/routes/users/$userId.tsx`. Per D-10.
Public profile page (no auth required to view):
- TanStack Router: `createFileRoute("/users/$userId")` with params
- Fetch profile with `usePublicProfile(Number(userId))`
- Layout: Avatar (or placeholder icon), display name (or "User #{id}" fallback), bio text
- Below profile: "Public Setups" heading with grid of PublicSetupCard components
- Empty state if no public setups: "No public setups yet"
- Loading skeleton while fetching
- 404 handling if user not found
**PublicSetupCard.tsx**: Create at `src/client/components/PublicSetupCard.tsx`.
A card for setups shown on the public profile:
- Setup name as heading
- Created date formatted
- Links to `/setups/${id}/public` for the public view (or you can create an inline expandable view)
- Light card styling with subtle border/shadow, matching existing setup cards
**setups/index.tsx or setup detail**: Update the setup list or detail view to include the isPublic toggle per D-14.
- In the setup detail/edit view, add a toggle switch or checkbox labeled "Public" next to the setup name
- When toggled, call the existing setup update mutation with `isPublic: true/false`
- Show a small icon or badge on the setup list indicating public status (e.g., a globe icon or "Public" chip)
- Default all existing setups to show as private (per D-12)
</action>
<verify>
<automated>bun run lint 2>&1 | tail -5</automated>
</verify>
<acceptance_criteria>
- test -f "src/client/routes/users/\$userId.tsx"
- grep -q "usePublicProfile" "src/client/routes/users/\$userId.tsx"
- test -f src/client/components/PublicSetupCard.tsx
- grep -q "isPublic\|public" src/client/routes/setups/index.tsx
</acceptance_criteria>
<done>Public profile page shows user info and public setups. Setup detail has visibility toggle. Public setups appear on profile. Private setups are hidden from profile.</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<name>Task 3: Verify profiles and public sharing UI</name>
<files>none</files>
<action>
Human verification of user profiles and public sharing. Review what was built: profile edit in settings, public profile page, setup visibility toggle.
Steps to verify:
1. Start dev server: `bun run dev`
2. Go to Settings — should see a new "Profile" section at top
3. Enter a display name and bio, save — should show success
4. Upload an avatar image — should display
5. Go to Setups, open a setup detail, find the "Public" toggle
6. Toggle a setup to public
7. Navigate to `/users/{your-user-id}` — should see profile with the public setup listed
8. Open an incognito/private window (no auth)
9. Visit the same `/users/{id}` URL — should show profile and public setup without login
10. Toggle the setup back to private — it should disappear from the profile page
</action>
<verify>
<automated>bun run build 2>&1 | tail -3</automated>
</verify>
<done>User approves profiles and sharing UI: profile edit works, public profile shows correct data, setup toggle works, unauthenticated access functions correctly.</done>
</task>
</tasks>
<verification>
- `bun run lint` passes
- `bun run build` succeeds
- Visual verification: profile edit, public profile page, setup toggle, and public access
</verification>
<success_criteria>
Profile can be edited in settings. Public profile page works without auth. Setup visibility toggle works. Public setups appear on profile, private ones don't. Avatar upload uses existing image infrastructure.
</success_criteria>
<output>
After completion, create `.planning/phases/18-global-items-public-profiles/18-05-SUMMARY.md`
</output>