Skip to content

feat: migrate backend to supabase, update prisma schema wrt supabase and add auth testing with logout button - #4

Merged
pseud039 merged 4 commits into
masterfrom
feat/backend-setup
Jun 25, 2026
Merged

feat: migrate backend to supabase, update prisma schema wrt supabase and add auth testing with logout button#4
pseud039 merged 4 commits into
masterfrom
feat/backend-setup

Conversation

@ankushchk

@ankushchk ankushchk commented Jun 19, 2026

Copy link
Copy Markdown
Owner
Screen.Recording.2026-06-19.at.7.43.06.PM.mov

Summary by CodeRabbit

Release Notes

  • New Features

    • Implemented authentication system with login, signup, and welcome onboarding screens.
    • Added dashboard with logout functionality.
    • Introduced tab-based navigation for home and explore sections.
    • Integrated secure credential storage and backend services.
  • Chores

    • Removed deprecated template components and scripts.
    • Updated dependencies and configuration.

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds Supabase authentication to the Expo mobile app and aligns the backend Prisma schema. The mobile side introduces a Supabase client with SecureStore token persistence, an AuthProvider/useAuth context, three new auth screens (welcome, login, signup), auth-guarded root navigation, redesigned theme/typography constants, and reusable auth UI components. The backend adds DIRECT_URL to the Prisma datasource and changes User.id to a @db.Uuid field.

Changes

Supabase Auth Integration

