---
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"
---
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
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
@.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
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)
Task 1: Profile hooks and profile edit UI
src/client/hooks/useProfile.ts, src/client/components/ProfileSection.tsx, src/client/routes/settings.tsx
src/client/routes/settings.tsx, src/client/hooks/useItems.ts, src/client/lib/api.ts
**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 ``. The section should have a heading "Profile" with a brief description "Your public profile information."
bun run lint 2>&1 | tail -5
- 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
Profile edit section in settings page with display name, bio, and avatar upload. Hooks handle fetch and mutation. Form saves correctly.
Task 2: Public profile page and setup visibility toggle
src/client/routes/users/$userId.tsx, src/client/components/PublicSetupCard.tsx, src/client/routes/setups/index.tsx
src/client/routes/setups/index.tsx, src/client/hooks/useProfile.ts
**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)
bun run lint 2>&1 | tail -5
- 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
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.
Task 3: Verify profiles and public sharing UI
none
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
bun run build 2>&1 | tail -3
User approves profiles and sharing UI: profile edit works, public profile shows correct data, setup toggle works, unauthenticated access functions correctly.
- `bun run lint` passes
- `bun run build` succeeds
- Visual verification: profile edit, public profile page, setup toggle, and public access
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.