feat: support parent and wallet profiles#15
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull Request Overview
This PR rebuilds the identity schema to support a parent profile (email-based) with multiple wallet profiles (one per connected wallet). The migration adds new database tables for profiles, credentials, and Cubid assignments with appropriate RLS policies. The frontend refactors from a single user profile model to a parent/wallet profile architecture with active wallet selection.
Key changes:
- New database schema with
profiles,profile_credentials, andprofiles_cubidtables - Frontend state management updated to track parent profile, wallet profiles array, and active wallet
- All UI components and pages updated to work with the new multi-profile model
Reviewed Changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| supabase/migrations/20251110000100_rebuild_identity_schema_from_scratch.sql | Defines new identity schema with parent/wallet profiles, credentials, Cubid timeline, views, RLS policies, and rollback logic |
| frontend/src/lib/store.ts | Replaces single user profile with parent profile, wallet profiles array, active wallet ID, and address normalization |
| frontend/src/lib/profile.ts | Adds functions to fetch profile bundles, create wallet profiles, and update wallet details |
| frontend/src/lib/onboarding.ts | Updates onboarding checks to validate wallet profiles instead of single user |
| frontend/src/components/UserSessionSummary.tsx | Displays active wallet profile info with fallback for empty wallet list |
| frontend/src/components/AuthProvider.tsx | Fetches and sets parent and wallet profiles on auth state changes |
| frontend/src/components/AppHeader.tsx | Shows active wallet profile display name and photo |
| frontend/src/app/vouch/page.tsx | Uses active wallet profile address for vouching |
| frontend/src/app/signin/page.tsx | Checks wallet profiles for onboarding completion |
| frontend/src/app/scan/my-qr/page.tsx | Generates QR with active wallet profile's Cubid ID |
| frontend/src/app/scan/camera/page.tsx | Uses active wallet profile for handshake completion |
| frontend/src/app/profile/page.tsx | Displays linked wallets grid and form to create new wallet profiles |
| frontend/src/app/page.tsx | Shows active wallet profile name on home page |
| frontend/src/app/new-user/page.tsx | Creates wallet profile during onboarding instead of updating user |
| frontend/src/app/circle/page.tsx | Fetches circle data using active wallet address |
| frontend/tests/*.test.tsx | Updates test fixtures to use new profile structure |
| frontend/tests/profile.test.ts | Rewrites tests for new profile service functions |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
frontend/src/lib/profile.ts
Outdated
| return null; | ||
|
|
||
| const createdRow = Array.isArray(created) ? created[0] : created; | ||
| const profileId: string | undefined = createdRow?.id ?? createdRow?.profile_id; |
There was a problem hiding this comment.
The code attempts to handle both id and profile_id fields from the RPC response, but the SQL function create_profile_with_credential returns public.profiles type which only has an id field. The fallback to profile_id appears unnecessary and could cause confusion. Consider removing createdRow?.profile_id from line 149.
| const profileId: string | undefined = createdRow?.id ?? createdRow?.profile_id; | |
| const profileId: string | undefined = createdRow?.id; |
frontend/src/lib/store.ts
Outdated
| setParentProfile: (profile) => | ||
| set((state) => ({ | ||
| parentProfile: profile, | ||
| // Preserve active wallet when clearing parent profile |
There was a problem hiding this comment.
The comment states 'Preserve active wallet when clearing parent profile' but the code actually clears the active wallet when the parent profile is null. The comment should read 'Clear active wallet when clearing parent profile' to match the implementation.
| // Preserve active wallet when clearing parent profile | |
| // Clear active wallet when clearing parent profile |
frontend/src/app/new-user/page.tsx
Outdated
| setError("Cubid ID must match cubid_[a-z0-9]{4,32}"); | ||
| return; | ||
| } | ||
| if (!walletProfiles.length && !walletAddress) { |
There was a problem hiding this comment.
The condition checks both !walletProfiles.length AND !walletAddress, but a user could have a connected wallet (walletAddress is set) without a saved profile yet. This would allow submission before the profile is actually created. The condition should be if (!walletProfiles.length) to ensure at least one wallet profile exists in the database.
| if (!walletProfiles.length && !walletAddress) { | |
| if (!walletProfiles.length) { |
| if (!isMounted) return; | ||
| setSession(session); | ||
| if (session) { | ||
| const profile = await fetchMyProfile(); | ||
| const bundle = await fetchMyProfiles(); | ||
| if (!isMounted) return; | ||
| setUser(profile); | ||
| setParentProfile(bundle.parent); | ||
| setWalletProfiles(bundle.wallets); |
There was a problem hiding this comment.
The session is set before checking if the component is still mounted after the async fetchMyProfiles() call. If the component unmounts between setting the session and fetching profiles, the session will be set but profiles won't be. Consider moving setSession(session) after the mounted check on line 31, or wrapping all state updates in a single check.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| setSession(session); | ||
| if (session) { | ||
| try { | ||
| const profile = await fetchMyProfile(); | ||
| setUser(profile); | ||
| const bundle = await fetchMyProfiles(); | ||
| setParentProfile(bundle.parent); | ||
| setWalletProfiles(bundle.wallets); | ||
| } catch (error) { | ||
| console.error("Failed to fetch Supabase profile", error); | ||
| setUser(null); | ||
| console.error("Failed to fetch Supabase profiles", error); | ||
| setParentProfile(null); | ||
| setWalletProfiles([]); | ||
| } | ||
| } else { | ||
| setUser(null); | ||
| setParentProfile(null); | ||
| setWalletProfiles([]); | ||
| } | ||
| setInitialised(true); |
There was a problem hiding this comment.
Clear wallet state on sign‑out
When the session is cleared, the provider only nulls the parent and wallet profile lists but leaves walletAddress untouched in the zustand store. Because setWalletProfiles([]) does not modify walletAddress, the previously connected wallet persists after logout and across subsequent logins. This stale address lets a fresh account skip the wallet-connection portion of onboarding (the New User page treats a non‑empty walletAddress as already connected) and can display or reuse the wrong wallet for the next user. The sign‑out paths should also call reset() or setWalletAddress(null) to avoid leaking wallet state between sessions.
Useful? React with 👍 / 👎.
Summary
Testing
Codex Task