Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds an InviteService, async invite code generation, and useUserInvite; signUp now returns userId and upserts profiles; onboarding invite UI replaced with data-driven flows and loading/error branches; several hooks, components, config, and permission updates across the frontend. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant AuthScreen as Auth Screen
participant AuthHook as useAuth / signUp
participant Supabase
participant InviteScreen as Invite Screen
participant InviteHook as useUserInvite
participant InviteService
participant FriendService
User->>AuthScreen: Submit signup (email, password)
AuthScreen->>AuthHook: signUp(...)
AuthHook->>Supabase: Create auth user
Supabase-->>AuthHook: Return user record (id)
AuthHook->>Supabase: Upsert profile (TABLES.PROFILES)
Supabase-->>AuthHook: Profile upsert result
AuthHook-->>AuthScreen: Return { error:null, data:{ userId } }
AuthScreen->>InviteScreen: Navigate with user_id
InviteScreen->>InviteHook: useUserInvite(userId)
InviteHook->>InviteService: getInvite(userId)
InviteService->>Supabase: Query invites by inviter_id
Supabase-->>InviteService: Invite record
InviteService-->>InviteHook: Invite data
InviteHook-->>InviteScreen: { invite, isLoading:false }
InviteScreen->>User: Render invite link / Share / Continue
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (1)
🧰 Additional context used🧬 Code graph analysis (1)frontend/components/friends/suggested-friends-list.tsx (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
frontend/providers/auth-provider.tsx (1)
15-23: AlignupdateProfiletype with the newprofiles.Updateshape (avoid misleading callers).
Right now the context advertisesPartial<Profile>which implies fields that shouldn’t be writable; butuseProfile.updateProfileis (correctly) narrower.Suggested fix:
import { Database } from '@/types/database'; type Profile = Database['public']['Tables']['profiles']['Row']; +type ProfileUpdate = Database['public']['Tables']['profiles']['Update']; interface AuthContextType { ... - updateProfile: (updates: Partial<Profile>) => Promise<{ error: Error | null }>; + updateProfile: (updates: Partial<ProfileUpdate>) => Promise<{ error: Error | null }>; refreshProfile: () => Promise<void>; }frontend/app/onboarding/auth.tsx (1)
40-55: Remove debug logging from sign-in error path.
console.log(error,"errorrr")is likely leftover and can leak details into device logs.const { error } = await signIn(email, password); if (error) { - console.log(error,"errorrr") // Handle custom error types with specific messages if (error instanceof EmailNotVerifiedError) {frontend/hooks/use-auth.ts (2)
86-93: Don’t log fullsessionobjects (token leakage).
console.log('Auth state changed:', event, session);can leak access/refresh tokens into device logs.const { data: { subscription } } = supabase.auth.onAuthStateChange( async (event, session) => { - console.log('Auth state changed:', event, session); + console.log('Auth state changed:', event); setSession(session);
98-148: Guarddata.user.emailand handle auth session state in signup flow.The non-null assertion on
data.user.email!is unsafe. Supabase can return a user object without an email (especially in email-confirmation flows), and without a verified session, RLS policies will fail on the profile upsert and any subsequent invite queries.Recommended changes:
- if (!data.user) return { error: new Error('User not found') }; + if (!data.user) return { error: new Error('User not found') }; + if (!data.user.email) return { error: new Error('Email is required') }; const userId = data.user.id; try { const profileData = { id: userId, - email: data.user.email!, + email: data.user.email, full_name: userData?.full_name || null, username: userData?.username || null, avatar_url: userData?.avatar_url || null, bio: userData?.bio || null, };Alternatively, if email may legitimately be absent during email-confirmation flows, allow null and ensure downstream invite-fetch logic handles the case where profile creation failed or was incomplete due to missing session authorization.
🧹 Nitpick comments (3)
frontend/services/device-storage.ts (1)
165-166: 7‑day TTL looks correct; consider makingexpirationMinutes === 0explicit insetItem.This change correctly caches
suggested_friendsfor60 * 24 * 7minutes. Minor footgun:setItemuses a truthy check (expirationMinutes ? ...) so a future caller passing0won’t expire immediately (it will store with no expiry). ConsiderexpirationMinutes != null(ortypeof expirationMinutes === 'number') insetItemto avoid surprising behavior.frontend/hooks/use-profile.ts (1)
8-16: Good tightening of update typing (ProfileUpdate)—but keep table refs consistent.
updateProfilecorrectly targetsTABLES.PROFILES, but the “create profile” path still inserts into hard-coded'profiles'.- const { data: newProfile, error: createError } = await supabase - .from('profiles') + const { data: newProfile, error: createError } = await supabase + .from(TABLES.PROFILES) .insert(profileData as never) .select() .single();Also applies to: 85-107
frontend/services/invite-service.ts (1)
42-50: RestrictupdateInviteto mutable columns.
Partial<Invite>allows callers to attempt updates to immutable fields (e.g.,id,inviter_id,created_at) and can create accidental corruption if RLS permits it. Prefer a narrowInviteUpdatetype (e.g., onlymessage,max_uses,current_uses,is_active).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
frontend/bun.lockis excluded by!**/*.lock
📒 Files selected for processing (16)
frontend/app.json(2 hunks)frontend/app/onboarding/auth.tsx(2 hunks)frontend/app/onboarding/invite.tsx(3 hunks)frontend/app/settings/index.tsx(2 hunks)frontend/components/friends-section.tsx(3 hunks)frontend/components/friends/suggested-friend-item.tsx(2 hunks)frontend/eas.json(1 hunks)frontend/hooks/use-auth.ts(3 hunks)frontend/hooks/use-friends.ts(1 hunks)frontend/hooks/use-invite-acceptance.ts(2 hunks)frontend/hooks/use-profile.ts(2 hunks)frontend/hooks/use-user-invite.ts(1 hunks)frontend/package.json(2 hunks)frontend/providers/auth-provider.tsx(1 hunks)frontend/services/device-storage.ts(1 hunks)frontend/services/invite-service.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
frontend/hooks/use-user-invite.ts (2)
frontend/types/database.ts (1)
Database(11-244)frontend/services/invite-service.ts (1)
InviteService(7-51)
frontend/hooks/use-friends.ts (2)
frontend/lib/supabase.ts (1)
supabase(14-30)frontend/constants/supabase.ts (1)
TABLES(4-13)
frontend/hooks/use-invite-acceptance.ts (1)
frontend/constants/supabase.ts (1)
FRIENDSHIP_STATUS(29-33)
frontend/components/friends/suggested-friend-item.tsx (4)
frontend/providers/auth-provider.tsx (1)
useAuthContext(69-75)frontend/services/friend-service.ts (1)
sendFriendRequest(90-99)frontend/hooks/use-invite-acceptance.ts (1)
useInviteAcceptance(34-183)frontend/hooks/use-toast.ts (1)
useToast(3-11)
frontend/app/onboarding/invite.tsx (3)
frontend/hooks/use-user-invite.ts (1)
useUserInvite(14-32)frontend/lib/utils.ts (1)
generateDeepLinkUrl(54-65)frontend/services/invite-service.ts (1)
InviteService(7-51)
frontend/hooks/use-profile.ts (1)
frontend/constants/supabase.ts (1)
TABLES(4-13)
🔇 Additional comments (7)
frontend/package.json (2)
2-2: Name change is fine.
No concerns with renaming the package.
65-65: No action needed.expo-updates@~29.0.14is compatible withexpo@54. The Expo SDK 54 compatibility matrix specifies expo-updates ~29.0.12 as the bundled version, with ~29.0.15 available on the latest docs. The pinned version ~29.0.14 is within the correct 29.0.x range and poses no compatibility risk.frontend/app/settings/index.tsx (1)
107-109: Improved UX copy for missing auth token.
The new message is clearer and less “internal error”-ish.frontend/hooks/use-invite-acceptance.ts (1)
41-48: Good guardrails on invite acceptance inputs.
The missing-id and self-invite checks prevent bad friendships from being created.frontend/components/friends-section.tsx (1)
65-73: Nice UX improvement: separate Connected vs Pending counts.
Only minor thing: ifBadgeexpectsstring, wrap withString(...); otherwise LGTM.Also applies to: 89-97, 116-119
frontend/app/onboarding/auth.tsx (1)
69-95: Signup-to-invite navigation looks consistent with the newuser_idparam.
Thedata?.userIdguard + fallback prevents hard failure if signup returns without a user id.frontend/app.json (1)
140-146: Expo updates config addition is fine—verify it matches the intended EAS/updates setup.
Just ensure the runtimeVersion policy and updates URL align with how you’re publishing updates for this project.
| export default function SuggestedFriendItem({ friend, onAccept, index = 0 }: FriendItemProps) { | ||
| const handleAccept = () => {} | ||
| const { profile } = useAuthContext(); | ||
| const { acceptInvite: sendFriendRequest } = useInviteAcceptance(); | ||
| const { toast: showToast } = useToast(); | ||
|
|
||
| const handleAccept = async () => { | ||
| if (!profile?.id) { | ||
| return showToast('Please login to send a friend request', 'error'); | ||
| } | ||
| const result = await sendFriendRequest(friend.id, profile.id); | ||
| if (result.success) { | ||
| showToast('Friend request sent', 'success'); | ||
| } else { | ||
| showToast(result.message || 'Failed to send friend request', 'error'); | ||
| } | ||
| } |
There was a problem hiding this comment.
Guard against thrown errors + prevent duplicate sends; consider wiring onAccept.
Wrap sendFriendRequest in try/catch and (if available from useInviteAcceptance) use an isProcessing flag to disable the button; also call onAccept?.(result.friendId) on success if the hook returns it.
export default function SuggestedFriendItem({ friend, onAccept, index = 0 }: FriendItemProps) {
const { profile } = useAuthContext();
- const { acceptInvite: sendFriendRequest } = useInviteAcceptance();
+ const { acceptInvite: sendFriendRequest, isProcessing } = useInviteAcceptance();
const { toast: showToast } = useToast();
const handleAccept = async () => {
if (!profile?.id) {
return showToast('Please login to send a friend request', 'error');
}
- const result = await sendFriendRequest(friend.id, profile.id);
- if (result.success) {
- showToast('Friend request sent', 'success');
- } else {
- showToast(result.message || 'Failed to send friend request', 'error');
- }
- }
+ try {
+ const result = await sendFriendRequest(friend.id, profile.id);
+ if (result.success) {
+ showToast('Friend request sent', 'success');
+ if (result.friendId) onAccept?.(result.friendId);
+ } else {
+ showToast(result.message || 'Failed to send friend request', 'error');
+ }
+ } catch {
+ showToast('Failed to send friend request', 'error');
+ }
+ };
return (
...
<TouchableOpacity
style={styles.addButton}
onPress={handleAccept}
+ disabled={isProcessing}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export default function SuggestedFriendItem({ friend, onAccept, index = 0 }: FriendItemProps) { | |
| const handleAccept = () => {} | |
| const { profile } = useAuthContext(); | |
| const { acceptInvite: sendFriendRequest } = useInviteAcceptance(); | |
| const { toast: showToast } = useToast(); | |
| const handleAccept = async () => { | |
| if (!profile?.id) { | |
| return showToast('Please login to send a friend request', 'error'); | |
| } | |
| const result = await sendFriendRequest(friend.id, profile.id); | |
| if (result.success) { | |
| showToast('Friend request sent', 'success'); | |
| } else { | |
| showToast(result.message || 'Failed to send friend request', 'error'); | |
| } | |
| } | |
| export default function SuggestedFriendItem({ friend, onAccept, index = 0 }: FriendItemProps) { | |
| const { profile } = useAuthContext(); | |
| const { acceptInvite: sendFriendRequest, isProcessing } = useInviteAcceptance(); | |
| const { toast: showToast } = useToast(); | |
| const handleAccept = async () => { | |
| if (!profile?.id) { | |
| return showToast('Please login to send a friend request', 'error'); | |
| } | |
| try { | |
| const result = await sendFriendRequest(friend.id, profile.id); | |
| if (result.success) { | |
| showToast('Friend request sent', 'success'); | |
| if (result.friendId) onAccept?.(result.friendId); | |
| } else { | |
| showToast(result.message || 'Failed to send friend request', 'error'); | |
| } | |
| } catch { | |
| showToast('Failed to send friend request', 'error'); | |
| } | |
| }; | |
| return ( | |
| ... | |
| <TouchableOpacity | |
| style={styles.addButton} | |
| onPress={handleAccept} | |
| disabled={isProcessing} | |
| hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} | |
| > |
| "development": { | ||
| "developmentClient": true, | ||
| "distribution": "internal" | ||
| "distribution": "internal", | ||
| "channel": "development" | ||
| }, | ||
| "preview": { | ||
| "distribution": "internal" | ||
| "distribution": "internal", | ||
| "channel": "preview" | ||
| }, | ||
| "production": { | ||
| "autoIncrement": true | ||
| "autoIncrement": true, | ||
| "channel": "production" | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
git ls-files | head -20Repository: fortune710/keepsafe
Length of output: 590
🏁 Script executed:
# Look for app.json and eas.json in the repository
fd -t f "app\.json|eas\.json|package\.json" --max-one-result-per-directory | head -20Repository: fortune710/keepsafe
Length of output: 310
🏁 Script executed:
# Search for CI/release workflow files
fd -t f "\.github|workflows|\.yml|\.yaml" | grep -E "(github|workflow|CI|release)" | head -20Repository: fortune710/keepsafe
Length of output: 45
🏁 Script executed:
# Look for eas update commands in the codebase
rg "eas update" --type js --type json --type yaml -A 2 -B 2Repository: fortune710/keepsafe
Length of output: 45
🏁 Script executed:
# Check if frontend directory exists
ls -la frontend/ 2>&1 || echo "frontend directory not found"Repository: fortune710/keepsafe
Length of output: 1663
🏁 Script executed:
# Search more broadly for the files mentioned in the review
find . -name "app.json" -o -name "eas.json" -o -name "package.json" 2>/dev/null | head -30Repository: fortune710/keepsafe
Length of output: 126
🏁 Script executed:
# Look for any frontend-related files
find . -type d -name "frontend" 2>/dev/nullRepository: fortune710/keepsafe
Length of output: 73
🏁 Script executed:
cat -n eas.jsonRepository: fortune710/keepsafe
Length of output: 103
🏁 Script executed:
cat -n app.json | head -80Repository: fortune710/keepsafe
Length of output: 103
🏁 Script executed:
# Check package.json for build and release scripts
cat package.json | jq '.scripts' 2>/dev/null || grep -A 20 '"scripts"' package.jsonRepository: fortune710/keepsafe
Length of output: 107
🏁 Script executed:
# Look for GitHub Actions or CI workflows
find .github -type f 2>/dev/null || echo "No .github directory found"Repository: fortune710/keepsafe
Length of output: 89
🏁 Script executed:
# Search for any update or publish scripts
rg "eas.*update|publish.*update" --type json --type js --type yamlRepository: fortune710/keepsafe
Length of output: 45
🏁 Script executed:
cat -n ./frontend/eas.jsonRepository: fortune710/keepsafe
Length of output: 665
🏁 Script executed:
cat -n ./frontend/app.json | head -100Repository: fortune710/keepsafe
Length of output: 3175
🏁 Script executed:
cat ./frontend/package.json | jq '.scripts' 2>/dev/null || sed -n '/"scripts":/,/^ }/p' ./frontend/package.jsonRepository: fortune710/keepsafe
Length of output: 436
🏁 Script executed:
# Search for eas update or release commands in frontend
rg "eas.*update|release|publish" ./frontend --type json --type jsRepository: fortune710/keepsafe
Length of output: 310
🏁 Script executed:
cat ./frontend/app.json | jq '.expo | {runtimeVersion, updates}' 2>/dev/null || rg "runtimeVersion|updates" ./frontend/app.jsonRepository: fortune710/keepsafe
Length of output: 208
🏁 Script executed:
# Search the entire frontend for runtimeVersion or updates config
rg "runtimeVersion|\"updates\"|updates:" ./frontend --type jsonRepository: fortune710/keepsafe
Length of output: 143
🏁 Script executed:
# Check if there are any CI or release scripts elsewhere
find ./frontend -type f \( -name "*.yml" -o -name "*.yaml" -o -name "Makefile" -o -name "*.sh" \) 2>/dev/nullRepository: fortune710/keepsafe
Length of output: 45
EAS channels are configured but lack update publishing workflow.
Your eas.json defines build channels (development/preview/production) and app.json has runtimeVersion and updates.url configured, but there is no CI/automation to publish updates to these channels. Add eas update commands to your release workflow or build scripts to distribute updates to the appropriate channel matching your app version.
🤖 Prompt for AI Agents
In frontend/eas.json around lines 7 to 19, you’ve defined
development/preview/production channels but haven’t automated publishing updates
to those channels; add CI or npm script steps that run EAS Update after a
successful build (e.g., run an EAS CLI update command targeted to the matching
channel/branch for each pipeline: development -> development, preview ->
preview, production -> production), ensure the command uses the same
runtimeVersion/updates.url configured in app.json, and wire these commands into
your release workflows (GitHub Actions/GitLab CI/whatever) so updates are
published automatically after a release build.
| interface UseUserInviteResult { | ||
| invite: Invite | undefined; | ||
| isLoading: boolean; | ||
| isError: boolean; | ||
| error: unknown; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n frontend/hooks/use-user-invite.tsRepository: fortune710/keepsafe
Length of output: 1069
🏁 Script executed:
cat package.json | grep -i "react-query\|@tanstack"Repository: fortune710/keepsafe
Length of output: 107
🏁 Script executed:
fd package.jsonRepository: fortune710/keepsafe
Length of output: 84
🏁 Script executed:
head -50 package.jsonRepository: fortune710/keepsafe
Length of output: 134
🏁 Script executed:
cat frontend/package.json | grep -A 2 -B 2 "@tanstack\|react-query"Repository: fortune710/keepsafe
Length of output: 240
🌐 Web query:
React Query v5 isPending vs isLoading deprecation
💡 Result:
Short answer: In v5 React Query (TanStack Query) the old "loading" terminology was renamed to "pending" — the derived flag isLoading was renamed to isPending, isInitialLoading was renamed to isLoading (and isInitialLoading is deprecated), and a new isLoading now equals isPending && isFetching (i.e., “initially pending AND currently fetching”). Update code to use isPending where you previously checked isLoading for pending state; use the new isLoading only when you want the combined pending+fetching behavior. [1][2]
Update to React Query v5 flags, expose refetch, and add retry strategy for async-created invites.
The hook uses deprecated isLoading (React Query v5 renamed this to isPending); use isPending for pending state instead. Also, refetch is not exposed, limiting consumers' ability to retry if an invite row isn't created yet—this is important when invites are created asynchronously. Add retry tuning to handle the race condition automatically.
interface UseUserInviteResult {
invite: Invite | undefined;
- isLoading: boolean;
+ isPending: boolean;
isError: boolean;
- error: unknown;
+ error: unknown;
+ refetch: () => Promise<void>;
}
export function useUserInvite(userId?: string): UseUserInviteResult {
- const { data, isLoading, isError, error } = useQuery({
+ const { data, isPending, isError, error, refetch } = useQuery({
queryKey: ['user-invite', userId],
enabled: !!userId,
+ retry: 5,
+ retryDelay: attempt => Math.min(500 * 2 ** attempt, 4000),
queryFn: async () => {
if (!userId) {
throw new Error('Missing user id');
}
return InviteService.getInvite(userId);
},
});
return {
invite: data,
- isLoading,
+ isPending,
isError,
error,
+ refetch,
};
}Also applies to: 15-24
🤖 Prompt for AI Agents
In frontend/hooks/use-user-invite.ts around lines 7-12 and 15-24, update the
hook to use React Query v5 flags and add retry/refetch support: replace the
deprecated isLoading with isPending in the UseUserInviteResult interface and in
any returned object, expose the query's refetch method on the result so callers
can manually retry, and configure the useQuery call with a retry strategy (e.g.,
set retry to a small number and an appropriate retryDelay or
exponential/backoff) to handle the async-created invite race; ensure types for
refetch and retry options align with React Query v5 APIs.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
frontend/hooks/use-invite-acceptance.ts (1)
149-169: declineInvite is a no-op stub.The
declineInvitefunction returns success without performing any database operations or cleanup. If invite declination is not needed, consider removing this method; otherwise, implement the required logic.Do you want me to help implement the decline logic, or should this method be removed?
frontend/hooks/use-profile.ts (1)
54-58: Use TABLES.PROFILES constant for consistency.Line 55 uses the string literal
'profiles'while line 92 usesTABLES.PROFILES. Both should use the constant for maintainability.Apply this diff:
- const { data: newProfile, error: createError } = await supabase - .from('profiles') - .insert(profileData as never) + const { data: newProfile, error: createError } = await supabase + .from(TABLES.PROFILES) + .insert(profileData as never)
♻️ Duplicate comments (4)
frontend/hooks/use-invite-acceptance.ts (1)
56-62: Past comment addressed: comment now matches code behavior.The comment correctly states that the friendship is created with pending status. This resolves the previous inconsistency.
frontend/services/invite-service.ts (2)
14-24: Past comment addressed: onConflict now uses 'inviter_id'.The upsert correctly uses
onConflict: 'inviter_id'to enforce one invite per inviter, resolving the previous mismatch. Ensure the database schema has a unique constraint oninviter_id.Run the following script to verify the unique constraint exists:
#!/bin/bash # Check for unique constraint on inviter_id in the invites table schema rg -A 5 -B 5 "inviter_id.*unique|unique.*inviter_id|constraint.*inviter_id" --type sql
26-40: Past comment addressed: maybeSingle() with explicit null check.The query now uses
maybeSingle()and explicitly checks for null, correctly handling the case when no invite exists. This resolves the previous issue.frontend/app/onboarding/invite.tsx (1)
49-50: Past comment addressed: "Continue to Login" now routes correctly.The handler now navigates to
mode=signininstead ofmode=signup, fixing the redirect loop.
🧹 Nitpick comments (3)
frontend/services/invite-service.ts (1)
10-12: Consider removing redundant wrapper.
InviteService.generateInviteCode()simply delegates to the importedgenerateInviteCode(). Unless you plan to add service-specific logic here, callers could import the utility directly.frontend/app/onboarding/invite.tsx (2)
16-18: useUserInvite exposes refetch but it's not destructured.According to the relevant code snippets,
useUserInvitereturns arefetchfunction. Consider destructuring it to enable retry functionality in the error state.Apply this diff:
- const { invite, isLoading, isError } = useUserInvite( + const { invite, isLoading, isError, refetch } = useUserInvite( typeof userId === 'string' ? userId : undefined );
89-105: Add retry button in error state.The error UI provides no way to retry fetching the invite. Since
useUserInvitereturnsrefetch, add a "Try Again" button alongside "Skip for now" to allow users to retry without leaving the screen.Example implementation:
const handleRetry = async () => { if (refetch) { await refetch(); } }; // In the error UI JSX: <View style={styles.actionButtons}> <TouchableOpacity style={styles.shareButton} onPress={handleRetry}> <Text style={styles.shareButtonText}>Try Again</Text> </TouchableOpacity> <TouchableOpacity style={styles.continueButton} onPress={handleSkip}> <Text style={styles.continueButtonText}>Skip for now</Text> <ArrowRight color="#8B5CF6" size={20} /> </TouchableOpacity> </View>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
frontend/app.json(2 hunks)frontend/app/onboarding/invite.tsx(3 hunks)frontend/hooks/use-friends.ts(1 hunks)frontend/hooks/use-invite-acceptance.ts(2 hunks)frontend/hooks/use-profile.ts(3 hunks)frontend/hooks/use-user-invite.ts(1 hunks)frontend/lib/utils.ts(2 hunks)frontend/services/friend-service.ts(2 hunks)frontend/services/invite-service.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- frontend/app.json
- frontend/hooks/use-user-invite.ts
🧰 Additional context used
🧬 Code graph analysis (5)
frontend/hooks/use-invite-acceptance.ts (2)
frontend/lib/supabase.ts (1)
supabase(14-30)frontend/constants/supabase.ts (2)
TABLES(4-13)FRIENDSHIP_STATUS(29-33)
frontend/app/onboarding/invite.tsx (3)
frontend/hooks/use-user-invite.ts (1)
useUserInvite(15-34)frontend/lib/utils.ts (1)
generateDeepLinkUrl(68-79)frontend/services/invite-service.ts (1)
InviteService(8-51)
frontend/lib/utils.ts (1)
frontend/services/invite-service.ts (1)
generateInviteCode(10-12)
frontend/services/friend-service.ts (2)
frontend/lib/utils.ts (1)
generateInviteCode(47-66)frontend/services/invite-service.ts (1)
generateInviteCode(10-12)
frontend/services/invite-service.ts (4)
frontend/types/database.ts (1)
Database(11-244)frontend/lib/utils.ts (1)
generateInviteCode(47-66)frontend/lib/supabase.ts (1)
supabase(14-30)frontend/constants/supabase.ts (1)
TABLES(4-13)
🔇 Additional comments (8)
frontend/hooks/use-invite-acceptance.ts (1)
37-43: LGTM! Input validation is solid.The validation correctly prevents empty IDs and self-connections with clear error messages.
frontend/services/friend-service.ts (2)
9-9: LGTM! Correctly imports the async generateInviteCode.The import statement properly references the external utility function.
16-28: LGTM! generateInviteLink now uses async invite code generation.The method correctly awaits the async
generateInviteCode()and constructs the invite link with the result.frontend/lib/utils.ts (1)
44-66: LGTM! Cryptographically secure invite code generation with proper rejection sampling.The implementation correctly uses
expo-cryptofor secure randomness and applies rejection sampling to avoid modulo bias. The logic is sound:maxMultipleensures only unbiased bytes are used, and the while loop guarantees exactly 8 characters.frontend/hooks/use-profile.ts (2)
51-51: LGTM! Correctly awaits async generateInviteCode().The profile creation now properly awaits the async invite code generation.
8-8: LGTM! ProfileUpdate type appropriately narrows the update interface.Using the database-generated
ProfileUpdatetype for theupdateProfileparameter ensures type safety and aligns with Supabase's generated types.Also applies to: 15-15, 85-85
frontend/services/invite-service.ts (1)
42-50: LGTM! updateInvite provides flexible partial updates.The method allows updating specific invite fields by
invite_codewith proper error handling.frontend/app/onboarding/invite.tsx (1)
13-22: LGTM! Invite link construction handles missing invite code gracefully.The code correctly extracts
userIdfrom route params, usesuseUserInviteto fetch invite data, and constructs the invite link with a fallback wheninviteCodeis absent.
| const updateFriendshipMutation = useMutation({ | ||
| mutationFn: async ({ id, status }: { id: string; status: typeof FRIENDSHIP_STATUS.ACCEPTED | typeof FRIENDSHIP_STATUS.DECLINED }) => { | ||
|
|
||
| if (__DEV__) console.log('Updating friendship:', { id, status }); | ||
| const { data, error } = await supabase | ||
| .from(TABLES.FRIENDSHIPS) | ||
| .update({ status } as never) | ||
| .eq('id', id) | ||
| .select() | ||
| .single(); | ||
|
|
||
| if (error) { | ||
| if (__DEV__) console.error('Error updating friendship:', error); | ||
| throw new Error(error.message); | ||
| } | ||
|
|
||
| if (__DEV__) console.log('Updated friendship:', data); | ||
| return data; | ||
| }, |
There was a problem hiding this comment.
Avoid possible runtime crash: __DEV__ may be undefined in this frontend build.
If __DEV__ isn’t globally defined (common outside React Native), if (__DEV__) ... can throw at runtime. Safer guard:
- if (__DEV__) console.log('Updating friendship:', { id, status });
+ const isDev = typeof __DEV__ !== 'undefined'
+ ? __DEV__
+ : process.env.NODE_ENV !== 'production';
+ if (isDev) console.log('Updating friendship:', { id, status });
const { data, error } = await supabase
.from(TABLES.FRIENDSHIPS)
.update({ status } as never)
.eq('id', id)
.select()
.single();
if (error) {
- if (__DEV__) console.error('Error updating friendship:', error);
+ if (isDev) console.error('Error updating friendship:', error);
throw new Error(error.message);
}
- if (__DEV__) console.log('Updated friendship:', data);
+ if (isDev) console.log('Updated friendship:', data);
return data;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const updateFriendshipMutation = useMutation({ | |
| mutationFn: async ({ id, status }: { id: string; status: typeof FRIENDSHIP_STATUS.ACCEPTED | typeof FRIENDSHIP_STATUS.DECLINED }) => { | |
| if (__DEV__) console.log('Updating friendship:', { id, status }); | |
| const { data, error } = await supabase | |
| .from(TABLES.FRIENDSHIPS) | |
| .update({ status } as never) | |
| .eq('id', id) | |
| .select() | |
| .single(); | |
| if (error) { | |
| if (__DEV__) console.error('Error updating friendship:', error); | |
| throw new Error(error.message); | |
| } | |
| if (__DEV__) console.log('Updated friendship:', data); | |
| return data; | |
| }, | |
| const updateFriendshipMutation = useMutation({ | |
| mutationFn: async ({ id, status }: { id: string; status: typeof FRIENDSHIP_STATUS.ACCEPTED | typeof FRIENDSHIP_STATUS.DECLINED }) => { | |
| const isDev = typeof __DEV__ !== 'undefined' | |
| ? __DEV__ | |
| : process.env.NODE_ENV !== 'production'; | |
| if (isDev) console.log('Updating friendship:', { id, status }); | |
| const { data, error } = await supabase | |
| .from(TABLES.FRIENDSHIPS) | |
| .update({ status } as never) | |
| .eq('id', id) | |
| .select() | |
| .single(); | |
| if (error) { | |
| if (isDev) console.error('Error updating friendship:', error); | |
| throw new Error(error.message); | |
| } | |
| if (isDev) console.log('Updated friendship:', data); | |
| return data; | |
| }, |
🤖 Prompt for AI Agents
In frontend/hooks/use-friends.ts around lines 137 to 155, there are several
runtime checks using __DEV__ which may be undefined in the browser build and
cause a crash; replace each raw `if (__DEV__)` with a safe guard such as `if
(typeof __DEV__ !== 'undefined' && __DEV__)` or use an equivalent environment
check like `process.env.NODE_ENV !== 'production'` to conditionally log,
ensuring no direct reference to an undefined global; keep the existing log
messages and error handling otherwise.
Summary by CodeRabbit
New Features
Bug Fixes
Other Changes
✏️ Tip: You can customize this high-level summary in your review settings.