Skip to content

fix: harden Supabase auth bootstrap to avoid import-time crash#10

Merged
support371 merged 1 commit intomainfrom
fix/auth-bootstrap-resilient
Mar 20, 2026
Merged

fix: harden Supabase auth bootstrap to avoid import-time crash#10
support371 merged 1 commit intomainfrom
fix/auth-bootstrap-resilient

Conversation

@support371
Copy link
Copy Markdown
Owner

@support371 support371 commented Mar 19, 2026

Summary

  • make Supabase client initialization resilient instead of throwing at import time
  • expose isSupabaseConfigured and authUnavailableReason
  • harden useAuth so loading always settles and config errors do not crash the app
  • return readable config errors from auth methods when Supabase is unavailable

Why

The merged production branch still had an import-time crash path in src/integrations/supabase/client.ts and src/hooks/useAuth.tsx. If VITE_SUPABASE_URL or VITE_SUPABASE_PUBLISHABLE_KEY is missing, the React tree could fail before auth state resolves.

Result

  • no import-time throw from Supabase client setup
  • auth provider settles deterministically even when config is missing
  • app can render a controlled fallback instead of blank-shelling

Summary by Sourcery

Harden Supabase auth bootstrap so missing configuration no longer crashes the app at import time and auth flows can fail gracefully.

New Features:

  • Expose Supabase auth configuration status and unavailability reason through the auth client and context.
  • Extend the auth context with configuration flags so consumers can detect when Supabase auth is unavailable.

Bug Fixes:

  • Prevent import-time crashes when Supabase environment variables are missing by making client initialization conditional.
  • Ensure the auth provider always settles its loading state even when Supabase is misconfigured or unavailable.
  • Return readable configuration errors from auth methods when Supabase is not configured instead of failing unpredictably.

Enhancements:

  • Log a console warning when Supabase configuration is missing to aid debugging.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Mar 19, 2026

Reviewer's Guide

Makes Supabase auth initialization and usage resilient to missing configuration by avoiding import-time crashes, exposing configuration status, and ensuring auth hooks and methods degrade gracefully when Supabase is unavailable.

Sequence diagram for resilient Supabase auth initialization in AuthProvider

sequenceDiagram
  actor ReactTree
  participant AuthProvider
  participant SupabaseClientModule as SupabaseClient
  participant SupabaseAuth as supabase.auth

  ReactTree->>AuthProvider: mount
  AuthProvider->>SupabaseClientModule: read isSupabaseConfigured, supabase, authUnavailableReason
  alt Supabase not configured or supabase is null
    AuthProvider->>AuthProvider: setSession(null), setUser(null)
    AuthProvider->>AuthProvider: setIsLoading(false)
    AuthProvider-->>ReactTree: provide context (isConfigured=false, authUnavailableReason)
  else Supabase configured and supabase exists
    AuthProvider->>SupabaseAuth: onAuthStateChange(handler)
    SupabaseAuth-->>AuthProvider: subscription
    AuthProvider->>SupabaseAuth: getSession()
    alt getSession resolves
      SupabaseAuth-->>AuthProvider: nextSession
      AuthProvider->>AuthProvider: setSession(nextSession), setUser(nextSession.user)
      AuthProvider->>AuthProvider: setIsLoading(false)
    else getSession rejects
      SupabaseAuth-->>AuthProvider: error
      AuthProvider->>AuthProvider: setSession(null), setUser(null)
      AuthProvider->>AuthProvider: setIsLoading(false)
    end
    AuthProvider-->>ReactTree: provide context (isConfigured=true, authUnavailableReason=null)
  end

  ReactTree->>AuthProvider: unmount
  AuthProvider->>SupabaseAuth: subscription.unsubscribe()
Loading

Sequence diagram for guarded auth method calls when Supabase is unavailable

