Auth & onboarding
Registration, sign-in, password recovery, and name/university setup. Product rules: ../onboarding.md. How this hooks to checkout and dashboard: 00-how-everything-connects.md.
PAGE: /login
Purpose: Sign in with Google or email/password.
Access: public (logged-in visitors are redirected by proxy to /auth/home with safe next)
Entry points: Header Sign in; protected-route bounce with next; signup Sign in →; forgot/reset return; checkout buildLoginHref; /login?next=; /login?message=password_updated; /login?error= from /auth/callback.
SECTION: Brand panel (desktop)
Copy: Your exam is coming. Your course is ready. Course-specific study packs.
Data shown: static
ELEMENT: Gradeful (logo, aria-label Gradeful homepage)
Action: navigate
Destination: /
Writes to: none
Reads from: none
SECTION: Status banner
Copy: Password updated. Please sign in.
Data shown: query message=password_updated only
SECTION: Callback error
Copy: allowlisted human messages from lib/auth/callback-errors.ts (auth_failed, access_denied, otp_expired, …). Raw IdP errors are never shown.
Data shown: query error
SECTION: Form header
Copy: Welcome back Sign in with Google or the email you used to create your account.
Data shown: static
ELEMENT: Continue with Google
Action: submit
Destination: Google OAuth then /auth/callback?next= (safe)
Writes to: auth.users / session; new Google users also get profiles via trigger
Reads from: none
Errors: Social sign-in is unavailable. Please try again. Unable to connect. Please check your connection.
SECTION: Divider
Copy: or email
Data shown: static
SECTION: Email panel
Copy: Label Account email placeholder you@email.com. Label Password.
Data shown: form state
ELEMENT: Forgot password?
Action: navigate
Destination: /forgot-password
Writes to: none
Reads from: none
ELEMENT: Sign in
Action: submit
Destination: /auth/home?next= (safe)
Writes to: Auth session only
Reads from: none
Errors: Email or password is incorrect. Unable to connect. Please check your connection.
SECTION: Footer
Copy: Don't have an account?
ELEMENT: Sign up →
Action: navigate
Destination: /signup or /signup?next=…
Writes to: none
Reads from: next query if present
Phone OTP is not offered. Hosted Gradeful had zero phone identities.
PAGE: /signup
Purpose: Create a student account (Google or email).
Access: public (logged-in redirected by proxy)
Entry points: Login Sign up →; marketing; course page Start this course (buildSignupHref to checkout); /signup?next=.
SECTION: Form header
Copy: Create your account Google or email. Then your name and university — enough to open the right course.
Data shown: static
ELEMENT: Continue with Google
Action: submit
Destination: Google then /auth/callback?next=…
Writes to: auth.users; profiles via private.handle_new_user (role stays student)
Reads from: none
Errors: Social sign-up is unavailable. Please try again. Unable to connect. Please check your connection.
SECTION: Email signup
Copy: Label Email placeholder you@email.com. Label Password. Help Use at least 8 characters. Avoid passwords that have shown up in a data breach. Label Confirm password.
Data shown: form state
ELEMENT: Create account
Action: submit
Destination: in-place “Check your email”
Writes to: auth.users; profiles via trigger
Reads from: none
Errors: Enter a valid email address. Passwords do not match. We could not create your account. Try signing in instead. Unable to connect. Please check your connection.
SECTION: Footer
ELEMENT: Sign in →
Destination: /login or /login?next=…
SECTION: Check your email
Copy: Check your email We sent a verification link to {submittedEmail} Didn't receive it?
ELEMENT: Resend → Success: Verification email sent.
ELEMENT: Back to sign in → /login (+ optional next)
PAGE: /forgot-password Purpose: Request a password-reset email. Access: public Entry points: Login Forgot password?
SECTION: Card
Copy: Reset your password Enter your email and we'll send you a reset link. Label Email placeholder you@email.com.
ELEMENT: ← Back to login → /login
ELEMENT: Send reset link / Sending...
Destination: none (redirectTo /reset-password)
Success: Check your email for a reset link.
Errors: We could not send the reset link. Please try again. Unable to connect. Please check your connection.
PAGE: /reset-password
Purpose: Set a new password from the email recovery link.
Access: public (needs recovery session or ?code=)
Entry points: Reset email link.
SECTION: Invalid
Copy: This reset link is invalid or has expired.
ELEMENT: Request a new reset link → /forgot-password
SECTION: Ready form
Copy: Label New password. Help Use at least 8 characters. Label Confirm password.
ELEMENT: Update password / Updating...
Destination: /login?message=password_updated after signOut
Writes to: auth.users password hash
Errors: Passwords do not match. We could not update your password. Try again. Unable to connect. Please check your connection.
PAGE: /auth/callback
Purpose: Exchange OAuth/email code for a session; optional welcome email; send the student through /auth/home with safe next.
Access: public (session is created here)
Entry points: Google OAuth; email verification link; Auth redirects with ?code=&next=.
SECTION: No UI
Copy: none. On failure: /login?error={allowlisted} plus preserved safe next.
ELEMENT: none (route handler)
Action: navigate
Destination: /auth/home?next= on success; login error URL on failure
Writes to: Auth session cookies. Welcome email via Resend if profiles.created_at within 5 seconds.
Reads from: profiles.created_at, profiles.full_name
PAGE: /auth/home
Purpose: After login, send incomplete name/university profiles to /welcome?next= and everyone else to the preserved destination (course, checkout, or /dashboard).
Access: logged-in (anonymous → /login)
Entry points: Default after login/signup/OAuth; proxy redirect from /login//signup when already logged in.
SECTION: No UI
ELEMENT: none (route handler)
Action: navigate
Destination: resolveLoggedInDestination
Reads from: profiles.full_name, university, onboarding_completed_at
PAGE: /welcome
Purpose: Required onboarding only: who are you (name) and which university offering (AUB/LAU). Preserve next.
Access: logged-in; incomplete onboarding. Anonymous → /login?next=/welcome. Already complete → preserved next, /dashboard if active access, otherwise /.
Entry points: /auth/home; workspace layout; checkout; direct URL.
SECTION: Brand panel (desktop)
Copy: Your university. Your courses. Twenty seconds. Then you're in — not a marketing page.
SECTION: Intro
Copy: Almost in Tell us where you study. Name, university, major. We use this to show the right packs — nothing to verify here.
SECTION: Who are you?
Copy: Label Your name placeholder Maya Haddad. How Gradeful greets you — not a public username.
SECTION: What course do you need?
Copy: Which university? AUB and LAU use different course codes. This picks the matching offering.
ELEMENT: AUB / LAU cards
If next is checkout/course: We'll send you back to checkout for the course you already picked. / We'll send you back to that course.
If homepage: After this you can pick a course. Major and campus can wait. plus continue to /courses.
SECTION: Submit
Copy: Continue to checkout / Continue to your course / Find a course
Writes to: profiles.full_name, university, onboarding_completed_at (optional campus/major if present)
Toasts: Add the name you use at uni. Pick AUB or LAU so we show the right packs. Could not save that. Try again.
Major, minors, and LAU campus are not on this form.