Layer / File(s) Summary
Backend Supabase config and Prisma schema
backend/.env.example, backend/prisma/schema.prisma
Adds Supabase pooler connection examples to the backend env file, adds directUrl = env("DIRECT_URL") to the Prisma datasource, and changes User.id from String @id @default(cuid()) to String @id @db.Uuid`` with no default.
Supabase client, auth context, and mobile env/deps
mobile/src/lib/supabase.ts, mobile/src/hooks/useAuth.tsx, mobile/.env.example, mobile/app.json, mobile/package.json, mobile/pnpm-workspace.yaml
Creates the Supabase client using an Expo SecureStore adapter, defines AuthContext/AuthProvider/useAuth tracking session state via getSession and onAuthStateChange, registers the expo-secure-store plugin, and adds new dependencies (expo-secure-store, lucide-react-native, react-native-url-polyfill, Google Fonts packages).
Theme palette, Typography, and ThemedText
mobile/src/constants/theme.ts, mobile/src/components/themed-text.tsx, mobile/src/hooks/use-color-scheme.web.ts
Replaces Colors light/dark palette values, introduces the Typography constant with explicit font family names, and updates ThemedText to use Typography font families across all variants with a new 'display' type that selects a dark-mode font family.
Auth UI component library
mobile/src/components/auth-ui.tsx
Introduces five themed auth building-block components: AuthInput, PrimaryButton (with loading/icon support), SocialButton, Divider, and PinDots, along with their StyleSheet definitions.
Auth screens: welcome, login, signup
mobile/app/(auth)/_layout.tsx, mobile/app/(auth)/welcome.tsx, mobile/app/(auth)/login.tsx, mobile/app/(auth)/signup.tsx
Adds a headerless (auth) route group layout, an auto-advancing three-slide welcome screen with pagination, a login screen calling supabase.auth.signInWithPassword, and a signup screen calling supabase.auth.signUp with full_name.
Root layout, auth-guarded routing, and tab screens
mobile/app/_layout.tsx, mobile/app/(tabs)/_layout.tsx, mobile/app/(tabs)/index.tsx, mobile/app/(tabs)/explore.tsx, mobile/src/components/app-tabs.web.tsx
Adds the root layout with font loading, AuthProvider/ThemeProvider wrapping, and segment-based redirects (unauthenticated → welcome, authenticated in auth → tabs home). Adds a HomeScreen with sign-out and a placeholder ExploreScreen. Updates the web tab bar brand name to "Splikaro" and removes the Docs link.
ESLint flat config
mobile/eslint.config.js
Adds Expo flat ESLint configuration with a dist/* ignore rule.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant AuthScreen as Login/Signup Screen
  participant SupabaseAuth as supabase.auth
  participant AuthProvider
  participant RootLayout

  User->>AuthScreen: submit credentials
  AuthScreen->>SupabaseAuth: signInWithPassword / signUp
  SupabaseAuth-->>AuthScreen: error (display) or success
  SupabaseAuth->>AuthProvider: onAuthStateChange fires with new session
  AuthProvider->>AuthProvider: setSession / setUser
  AuthProvider->>RootLayout: useAuth() returns updated session
  RootLayout->>RootLayout: segment check → replace route to /
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 Hopped through the auth gates, one, two, three,
A welcome screen spins — come sign in with me!
Supabase holds the keys, SecureStore the token,
UUID ids fresh, no old cuid broken.
The rabbit rebranded: "Splikaro" aglow —
Now login, sign up, let the sessions flow! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title comprehensively covers the main changes: backend Supabase migration, Prisma schema updates, and authentication testing with logout button.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/backend-setup

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (2)
mobile/src/components/auth-ui.tsx (2)

20-25: ⚡ Quick win

Use a TouchableOpacity ref type for PrimaryButton.

Line 20 declares forwardRef<View, ...> but the ref is attached to <TouchableOpacity>. Use React.ElementRef<typeof TouchableOpacity> (or TouchableOpacity) for the forwarded ref type to keep the public ref contract correct.

Suggested fix
-export const PrimaryButton = React.forwardRef<View, TouchableOpacityProps & { title: string; loading?: boolean; iconRight?: React.ReactNode }>(({ title, loading, style, iconRight, ...props }, ref) => {
+type PrimaryButtonProps = TouchableOpacityProps & {
+  title: string;
+  loading?: boolean;
+  iconRight?: React.ReactNode;
+};
+
+export const PrimaryButton = React.forwardRef<React.ElementRef<typeof TouchableOpacity>, PrimaryButtonProps>(
+({ title, loading, style, iconRight, ...props }, ref) => {
   const theme = useTheme();
   return (
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/src/components/auth-ui.tsx` around lines 20 - 25, In the PrimaryButton
component, the forwardRef generic type declaration uses View as the ref type,
but the actual ref is being attached to TouchableOpacity. Update the forwardRef
type parameter from View to React.ElementRef<typeof TouchableOpacity> to ensure
the public ref contract matches the underlying component being referenced.

23-27: ⚡ Quick win

Disable presses while loading inside PrimaryButton.

loading currently only changes visuals. If a caller forgets to pass disabled, duplicate submits are still possible.

Suggested fix
 export const PrimaryButton = React.forwardRef<...>(({ title, loading, style, iconRight, ...props }, ref) => {
   const theme = useTheme();
+  const isDisabled = Boolean(props.disabled || loading);
   return (
     <TouchableOpacity 
       ref={ref}
-      style={[styles.primaryButton, { backgroundColor: theme.primary, opacity: props.disabled ? 0.7 : 1 }, style]} 
+      disabled={isDisabled}
+      style={[styles.primaryButton, { backgroundColor: theme.primary, opacity: isDisabled ? 0.7 : 1 }, style]} 
       activeOpacity={0.8}
       {...props}
     >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/src/components/auth-ui.tsx` around lines 23 - 27, The PrimaryButton
component should prevent button presses when the loading state is active, not
just when the disabled prop is true. Modify the TouchableOpacity component to
disable presses by checking both props.disabled and props.loading together - the
button should be disabled if either condition is true. This ensures duplicate
submissions are prevented even if a caller forgets to pass the disabled prop
alongside the loading state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/.env.example`:
- Line 2: The backend/.env.example file contains duplicate DATABASE_URL entries
at multiple locations, where the second entry will silently override the first
during configuration, leading to misconfiguration. Identify all occurrences of
the DATABASE_URL key in the file and remove the duplicate entries, keeping only
one active DATABASE_URL configuration that represents the correct database
connection string for local development.

In `@mobile/app/_layout.tsx`:
- Around line 19-42: The RootLayoutNav component renders the Stack navigator
even when initialized is false, causing a flash of protected content before the
useEffect redirect logic executes. Gate the entire rendering of the
ThemeProvider and Stack components behind a check for the initialized flag -
only render the navigation tree when initialized is true, otherwise return null
or a loading indicator to prevent premature rendering of protected tabs.

In `@mobile/app/`(auth)/login.tsx:
- Around line 68-70: The TouchableOpacity component rendering the "Forgot
password?" text has no onPress handler, making it a non-functional control.
Either add an onPress prop to the TouchableOpacity that navigates to a password
reset/recovery screen (or triggers appropriate password recovery flow), or
remove the entire TouchableOpacity component and its child Text if the forgot
password functionality is not currently implemented.

In `@mobile/app/`(auth)/signup.tsx:
- Around line 62-65: The SocialButton components for "Continue with Google" and
"Continue with Phone" are missing onPress event handlers, creating
non-functional UI elements that appear interactive. Add onPress callbacks to
both SocialButton instances in the socialBlock that implement the appropriate
authentication or navigation logic for each provider, ensuring users can
actually interact with these buttons.

In `@mobile/app/`(tabs)/index.tsx:
- Around line 7-9: The handleLogout function currently ignores errors returned
by supabase.auth.signOut(), which means logout failures occur silently without
user feedback. Modify the handleLogout function to capture the result of the
supabase.auth.signOut() call and destructure or check the error property from
the returned object. If an error exists, handle it appropriately by logging it
and providing user feedback (such as displaying an error message) instead of
allowing the logout to fail silently.

In `@mobile/src/hooks/useAuth.tsx`:
- Around line 22-33: The useEffect hook in useAuth.tsx needs to handle both
error checking and cleanup on unmount. The getSession() call returns an object
with both data and error properties, so you need to check if the error property
exists and handle initialization failures instead of silently ignoring them.
Additionally, add a cleanup flag (like a mounted or isMounted ref) that gets set
to false in a cleanup function returned from the useEffect, and check this flag
before calling setSession, setUser, and setInitialized to prevent state updates
after component unmount. Return a cleanup function from the useEffect that sets
this flag and unsubscribes from the onAuthStateChange subscription to fully
prevent memory leaks.

In `@mobile/src/lib/supabase.ts`:
- Around line 17-20: The createClient call in the supabase initialization uses
non-null assertions on the environment variables EXPO_PUBLIC_SUPABASE_URL and
EXPO_PUBLIC_SUPABASE_ANON_KEY, which results in unclear runtime errors if they
are missing. Replace these non-null assertions with explicit validation that
checks if both environment variables are defined before the createClient call,
and throw a descriptive error message that clearly states which variable is
missing if validation fails. This ensures the application fails fast with
actionable feedback during startup.
- Around line 9-14: The setItem and removeItem methods in the storage adapter
are calling SecureStore.setItemAsync and SecureStore.deleteItemAsync but not
returning the promises they produce. This creates a race condition where
Supabase's persistSession and autoRefreshToken features cannot await these
operations. Add return statements before each SecureStore async call in both the
setItem method (before SecureStore.setItemAsync) and the removeItem method
(before SecureStore.deleteItemAsync) so that the promises are properly returned
and can be awaited by the Supabase client.

---

Nitpick comments:
In `@mobile/src/components/auth-ui.tsx`:
- Around line 20-25: In the PrimaryButton component, the forwardRef generic type
declaration uses View as the ref type, but the actual ref is being attached to
TouchableOpacity. Update the forwardRef type parameter from View to
React.ElementRef<typeof TouchableOpacity> to ensure the public ref contract
matches the underlying component being referenced.
- Around line 23-27: The PrimaryButton component should prevent button presses
when the loading state is active, not just when the disabled prop is true.
Modify the TouchableOpacity component to disable presses by checking both
props.disabled and props.loading together - the button should be disabled if
either condition is true. This ensures duplicate submissions are prevented even
if a caller forgets to pass the disabled prop alongside the loading state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bcc14e2d-c688-476b-b241-09c0fc321ac2

📥 Commits

Reviewing files that changed from the base of the PR and between 6c836d2 and 9ed2849.

⛔ Files ignored due to path filters (1)
  • mobile/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (35)
  • backend/.env.example
  • backend/prisma/schema.prisma
  • mobile/.env.example
  • mobile/AGENTS.md
  • mobile/CLAUDE.md
  • mobile/app.json
  • mobile/app/(auth)/_layout.tsx
  • mobile/app/(auth)/login.tsx
  • mobile/app/(auth)/signup.tsx
  • mobile/app/(auth)/welcome.tsx
  • mobile/app/(tabs)/_layout.tsx
  • mobile/app/(tabs)/explore.tsx
  • mobile/app/(tabs)/index.tsx
  • mobile/app/_layout.tsx
  • mobile/eslint.config.js
  • mobile/package.json
  • mobile/pnpm-workspace.yaml
  • mobile/scripts/reset-project.js
  • mobile/src/components/animated-icon.module.css
  • mobile/src/components/animated-icon.tsx
  • mobile/src/components/animated-icon.web.tsx
  • mobile/src/components/app-tabs.web.tsx
  • mobile/src/components/auth-ui.tsx
  • mobile/src/components/external-link.tsx
  • mobile/src/components/hint-row.tsx
  • mobile/src/components/themed-text.tsx
  • mobile/src/components/ui/collapsible.tsx
  • mobile/src/components/web-badge.tsx
  • mobile/src/constants/theme.ts
  • mobile/src/hooks/use-color-scheme.web.ts
  • mobile/src/hooks/useAuth.tsx
  • mobile/src/lib/supabase.ts
  • mobile/src/screens/_layout.tsx
  • mobile/src/screens/explore.tsx
  • mobile/src/screens/index.tsx
💤 Files with no reviewable changes (13)
  • mobile/CLAUDE.md
  • mobile/AGENTS.md
  • mobile/src/components/ui/collapsible.tsx
  • mobile/src/components/animated-icon.module.css
  • mobile/src/components/hint-row.tsx
  • mobile/src/components/animated-icon.web.tsx
  • mobile/scripts/reset-project.js
  • mobile/src/components/web-badge.tsx
  • mobile/src/screens/explore.tsx
  • mobile/src/screens/index.tsx
  • mobile/src/components/external-link.tsx
  • mobile/src/components/animated-icon.tsx
  • mobile/src/screens/_layout.tsx

Comment thread backend/.env.example
@@ -1 +1,9 @@
# if you're using neondb then use this
DATABASE_URL="postgresql://postgres:password@localhost:5432/kryze_innoforge?schema=public"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid duplicate DATABASE_URL keys in the example file.

Two active DATABASE_URL entries means the second one always overrides the first, which can silently misconfigure local setup.

Suggested fix
-# if you're using neondb then use this
-DATABASE_URL="postgresql://postgres:password@localhost:5432/kryze_innoforge?schema=public"
+# Option A: local Postgres
+# DATABASE_URL=postgresql://postgres:password@localhost:5432/kryze_innoforge?schema=public

 # if you're using supabase :)
 # Connect to Postgres via the shared transaction-mode pooler (IPv4-only)
-DATABASE_URL="postgresql://postgres.rjzxredxxfbkrlmuyabb:[YOUR-PASSWORD]`@aws-1-ap-southeast-1.pooler.supabase.com`:6543/postgres?pgbouncer=true"
+DATABASE_URL=postgresql://postgres.rjzxredxxfbkrlmuyabb:[YOUR-PASSWORD]`@aws-1-ap-southeast-1.pooler.supabase.com`:6543/postgres?pgbouncer=true

Also applies to: 6-6

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/.env.example` at line 2, The backend/.env.example file contains
duplicate DATABASE_URL entries at multiple locations, where the second entry
will silently override the first during configuration, leading to
misconfiguration. Identify all occurrences of the DATABASE_URL key in the file
and remove the duplicate entries, keeping only one active DATABASE_URL
configuration that represents the correct database connection string for local
development.

Source: Linters/SAST tools

Comment thread mobile/app/_layout.tsx
Comment on lines +19 to +42
const { session, initialized } = useAuth();
const segments = useSegments();
const router = useRouter();

useEffect(() => {
if (!initialized) return;

const inAuthGroup = segments[0] === '(auth)';

if (!session && !inAuthGroup) {
router.replace('/(auth)/welcome');
} else if (session && inAuthGroup) {
router.replace('/');
}
}, [session, initialized, segments, router]);

return (
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="(auth)" options={{ headerShown: false }} />
</Stack>
</ThemeProvider>
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Gate navigator rendering until auth initialization completes.

RootLayoutNav renders the Stack even when initialized === false, and redirect logic is deferred (Line 24). That allows a pre-redirect flash of protected tabs.

Suggested fix
 function RootLayoutNav() {
   const colorScheme = useColorScheme();
   const { session, initialized } = useAuth();
   const segments = useSegments();
   const router = useRouter();
+
+  if (!initialized) {
+    return null;
+  }
 
   useEffect(() => {
-    if (!initialized) return;
-
     const inAuthGroup = segments[0] === '(auth)';
 
     if (!session && !inAuthGroup) {
       router.replace('/(auth)/welcome');
📝 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.

Suggested change
const { session, initialized } = useAuth();
const segments = useSegments();
const router = useRouter();
useEffect(() => {
if (!initialized) return;
const inAuthGroup = segments[0] === '(auth)';
if (!session && !inAuthGroup) {
router.replace('/(auth)/welcome');
} else if (session && inAuthGroup) {
router.replace('/');
}
}, [session, initialized, segments, router]);
return (
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="(auth)" options={{ headerShown: false }} />
</Stack>
</ThemeProvider>
);
const { session, initialized } = useAuth();
const segments = useSegments();
const router = useRouter();
if (!initialized) {
return null;
}
useEffect(() => {
const inAuthGroup = segments[0] === '(auth)';
if (!session && !inAuthGroup) {
router.replace('/(auth)/welcome');
} else if (session && inAuthGroup) {
router.replace('/');
}
}, [session, initialized, segments, router]);
return (
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="(auth)" options={{ headerShown: false }} />
</Stack>
</ThemeProvider>
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/app/_layout.tsx` around lines 19 - 42, The RootLayoutNav component
renders the Stack navigator even when initialized is false, causing a flash of
protected content before the useEffect redirect logic executes. Gate the entire
rendering of the ThemeProvider and Stack components behind a check for the
initialized flag - only render the navigation tree when initialized is true,
otherwise return null or a loading indicator to prevent premature rendering of
protected tabs.

Comment on lines +68 to +70
<TouchableOpacity style={styles.forgotPassword}>
<Text style={[styles.forgotText, { color: theme.primary }]}>Forgot password?</Text>
</TouchableOpacity>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Wire or remove the “Forgot password?” action.

Line 68 renders a tappable control with no handler, so it is currently a dead action.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/app/`(auth)/login.tsx around lines 68 - 70, The TouchableOpacity
component rendering the "Forgot password?" text has no onPress handler, making
it a non-functional control. Either add an onPress prop to the TouchableOpacity
that navigates to a password reset/recovery screen (or triggers appropriate
password recovery flow), or remove the entire TouchableOpacity component and its
child Text if the forgot password functionality is not currently implemented.

Comment on lines +62 to +65
<View style={styles.socialBlock}>
<SocialButton title="Continue with Google" icon="G" />
<SocialButton title="Continue with Phone" icon="📱" />
</View>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid shipping social sign-up buttons as no-op controls.

These CTAs look interactive but have no onPress, which is misleading for users.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/app/`(auth)/signup.tsx around lines 62 - 65, The SocialButton
components for "Continue with Google" and "Continue with Phone" are missing
onPress event handlers, creating non-functional UI elements that appear
interactive. Add onPress callbacks to both SocialButton instances in the
socialBlock that implement the appropriate authentication or navigation logic
for each provider, ensuring users can actually interact with these buttons.

Comment on lines +7 to +9
const handleLogout = async () => {
await supabase.auth.signOut();
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle sign-out errors explicitly.

At Line 8, logout failures are currently silent. Check the returned error so users aren’t left signed in without feedback.

Suggested fix
 import { ThemedView } from '`@/components/themed-view`';
 import { ThemedText } from '`@/components/themed-text`';
-import { StyleSheet, Button } from 'react-native';
+import { StyleSheet, Button, Alert } from 'react-native';
 import { supabase } from '`@/lib/supabase`';
 
 export default function HomeScreen() {
   const handleLogout = async () => {
-    await supabase.auth.signOut();
+    const { error } = await supabase.auth.signOut();
+    if (error) {
+      Alert.alert('Logout failed', error.message);
+    }
   };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/app/`(tabs)/index.tsx around lines 7 - 9, The handleLogout function
currently ignores errors returned by supabase.auth.signOut(), which means logout
failures occur silently without user feedback. Modify the handleLogout function
to capture the result of the supabase.auth.signOut() call and destructure or
check the error property from the returned object. If an error exists, handle it
appropriately by logging it and providing user feedback (such as displaying an
error message) instead of allowing the logout to fail silently.

Comment on lines +22 to +33
useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session);
setUser(session?.user ?? null);
setInitialized(true);
});

const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
setUser(session?.user ?? null);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find the useAuth hook file
find . -name "useAuth.tsx" -type f

Repository: ankushchk/kryze

Length of output: 90


🏁 Script executed:

cat -n ./mobile/src/hooks/useAuth.tsx

Repository: ankushchk/kryze

Length of output: 1636


🏁 Script executed:

# Search for other usages of getSession to understand its behavior
rg "getSession" --type ts --type tsx -B 2 -A 2

Repository: ankushchk/kryze

Length of output: 87


🏁 Script executed:

# Search for getSession with different approach
rg "getSession" -B 2 -A 2

Repository: ankushchk/kryze

Length of output: 355


🌐 Web query:

Supabase getSession() error handling rejection promise

💡 Result:

The Supabase Auth getSession() method does not throw a promise rejection; instead, it returns an object containing both a session and an error property [1][2]. You should handle errors by checking the error property rather than using a try-catch block for promise rejections [1][2]. Key aspects of handling getSession() results: 1. Method Signature: The method returns a Promise that resolves to an object: { data: { session: Session | null }, error: ApiError | null } [1][2]. 2. Error Handling: Always check for the presence of the error object [3]. If error is present, it indicates that the session could not be retrieved or refreshed successfully (e.g., due to an expired or revoked refresh token) [1][4]. 3. Session Preservation: As of June 2026, recent updates to the Supabase JS SDK have improved how getSession() handles failed token refreshes [5][6]. If a refresh fails but the current access token is still valid (within its expiry window), the SDK now preserves the session instead of returning an error or clearing storage [5][6]. This prevents unnecessary user logouts during transient network or refresh failures [5]. 4. Recommended Usage: - For client-side apps, checking error is sufficient to determine if the user is authenticated [3]. - For server-side contexts (like Next.js SSR), you should prioritize getUser() over getSession() [2][7][8]. getUser() makes an API call to verify the JWT against the Supabase Auth server, whereas getSession() only reads from local storage or cookies, which can be insecure or stale [8]. Example pattern: const { data, error } = await supabase.auth.getSession; if (error) { // Handle the error (e.g., log, redirect to login, or clear local state) console.error('Error fetching session:', error.message); return; } if (data.session) { // Session is valid console.log('Session retrieved:', data.session); } else { // No session found, user is likely logged out console.log('No active session'); }

Citations:


Check error property from getSession() and handle state cleanup on unmount.

The getSession() promise doesn't reject—it resolves with an object containing both data and error properties. The current code doesn't check the error property, so initialization failures are silently ignored. Additionally, state updates can occur after component unmount when getSession() completes asynchronously, causing memory leak warnings.

Use a cleanup flag to prevent state updates after unmount and handle the error case:

Suggested fix
   useEffect(() => {
+    let active = true;
+    (async () => {
-    supabase.auth.getSession().then(({ data: { session } }) => {
+      const { data: { session }, error } = await supabase.auth.getSession();
+      if (!active) return;
       setSession(session);
       setUser(session?.user ?? null);
+    })().catch(err => {
+      if (active) console.error('Failed to fetch session:', err);
+    }).finally(() => {
+      if (active) setInitialized(true);
     });

     const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
+      if (!active) return;
       setSession(session);
       setUser(session?.user ?? null);
     });

     return () => {
+      active = false;
       subscription.unsubscribe();
     };
   }, []);
📝 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.

Suggested change
useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session);
setUser(session?.user ?? null);
setInitialized(true);
});
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
setUser(session?.user ?? null);
});
useEffect(() => {
let active = true;
(async () => {
const { data: { session }, error } = await supabase.auth.getSession();
if (!active) return;
setSession(session);
setUser(session?.user ?? null);
})().catch(err => {
if (active) console.error('Failed to fetch session:', err);
}).finally(() => {
if (active) setInitialized(true);
});
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
if (!active) return;
setSession(session);
setUser(session?.user ?? null);
});
return () => {
active = false;
subscription.unsubscribe();
};
}, []);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/src/hooks/useAuth.tsx` around lines 22 - 33, The useEffect hook in
useAuth.tsx needs to handle both error checking and cleanup on unmount. The
getSession() call returns an object with both data and error properties, so you
need to check if the error property exists and handle initialization failures
instead of silently ignoring them. Additionally, add a cleanup flag (like a
mounted or isMounted ref) that gets set to false in a cleanup function returned
from the useEffect, and check this flag before calling setSession, setUser, and
setInitialized to prevent state updates after component unmount. Return a
cleanup function from the useEffect that sets this flag and unsubscribes from
the onAuthStateChange subscription to fully prevent memory leaks.

Comment on lines +9 to +14
setItem: (key: string, value: string) => {
SecureStore.setItemAsync(key, value);
},
removeItem: (key: string) => {
SecureStore.deleteItemAsync(key);
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find mobile -name "supabase.ts" -o -name "supabase.js" 2>/dev/null | head -20

Repository: ankushchk/kryze

Length of output: 86


🏁 Script executed:

cd mobile && find . -name "*.ts" -o -name "*.tsx" | grep -i supabase | head -20

Repository: ankushchk/kryze

Length of output: 81


🏁 Script executed:

git ls-files mobile | grep -i supabase

Repository: ankushchk/kryze

Length of output: 86


🏁 Script executed:

cat -n mobile/src/lib/supabase.ts

Repository: ankushchk/kryze

Length of output: 973


🏁 Script executed:

rg -A 10 -B 5 "ExpoSecureStoreAdapter|setItem|removeItem" mobile/src/lib/supabase.ts

Repository: ankushchk/kryze

Length of output: 777


🏁 Script executed:

rg "ExpoSecureStoreAdapter" mobile --type ts --type tsx

Repository: ankushchk/kryze

Length of output: 87


🌐 Web query:

Supabase storage adapter interface TypeScript getItem setItem removeItem promises

💡 Result:

In Supabase, the storage adapter used for authentication persistence (session management) follows the SupportedStorage interface [1][2][3]. This interface allows you to implement custom storage logic (e.g., for SSR, mobile environments, or non-browser contexts) [4][2][5]. The SupportedStorage interface defines three methods [2][3]: getItem(key: string): string | null | Promise<string | null>; setItem(key: string, value: string): void | Promise; removeItem(key: string): void | Promise; Key Implementation Details: 1. Asynchronous Support: While many browser-based implementations are synchronous (like standard localStorage), the interface explicitly allows these methods to return Promises [4][2]. This is essential for environments like React Native (using AsyncStorage) or server-side environments where storage access may be asynchronous [2][5]. 2. Serialization: The Supabase Auth client handles the serialization of session data (JSON.stringify/parse) [3], but the storage adapter is responsible for storing and retrieving the raw string value associated with the provided key [2]. 3. Usage: You can inject a custom adapter when initializing the Supabase client under the auth.storage configuration option: import { createClient } from '@supabase/supabase-js'; const customStorage = { getItem: (key) => { /* logic to retrieve string / }, setItem: (key, value) => { / logic to store string / }, removeItem: (key) => { / logic to delete key */ }, }; const supabase = createClient('URL', 'KEY', { auth: { storage: customStorage, }, }); Important Note: This interface is specific to the Auth client's session persistence [1][3]. It is separate from the Supabase Storage service used for uploading and managing user files (buckets), which uses a different StorageClient API [6][7]. The file storage service does not use this getItem/setItem interface [8][9].

