Skip to content

fix(#312): show email confirmation pending state after signup#317

Merged
ZappoMan merged 5 commits into
developfrom
fix/312-signup-pending-ux
May 21, 2026
Merged

fix(#312): show email confirmation pending state after signup#317
ZappoMan merged 5 commits into
developfrom
fix/312-signup-pending-ux

Conversation

@diego-artificerinnovations
Copy link
Copy Markdown
Contributor

@diego-artificerinnovations diego-artificerinnovations commented May 21, 2026

Summary

Fixes #312. After auth.signUp(), Supabase may not create a session if email confirmation is required. The old code silently redirected users to /dashboard (web) or Dashboard (mobile) as if signup succeeded — which looked like a broken login.

  • Web (SignupPage.tsx): Remove the else { navigate('/dashboard') } branch. All no-session signups now show the existing awaitingEmail pending UI. Billing-specific copy ("we'll take you to billing") is conditionalized on paidIntent.
  • Mobile (SignupScreen.tsx): Call supabase.auth.getSession() after signup; navigate to new SignupPendingScreen when no session, Dashboard when session is present.
  • New SignupPendingScreen.tsx: Displays email, instruction copy, and "Already confirmed? Sign in" link. Registered in AppNavigator and typed in RootStackParamList.
  • Tests: Web test rewritten to expect pending UI (not navigate to dashboard); two mobile tests split to cover both session / no-session paths.

Test plan

  • Plain signup on web with email confirmation on → should see "Check your email" screen, no navigation to dashboard
  • Paid-plan signup on web with no session → should see "Check your email" with billing copy
  • Session-returning signup on web → should navigate to dashboard as before
  • Mobile signup with email confirmation on → should navigate to SignupPending screen with email displayed
  • Mobile signup returning a session → should navigate to Dashboard
  • "Already confirmed? Sign in" link on SignupPendingScreen navigates to Login
  • All existing tests pass: npm run test in apps/web (18/18 SignupPage tests) and apps/mobile (231/231 tests)

Web: remove the else-navigate bug that sent users to /dashboard before
their email was confirmed. All no-session signups now show the
awaitingEmail pending UI. Billing-specific copy is conditionalized.

Mobile: check getSession() after signUp(); navigate to new
SignupPendingScreen when no session, Dashboard when session exists.

Also adds SignupPendingScreen, registers it in AppNavigator, and
updates RootStackParamList and tests on both platforms.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

CI Coverage & Test Summary

Metric Coverage Covered / Total
Statements 99.54% 17259 / 17338
Branches 95.11% 4298 / 4519
Functions 99.02% 708 / 715
Lines 99.58% 17228 / 17301

Suites: 43 passed, 0 failed (43 total) · Tests: 505 passed, 0 failed (505 total)

✅ All reported test suites passed.

Coverage artifacts: coverage-summary, coverage-packages.


Updated at: May 21, 2026 at 2:47 PM PDT

Comment thread apps/web/src/pages/SignupPage.tsx
Comment thread apps/web/src/pages/SignupPage.tsx Outdated
Comment thread apps/mobile/src/screens/SignupScreen.tsx Outdated
Comment thread apps/web/src/pages/__tests__/SignupPage.test.tsx
Comment thread apps/mobile/src/screens/SignupScreen.tsx
Comment thread apps/mobile/src/screens/SignupPendingScreen.tsx
Comment thread apps/web/src/pages/__tests__/SignupPage.test.tsx Outdated
Comment thread apps/mobile/src/screens/SignupPendingScreen.tsx Outdated
Comment thread apps/mobile/__tests__/screens/SignupScreen.test.tsx
Comment thread apps/mobile/src/screens/SignupPendingScreen.tsx
Comment thread apps/mobile/__tests__/screens/SignupScreen.test.tsx
Copy link
Copy Markdown
Contributor

@ZappoMan ZappoMan left a comment

Choose a reason for hiding this comment

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

Review summary

Solid fix for #312. Diego's implementation matches the plan we discussed and correctly addresses the root cause on both platforms.

What works well

  • Web: Consolidated no-session branch — all email signups without a session show the existing awaitingEmail UI; billing redirect stash is preserved only for paid intent.
  • Web copy: Conditional billing sentence avoids the awkward em-dash-only fragment for plain signup.
  • Mobile: getSession() check + new SignupPendingScreen mirrors web behavior.
  • Tests: Web test rewritten correctly (18/18 pass); mobile tests split for session/no-session paths (10/10 pass). Mock strategy for supabase.auth.getSession is correct.

Suggested before/after merge (non-blocking)

  1. Add AppHeader to SignupPendingScreen — every other auth screen has it; web pending view does too.
  2. Drop unnecessary route?.params?. optional chaining — types already guarantee params.
  3. Import shared RootStackParamList in SignupScreen instead of local duplicate.
  4. Minor: smart apostrophe consistency in billing copy string.

Follow-up (out of scope)

  • Mobile emailRedirectTo + native confirm deep link (confirmation emails currently land on web /auth/confirm)
  • Short docs note in EMAIL_TEMPLATES.md per issue AC

Verdict: Approve — nothing blocking merge from my side. Left inline comments on the specific lines.

Copy link
Copy Markdown
Contributor

@mei-artificerinnovations mei-artificerinnovations left a comment

Choose a reason for hiding this comment

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

Core logic is correct and the test split (session vs no-session) is clean. Two things to resolve before merge.

⚠️ Conflict with #316 (SignupPage.tsx)

PR #316 (my kit-sync plan_id pipeline fix) also modifies apps/web/src/pages/SignupPage.tsx — specifically handleSignup. It adds plan_id to the auth.signUp call but did not pick up this PR's fix (it still has the silent navigate('/dashboard') for the no-session case). The two PRs need to land with a merged handleSignup:

const planId = searchParams.get('plan');
await auth.signUp(email, password, planId ? { data: { plan_id: planId } } : undefined);
const { data: { session } } = await supabase.auth.getSession();
if (session) {
  clearPostAuthRedirectKeys();
  navigate(postAuthPath, { replace: true });
} else {
  if (paidIntent && postAuthPath !== '/dashboard') {
    localStorage.setItem(POST_AUTH_REDIRECT_KEY, serializePostAuthRedirectPayload(postAuthPath));
  }
  setAwaitingEmail(true);
}

I'll update #316 with this merged version once we know merge order. Either way one of us needs to pick up the other's change before Brad merges.

Nit: SignupPending needs a header title in AppNavigator

Without options, RN Stack will show the literal string "SignupPending" as the header title. Either add options={{ title: 'Confirm your email' }} or options={{ headerShown: false }} if the screen handles its own header.

Comment thread apps/mobile/src/screens/SignupScreen.tsx Outdated
Comment thread apps/mobile/src/navigation/AppNavigator.tsx Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

Component Status Link
🌐 Web ✅ Deployed Preview →
🗄️ Database ✅ Migrated
💳 Billing ✅ Deployed
📱 Mobile ✅ Deployed

Last deployed: 14:47 PDT


📱 Mobile preview: Channel pr-317

📱 Mobile Preview (OTA Updates)

No native code changes detected - using OTA updates only.

⚠️ Note: This app requires a Development Build (Expo Go will not work due to native Google OAuth).

Step 1: Install Development Build (one-time setup)

  1. Build: npm run mobile:build:dev:ios (or mobile:build:dev:android)
  2. Download from Expo dashboard
  3. Install: EAS_BUILD_PATH=~/Downloads/BeakerStack.ipa npm run mobile:install:dev:ios (iOS) or EAS_BUILD_PATH=~/Downloads/BeakerStack.apk npm run mobile:install:dev:android (Android)

Step 2: Load PR Preview Update

  1. Open the development build on your device/simulator
  2. Shake device (or Cmd+D on iOS / Cmd+M on Android) → "Enter URL manually"
  3. Paste the update URL: https://u.expo.dev/23c5e522-5341-4342-85f5-f2e46dd6087f?channel-name=pr-317
  4. The app will reload with the JavaScript bundle from channel pr-317

Note: You must use the full URL format - just entering the channel name (pr-317) will not work.

Alternative: For local development, use: cd apps/mobile && npx expo start --dev-client, then press 'i' for iOS simulator.

📖 See Mobile Build Testing Guide for detailed instructions.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

DB & Integration Test Summary

Check Status Details
Migration filename format ✅ Pass
Database tests (pgTAP) ✅ Pass 10/10 files · 141/141 assertions
Integration tests (Jest) ✅ Pass 9/9 suites · 50/51 tests
Live Supabase client (Vitest) ✅ Pass 1/1 files · 1/1 tests

Full logs: db-tests-log, integration-log artifacts.


Updated at: May 21, 2026 at 2:49 PM PDT

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes post-signup UX when Supabase email confirmations are enabled and auth.signUp() does not immediately create a session, by showing an explicit “check your email” pending state instead of navigating to authenticated areas.

Changes:

  • Web: Treat all “no session after signup” outcomes as an email-confirmation-pending state (and only stash billing redirect keys when a paid intent exists).
  • Mobile: After signup, check session and route either to Dashboard (session present) or a new SignupPending screen (no session).
  • Tests: Update/add web + mobile tests to cover both session/no-session branches and verify the pending UI behavior.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
apps/web/src/pages/SignupPage.tsx Removes dashboard redirect on no-session signup and adjusts pending UI copy to be conditional for paid intent.
apps/web/src/pages/tests/SignupPage.test.tsx Updates expectations to assert pending UI for no-session signup and adds a test for non-billing copy on plain signup.
apps/mobile/src/screens/SignupScreen.tsx Adds session check after signup and navigates to SignupPending when no session exists.
apps/mobile/src/screens/SignupPendingScreen.tsx New screen to display email confirmation pending instructions with a link to sign in.
apps/mobile/src/navigation/types.ts Adds SignupPending to the root stack param list typing.
apps/mobile/src/navigation/AppNavigator.tsx Registers the new SignupPending screen in the navigator.
apps/mobile/tests/screens/SignupScreen.test.tsx Splits “successful signup” coverage into session vs no-session navigation paths by mocking getSession().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/web/src/pages/SignupPage.tsx Outdated
Comment thread apps/mobile/src/screens/SignupScreen.tsx Outdated
Comment thread apps/mobile/src/screens/SignupScreen.tsx
- Web: split billing copy into own <p>, use &apos; entities (no smart quotes)
- Web test: merge two pending tests into one; add localStorage null assertion
- Mobile: import shared RootStackParamList in SignupScreen (remove local dup)
- Mobile: add AppHeader to SignupPendingScreen for parity with other screens
- Mobile: use route.params.email (remove defensive optional chaining)
- Mobile: add options={{ title: 'Confirm your email' }} to SignupPending screen
- Mobile: new SignupPendingScreen.test.tsx (heading, email, Login navigation)
- Mobile: mock SignupPendingScreen in App.test.tsx and AppNavigator.test.tsx
Copy link
Copy Markdown
Contributor

@ZappoMan ZappoMan left a comment

Choose a reason for hiding this comment

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

Re-review (post feedback commit e4da5c8)

Diego addressed all review feedback from the first pass. Re-reviewed the latest diff and ran tests locally.

Verified fixes

  • Web: consolidated no-session branch + split billing copy into separate <p> with &apos; entities
  • Web tests: merged pending test + localStorage null assertion (17/17 pass)
  • Mobile: shared RootStackParamList import, AppHeader on pending screen, screen title option, SignupPendingScreen tests, navigator mocks
  • CI: all checks green; PR is mergeable

Follow-up issues filed

Thread Issue
RootStackParamList cleanup (other screens) #318
SignupScreen useEffect double navigation #319
Native mobile confirm deep link + emailRedirectTo #320
Auth context vs singleton getSession #321
Docs for post-signup pending UX #322

Linked #318#321 on the relevant inline threads.

Blockers

None for this PR. Ready to merge.

Coordination note (not a git conflict): PR #316 also touches SignupPage.tsx (adds plan_id to signup metadata). Whichever lands second should rebase — functionally both changes belong in the same handleSignup. Git merge state is currently clean.

@ZappoMan
Copy link
Copy Markdown
Contributor

Additional follow-ups from the re-review (no specific inline thread):

@ZappoMan ZappoMan merged commit b8c48d9 into develop May 21, 2026
40 checks passed
ZappoMan pushed a commit that referenced this pull request May 22, 2026
…onfig guards (#316)

Clean rebase onto develop (b8c48d9), incorporating #317 and #298 without regressions.

- deploy: gate KIT_API_KEY / KIT_CRON_SECRET with [[ -n ]] guards; preserve all #298
  Resend sync step and develop/main branch if-conditions
- SignupPage: signupPlanMetadata() validates plan before auth.signUp;
  waitlistCaptureMetadata restored to include plan_id alongside plan_interest
- useAuth.signUp: forward options.data to supabase auth.signUp
- waitlist-capture: runtime typeof string guard on plan_id before hoisting
- kit-sync: typeof string guards on all payload.plan_id reads; namespace error
  message updated (removes nonexistent script reference); user.tier_changed
  dead-letters when tierTagNames is empty
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.

4 participants