sequenceDiagram
  actor UserComponent
  participant AuthProvider
  participant SupabaseClientModule as SupabaseClient
  participant SupabaseAuth as supabase.auth

  UserComponent->>AuthProvider: signIn(email, password)
  AuthProvider->>SupabaseClientModule: check isSupabaseConfigured and supabase
  alt Supabase not configured or supabase is null
    AuthProvider-->>UserComponent: { error: getConfigError() }
  else Supabase configured
    AuthProvider->>SupabaseAuth: signInWithPassword(email, password)
    SupabaseAuth-->>AuthProvider: { error }
    AuthProvider-->>UserComponent: { error }
  end

  UserComponent->>AuthProvider: signUp(email, password, metadata)
  AuthProvider->>SupabaseClientModule: check isSupabaseConfigured and supabase
  alt Supabase not configured or supabase is null
    AuthProvider-->>UserComponent: { error: getConfigError(), needsEmailConfirmation: false }
  else Supabase configured
    AuthProvider->>SupabaseAuth: signUp(email, password, redirectUrl, metadata)
    SupabaseAuth-->>AuthProvider: { data, error }
    AuthProvider->>AuthProvider: needsEmailConfirmation = not error and not data.session
    AuthProvider-->>UserComponent: { error, needsEmailConfirmation }
  end

  UserComponent->>AuthProvider: resetPassword(email)
  AuthProvider->>SupabaseClientModule: check isSupabaseConfigured and supabase
  alt Supabase not configured or supabase is null
    AuthProvider-->>UserComponent: { error: getConfigError() }
  else Supabase configured
    AuthProvider->>SupabaseAuth: resetPasswordForEmail(email, redirectTo)
    SupabaseAuth-->>AuthProvider: { error }
    AuthProvider-->>UserComponent: { error }
  end

  UserComponent->>AuthProvider: updatePassword(password)
  AuthProvider->>SupabaseClientModule: check isSupabaseConfigured and supabase
  alt Supabase not configured or supabase is null
    AuthProvider-->>UserComponent: { error: getConfigError() }
  else Supabase configured
    AuthProvider->>SupabaseAuth: updateUser(password)
    SupabaseAuth-->>AuthProvider: { error }
    AuthProvider-->>UserComponent: { error }
  end

  UserComponent->>AuthProvider: signOut()
  AuthProvider->>SupabaseClientModule: check isSupabaseConfigured and supabase
  alt Supabase not configured or supabase is null
    AuthProvider-->>UserComponent: return without calling supabase
  else Supabase configured
    AuthProvider->>SupabaseAuth: signOut()
    SupabaseAuth-->>AuthProvider: result
    AuthProvider-->>UserComponent: void
  end
Loading

Updated class diagram for Supabase client and AuthContext types

classDiagram
  class SupabaseClientModule {
    +string SUPABASE_URL
    +string SUPABASE_PUBLISHABLE_KEY
    +boolean isSupabaseConfigured
    +string authUnavailableReason
    +SupabaseClient supabase
  }

  class SupabaseClient {
    +SupabaseAuth auth
  }

  class SupabaseAuth {
    +onAuthStateChange(handler) subscription
    +getSession() Promise
    +signInWithPassword(email, password) Promise
    +signUp(email, password, redirectUrl, metadata) Promise
    +resetPasswordForEmail(email, options) Promise
    +updateUser(password) Promise
    +signOut() Promise
  }

  class AuthContextType {
    +User user
    +Session session
    +boolean isLoading
    +boolean isConfigured
    +string authUnavailableReason
    +signIn(email, password) Promise
    +signUp(email, password, metadata) Promise
    +resetPassword(email) Promise
    +updatePassword(password) Promise
    +signOut() Promise
  }

  class AuthProvider {
    -User user
    -Session session
    -boolean isLoading
    +getConfigError() Error
    +AuthProvider(children)
  }

  SupabaseClientModule --> SupabaseClient : supabase (nullable)
  SupabaseClient --> SupabaseAuth : auth
  AuthProvider --> AuthContextType : provides
  AuthProvider --> SupabaseClientModule : uses exports isSupabaseConfigured, supabase, authUnavailableReason
Loading

File-Level Changes

Change Details Files
Make Supabase client creation conditional on presence of configuration and expose configuration status to consumers.
  • Introduce isSupabaseConfigured boolean derived from SUPABASE_URL and SUPABASE_PUBLISHABLE_KEY.
  • Introduce authUnavailableReason string to describe why auth cannot be used when misconfigured.
  • Replace import-time throw on missing Supabase config with a console warning to avoid crashing at module load.
  • Change supabase export to be nullable and only instantiate the client when configuration is valid.
src/integrations/supabase/client.ts
Harden AuthProvider and auth methods to handle missing Supabase configuration without crashing and to surface clear errors.
  • Extend AuthContextType to include isConfigured and authUnavailableReason fields so consumers can render fallbacks.
  • Short-circuit AuthProvider initialization when Supabase is unavailable, ensuring user and session are null and loading state settles.
  • Guard all auth operations (signIn, signUp, resetPassword, updatePassword, signOut) to return configuration errors or no-op when Supabase is not configured.
  • Add a helper getConfigError() that builds a readable error object based on authUnavailableReason.
  • Add defensive error handling around supabase.auth.getSession() to avoid leaving isLoading stuck on errors and normalize user/session to null.
src/hooks/useAuth.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Mar 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
gem-enterprise Ready Ready Preview, Comment, Open in v0 Mar 19, 2026 8:49am
gem-enterprise-yy99 Ready Ready Preview, Comment Mar 19, 2026 8:49am

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • Since supabase is guaranteed to be null when isSupabaseConfigured is false, you can simplify the repeated guards (if (!isSupabaseConfigured || !supabase)) to just check if (!supabase) and rely on the single source of truth from the client module.
  • authUnavailableReason is only non-null when Supabase is misconfigured, so getConfigError doesn’t need the || fallback; using authUnavailableReason! or inlining the message in client.ts as a constant would make the relationship between the two clearer and avoid redundant messaging logic.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Since `supabase` is guaranteed to be `null` when `isSupabaseConfigured` is false, you can simplify the repeated guards (`if (!isSupabaseConfigured || !supabase)`) to just check `if (!supabase)` and rely on the single source of truth from the client module.
- `authUnavailableReason` is only non-null when Supabase is misconfigured, so `getConfigError` doesn’t need the `||` fallback; using `authUnavailableReason!` or inlining the message in `client.ts` as a constant would make the relationship between the two clearer and avoid redundant messaging logic.

Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 686b665c6a

ℹ️ 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".

Comment on lines +29 to +30
})
: 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.

P1 Badge Avoid exporting a null Supabase client

Returning null here makes missing-config mode crash again in code paths that still assume supabase is always a client (for example src/pages/ResetPassword.tsx:29 calls supabase.auth.onAuthStateChange and src/pages/Contact.tsx:75 calls supabase.functions.invoke without guards). Because strictNullChecks is disabled, these call sites are not type-checked and will fail at runtime with Cannot read properties of null, which undermines the goal of a controlled fallback when env vars are absent.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment on lines +22 to +30
export const supabase = isSupabaseConfigured
? createClient<Database>(SUPABASE_URL!, SUPABASE_PUBLISHABLE_KEY!, {
auth: {
storage: localStorage,
persistSession: true,
autoRefreshToken: true,
}
})
: null;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 ResetPassword.tsx crashes at runtime when supabase is null due to incomplete null-guard migration

The PR changes supabase from always-defined to SupabaseClient | null (src/integrations/supabase/client.ts:22-30), but src/pages/ResetPassword.tsx:29 still calls supabase.auth.onAuthStateChange(...) directly inside a useEffect without a null check. Since /reset-password is a public route with no auth guard (src/App.tsx:99), navigating to it when Supabase is unconfigured causes a TypeError: Cannot read properties of null (reading 'auth'). This will crash the component (or trigger the app-level ErrorBoundary, showing a generic "Something went wrong" page). Since strictNullChecks is false in tsconfig.app.json:20, TypeScript does not catch this at compile time. CLAUDE.md explicitly requires password reset to work and demands "no silent auth failures" and a "readable auth/config failure state."

Prompt for agents
In src/pages/ResetPassword.tsx, the useEffect at line 29 calls supabase.auth.onAuthStateChange() directly without a null check. Since supabase can now be null (changed in src/integrations/supabase/client.ts:22-30), this needs a guard. Add an early return in the useEffect if supabase is null, similar to the pattern used in src/hooks/useAuth.tsx:29-34. You should also check isSupabaseConfigured before the call. The same pattern should also be applied to Contact.tsx:75 (supabase.functions.invoke) and all functions in src/hooks/useBlog.ts that call supabase.from() directly, as these are also used on public routes (/contact, /blog, /blog/:slug) and will crash the same way. For useBlog.ts, either add null guards inside each queryFn or add an `enabled: !!supabase` condition to each useQuery/useMutation call.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

support371 pushed a commit that referenced this pull request Mar 19, 2026
PR #10 branched before the onboarding redirect fix was on main,
so it carried the old ${origin}/ value. Restore to ${origin}/kyc
so email-confirmed users land on the KYC form, not the public homepage.

https://claude.ai/code/session_01Gn5FmGBZL1etB2kShwaNyX
support371 pushed a commit that referenced this pull request Mar 19, 2026
- client.ts: no longer throws at module load when env vars are missing;
  exports isSupabaseConfigured + authUnavailableReason; supabase is null
  when unconfigured (console.warn only)
- useAuth.tsx: guards all auth methods against null supabase; isLoading
  always settles (including .catch path); exposes isConfigured +
  authUnavailableReason in context; emailRedirectTo restored to /kyc

https://claude.ai/code/session_01Gn5FmGBZL1etB2kShwaNyX
@support371 support371 merged commit c7af7d7 into main Mar 20, 2026
7 of 8 checks passed
support371 pushed a commit that referenced this pull request Mar 20, 2026
…e Safari blank render

Root cause: framer-motion v12.26.2 silently fails to fire its animation
engine on mobile Safari. Every homepage element used initial={{ opacity: 0 }}
with animate={{ opacity: 1 }}, meaning ALL visible content remained invisible
when the animation engine stalled. Only the raw CSS background layers
(gradient mesh, cyber-grid, blur blobs) were visible — the reported
"dark gradient shell" symptom.

Files changed:
- HeroSection.tsx: remove framer-motion import; replace all motion.div /
  motion.h1 / motion.p with plain HTML + animate-fade-in class + inline
  animation-delay for stagger; remove unused Eye import
- BentoGrid.tsx: remove motion import; plain div on BentoCard and section
  header; all hover effects preserved via Tailwind group/CSS transitions
- StatsSection.tsx: remove motion import and framer-motion useInView;
  replace with native IntersectionObserver (threshold 0.1) to trigger the
  counter animation; remove motion.div wrappers on uptime card and stat cards
- CTASection.tsx: remove motion import; replace single motion.div CTA box
  with plain div — no animation needed on a call-to-action
- GemAssist.tsx: remove motion and AnimatePresence imports; floating button
  becomes a plain <button> with Tailwind hover:scale / active:scale;
  chat panel uses conditional render + animate-scale-in CSS class instead
  of AnimatePresence; message items use animate-fade-in

All CSS animations use keyframes already defined in tailwind.config.ts
(fade-in, scale-in) and index.css (slide-up). These are native CSS and
work reliably across all browsers including mobile Safari.

Auth/bootstrap hardening from PR #10 is untouched.

https://claude.ai/code/session_01Gn5FmGBZL1etB2kShwaNyX
support371 pushed a commit that referenced this pull request Apr 3, 2026
PR #10 branched before the onboarding redirect fix was on main,
so it carried the old ${origin}/ value. Restore to ${origin}/kyc
so email-confirmed users land on the KYC form, not the public homepage.

https://claude.ai/code/session_01Gn5FmGBZL1etB2kShwaNyX
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.

1 participant