Citations:


🌐 Web query:

Expo SecureStore setItemAsync deleteItemAsync return value promise

💡 Result:

Both setItemAsync and deleteItemAsync in the Expo SecureStore library return a Promise [1][2]. For setItemAsync(key, value, options): The returned promise resolves when the key-value pair is successfully stored [1]. It rejects if the value cannot be stored on the device (e.g., due to size limitations or other underlying system errors) [1][3]. For deleteItemAsync(key, options): The returned promise resolves when the operation is complete [2]. It rejects if the value cannot be deleted [2]. In both cases, because they return promises, you should use async/await or.then/.catch patterns to handle the results and potential errors [1][2].

Citations:


Return the SecureStore promises from the adapter methods.

On lines 10 and 13, setItemAsync and deleteItemAsync are invoked but not returned, making storage mutations fire-and-forget. Since persistSession and autoRefreshToken are enabled, Supabase expects to await these operations during token refresh. Without returning the promises, you create a race condition where session data may not be fully persisted before the auth client proceeds, causing potential session loss.

Suggested fix
 const ExpoSecureStoreAdapter = {
   getItem: (key: string) => {
     return SecureStore.getItemAsync(key);
   },
   setItem: (key: string, value: string) => {
-    SecureStore.setItemAsync(key, value);
+    return SecureStore.setItemAsync(key, value);
   },
   removeItem: (key: string) => {
-    SecureStore.deleteItemAsync(key);
+    return SecureStore.deleteItemAsync(key);
   },
 };
