refactor: Convert Supabase Auth from library UI to custom components#1969
refactor: Convert Supabase Auth from library UI to custom components#1969
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Preview build of published Zudoku package for commit 979cc50. See the deployment at: https://996d9849.cosmocargo-public-package.pages.dev Note This is a preview of the Cosmo Cargo example using the Zudoku package published to a local registry to ensure it'll be working when published to the public NPM registry. Last updated: 2026-02-13T10:46:09.647Z |
Replace @supabase/auth-ui-react with custom UI components matching the Firebase Auth implementation. This provides: - Consistent UX across all auth providers using shared ZudokuAuthUi - Direct Supabase client API calls for all auth operations - Custom error message mapping for better user experience - Support for email/password sign in/up, OAuth, password reset, and email verification flows Changes: - Update supabase.tsx to use ZudokuSignInUi, ZudokuSignUpUi, ZudokuPasswordResetUi, and EmailVerificationUi components - Add getSupabaseErrorMessage function for user-friendly error messages - Remove SupabaseAuthUI.tsx and @supabase/auth-ui-* dependencies https://claude.ai/code/session_01Hq3vMiJDcKrU8NSHyvmdZS
82deeb2 to
1c983d5
Compare
There was a problem hiding this comment.
Pull request overview
This PR refactors the Supabase authentication provider to stop relying on @supabase/auth-ui-react and instead use the shared custom auth UI components already used by the Firebase provider, aiming for consistent UX and more control over auth flows and errors.
Changes:
- Removed
@supabase/auth-ui-react/@supabase/auth-ui-sharedusage and deleted theSupabaseAuthUIwrapper component. - Updated the Supabase auth provider routes to use
ZudokuSignInUi,ZudokuSignUpUi,ZudokuPasswordResetUi, andEmailVerificationUi. - Added Supabase-specific error message mapping via
getSupabaseErrorMessage.
Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| pnpm-lock.yaml | Removes lock entries for Supabase Auth UI deps from the zudoku package (still present elsewhere in the repo). |
| packages/zudoku/src/lib/authentication/providers/supabase/SupabaseAuthUI.tsx | Deletes the Supabase Auth UI wrapper that depended on @supabase/auth-ui-*. |
| packages/zudoku/src/lib/authentication/providers/supabase.tsx | Switches Supabase auth routes to shared custom UI, adds email verification + password reset wiring, and introduces Supabase error mapping. |
| packages/zudoku/package.json | Removes optional deps on @supabase/auth-ui-react and @supabase/auth-ui-shared. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Support both 'provider' (deprecated) and 'providers' config | ||
| const configuredProviders = config.provider | ||
| ? [config.provider] | ||
| : (config.providers ?? []); | ||
| this.providers = configuredProviders; | ||
| this.enableUsernamePassword = !config.onlyThirdPartyProviders; |
There was a problem hiding this comment.
config.providers for Supabase is currently an unvalidated string[], but ZudokuSignInUi/ZudokuSignUpUi will throw at runtime for any provider ID not in its supported list (e.g., Supabase docs commonly use azure, discord, etc.). To avoid breaking existing Supabase configurations, either validate/map the configured providers to what the UI supports (with a clear error message), or extend the shared auth UI to render unsupported provider IDs generically instead of throwing.
| options: { | ||
| emailRedirectTo: `${window.location.origin}${this.config.basePath ?? ""}/verify-email`, | ||
| }, | ||
| }); | ||
| useAuthState.setState({ isPending: false }); | ||
| if (error) { | ||
| throw Error(getSupabaseErrorMessage(error), { cause: error }); | ||
| } | ||
|
|
||
| // If user exists and is confirmed, update state | ||
| if (data.user) { | ||
| const profile: UserProfile = { | ||
| sub: data.user.id, | ||
| email: data.user.email, | ||
| name: data.user.user_metadata.full_name || data.user.user_metadata.name, | ||
| emailVerified: data.user.email_confirmed_at != null, | ||
| pictureUrl: data.user.user_metadata.avatar_url, | ||
| }; | ||
|
|
||
| useAuthState.getState().setLoggedIn({ | ||
| profile, | ||
| providerData: { session: data.session }, | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
| private onOAuthSignIn = async (providerId: string) => { | ||
| useAuthState.setState({ isPending: true }); | ||
| const { error } = await this.client.auth.signInWithOAuth({ | ||
| provider: providerId as Provider, | ||
| options: { | ||
| redirectTo: | ||
| this.config.redirectToAfterSignIn ?? | ||
| `${window.location.origin}${this.config.basePath ?? ""}`, | ||
| }, | ||
| }); | ||
| if (error) { | ||
| useAuthState.setState({ isPending: false }); | ||
| throw new AuthorizationError(error.message); | ||
| } | ||
| // Note: OAuth sign-in redirects the page, so isPending stays true | ||
| }; | ||
|
|
||
| private onPasswordReset = async (email: string) => { | ||
| const { error } = await this.client.auth.resetPasswordForEmail(email, { | ||
| redirectTo: `${window.location.origin}${this.config.basePath ?? ""}/reset-password`, | ||
| }); |
There was a problem hiding this comment.
Building redirect URLs via string concatenation with this.config.basePath (e.g. ${window.location.origin}${this.config.basePath ?? ""}/verify-email) is brittle if basePath is configured without a leading slash or with a trailing slash. Prefer using the existing joinUrl helper (used by other auth providers) to compose these URLs safely and consistently.
| // If user exists and is confirmed, update state | ||
| if (data.user) { | ||
| const profile: UserProfile = { | ||
| sub: data.user.id, | ||
| email: data.user.email, | ||
| name: data.user.user_metadata.full_name || data.user.user_metadata.name, | ||
| emailVerified: data.user.email_confirmed_at != null, | ||
| pictureUrl: data.user.user_metadata.avatar_url, | ||
| }; | ||
|
|
||
| useAuthState.getState().setLoggedIn({ | ||
| profile, | ||
| providerData: { session: data.session }, | ||
| }); | ||
| } |
There was a problem hiding this comment.
onUsernamePasswordSignUp calls setLoggedIn whenever data.user exists, but Supabase often returns data.user with data.session === null when email confirmation is required. That would mark the user as authenticated in Zudoku while they have no session/access token, causing protected-route flows and signRequest to fail. Only set logged-in state when a valid session exists (or after confirmation), and otherwise route the user into the email verification flow.
| private onPasswordReset = async (email: string) => { | ||
| const { error } = await this.client.auth.resetPasswordForEmail(email, { | ||
| redirectTo: `${window.location.origin}${this.config.basePath ?? ""}/reset-password`, | ||
| }); | ||
| if (error) { | ||
| throw Error(getSupabaseErrorMessage(error), { cause: error }); | ||
| } |
There was a problem hiding this comment.
resetPasswordForEmail is configured to redirect users back to /reset-password after they click the email link, but the /reset-password route renders ZudokuPasswordResetUi, which only requests a reset email and doesn't handle the recovery callback / setting a new password (e.g. via auth.updateUser({ password })). This makes the Supabase password reset flow incomplete; add a dedicated recovery/new-password UI for the redirect or enhance this route to handle the recovery session/code.
… error Add check for typeof window === "undefined" to handle server-side rendering where window is not available. Also update ZudokuSignUpUi to use getRelativeRedirectUrl consistently instead of direct window.location.origin access. https://claude.ai/code/session_01Hq3vMiJDcKrU8NSHyvmdZS
- Use joinUrl helper for URL construction instead of string concatenation for safer path joining with basePath configuration - Add ZudokuPasswordUpdateUi component for handling password recovery callback flow (users clicking reset link now see "set new password" form instead of "enter email" form) - Add /update-password route and onPasswordUpdate method https://claude.ai/code/session_01Hq3vMiJDcKrU8NSHyvmdZS
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing touches🧪 Generate unit tests (beta)✅ Unit Test PR creation complete.
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. Comment |
|
Note Unit test generation is a public access feature. Expect some limitations and changes as we gather feedback and continue to improve it. Generating unit tests... This may take up to 20 minutes. |
|
✅ Created PR with unit tests: #1971 |
Replace @supabase/auth-ui-react with custom UI components matching the Firebase Auth implementation. This provides:
Changes:
https://claude.ai/code/session_01Hq3vMiJDcKrU8NSHyvmdZS