📝 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.

Suggested change
setItem: (key: string, value: string) => {
SecureStore.setItemAsync(key, value);
},
removeItem: (key: string) => {
SecureStore.deleteItemAsync(key);
},
setItem: (key: string, value: string) => {
return SecureStore.setItemAsync(key, value);
},
removeItem: (key: string) => {
return SecureStore.deleteItemAsync(key);
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/src/lib/supabase.ts` around lines 9 - 14, The setItem and removeItem
methods in the storage adapter are calling SecureStore.setItemAsync and
SecureStore.deleteItemAsync but not returning the promises they produce. This
creates a race condition where Supabase's persistSession and autoRefreshToken
features cannot await these operations. Add return statements before each
SecureStore async call in both the setItem method (before
SecureStore.setItemAsync) and the removeItem method (before
SecureStore.deleteItemAsync) so that the promises are properly returned and can
be awaited by the Supabase client.

Comment on lines +17 to +20
export const supabase = createClient(
process.env.EXPO_PUBLIC_SUPABASE_URL!,
process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY!,
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail fast with explicit env validation instead of non-null assertions.

Line 18 and Line 19 rely on !; if either env var is missing, startup fails with a less-actionable runtime error. Validate once and throw a clear message.

Suggested fix
+const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL;
+const supabaseAnonKey = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY;
+
+if (!supabaseUrl || !supabaseAnonKey) {
+  throw new Error('Missing EXPO_PUBLIC_SUPABASE_URL or EXPO_PUBLIC_SUPABASE_ANON_KEY');
+}
+
 export const supabase = createClient(
-  process.env.EXPO_PUBLIC_SUPABASE_URL!,
-  process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY!,
+  supabaseUrl,
+  supabaseAnonKey,
   { 
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/src/lib/supabase.ts` around lines 17 - 20, The createClient call in
the supabase initialization uses non-null assertions on the environment
variables EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY, which
results in unclear runtime errors if they are missing. Replace these non-null
assertions with explicit validation that checks if both environment variables
are defined before the createClient call, and throw a descriptive error message
that clearly states which variable is missing if validation fails. This ensures
the application fails fast with actionable feedback during startup.

@pseud039
pseud039 merged commit 9ed2849 into master Jun 25, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants