Skip to content

Redesign onboarding as a visible two-step sequence - #22

Merged
Badri-Rajendran merged 2 commits into
mainfrom
feature_onboarding_page_redesign
Sep 7, 2026
Merged

Badri-Rajendran merged 2 commits into
mainfrom
feature_onboarding_page_redesign

Conversation

@Badri-Rajendran

Copy link
Copy Markdown
Owner

Problem

The onboarding page swapped its entire contents based on KYC status — you only ever saw one step at a time, with no sense of the whole journey. Two real functional gaps came with that structure:

  • A rejected KYC attempt was indistinguishable from never having started. The backend has a real rejected status; the frontend's isKycApproved ? BankLinkSection : KycStep branch treated it identically to pending, showing the same first-time prompt with no explanation of what happened.
  • Once both steps were done, the page just sat there — no next action toward the thing onboarding was actually for (choosing a portfolio).

Implementation

  • New OnboardingSteps/StepRow (features/onboarding/components/): both steps render together now, connected by a rail, using the exact numbered-circle idiom already established on the signup page's brand panel (SignupBrandPanel.jsx, PR Redesign the signup page as a split-screen brand moment #21) — the same real sequence, continued, not a new decorative device. A step's marker is a numbered circle (current), a muted outline (locked), or a checkmark (completed) — color is never the only signal.
  • KycStep now takes the customer's real stored kycStatus (not just its own local in-progress-attempt state) and shows a distinct rejection message with a "Try again" label instead of the generic first-time prompt.
  • BankLinkStep is locked (no interactive content, no wasted Plaid link-token fetch) until KYC clears.
  • Once both are done, a line below the card offers the real next step: "View portfolio options", linking straight to /portfolio.
  • BankLinkStep's heading is now conditional (showTitle, default true) — OnboardingPage suppresses it since StepRow supplies the title, but FundingPage's own usage (a bare Card with no title of its own) needed it kept, which a first pass at this missed. Caught and fixed via a direct screenshot comparison before committing, not assumed safe from reading the diff.

Validation

Verified against the real running app, not the diff: all four reachable states (never started, rejected, KYC-done/bank-pending, both complete) exercised via direct DB manipulation of a real registered test account — including generating a validly-encrypted placeholder Plaid token through the app's own cipher rather than a raw byte string, since the encrypted column round-trips through decryption even when the field is never serialized. Confirmed light and dark themes (dark mode's completion button correctly inverts), 375px mobile, the completion link's real navigation to /portfolio, and a regression screenshot confirming FundingPage's bank-link card kept its heading. npm run lint and npm run build clean.

Security / DB / API impact

  • Security: none.
  • DB: none — no schema change, reads the same kyc_status/account_approval_status/bank-link fields already fetched today.
  • API: none — no new route, request, or response shape.

🤖 Generated with Claude Code

Badri-Rajendran and others added 2 commits September 6, 2026 17:14
/register was a bare centered form on ivory, shared with /login, with
zero brand presence -- the product's actual front door (no marketing
route exists) looked identical to every other unauthenticated screen.

Adds:
- A split-screen layout (form left, brand panel right, stacking below
  768px) via a new `split` variant on the existing AuthLayout -- /login
  is untouched, still the plain centered column.
- SignupBrandPanel: a deliberate one-time reversal of the app's normal
  ink-on-paper (uses --color-text/--color-surface directly, so it still
  reads as a reversal in dark mode rather than flattening into it), the
  serif "Trueup" wordmark on its first public-page appearance, real
  grounded copy (four model portfolios, monthly rebalancing) instead of
  generic fintech copy, and the genuine three-step sequence -- create
  account, verify identity, link bank -- numbered because it actually is
  one, with the current step marked via the system's sanctioned
  "current selection" use of the accent color.
- A password visibility toggle, via a new optional `labelExtra` slot on
  the shared Input component (purely additive -- every other caller is
  unaffected, verified by screenshot).
- A segmented password-strength meter driven by the exact same
  describePassword() the checklist already used -- no new fabricated
  scale -- as the page's one deliberate interactive/animated moment,
  respecting the app's existing global reduced-motion rule.
- A real fix for the duplicate-email case: the backend now raises
  ValidationError(..., code="email_taken") instead of the shared
  validation_failed code, so the form can show "This account already
  exists -- log in instead." with a working link, instead of the
  generic "check your details" message the dead-code fallback string
  was silently never reaching.

Deliberately not added: a Terms-of-Service checkbox (no such page/legal
content exists yet in the app -- would mean inventing legal copy) and
any specific regulatory disclosure claims (kept the one trust line to
something true by construction).

Verified: backend 24/24 auth tests pass (including the updated
duplicate-email assertion); npm run lint and build clean; full manual
pass in the browser -- real registration through the running local
backend, the duplicate-email path end to end with the new message and
link, password toggle and strength meter both interactive, light and
dark themes (dark mode's inversion checked explicitly, panel correctly
flips light-on-dark), mobile width (375px, panel correctly hidden,
single column), and a regression screenshot confirming /login (which
doesn't use the new labelExtra prop) is pixel-identical to before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The onboarding page swapped its entire contents based on KYC status --
you only ever saw one step at a time, with no sense of the whole
journey, and two real functional gaps came with that structure:

- A rejected KYC attempt was indistinguishable from never having
  started. The backend has a real `rejected` status; the frontend's
  `isKycApproved ? BankLinkSection : KycStep` branch treated it
  identically to `pending`, showing the same first-time prompt with no
  explanation of what happened.
- Once both steps were done, the page just sat there -- no next action
  toward the thing onboarding was actually for (choosing a portfolio).

Adds OnboardingSteps/StepRow (features/onboarding/components/): both
steps render together now, connected by a rail, using the exact
numbered-circle idiom already established on the signup page's brand
panel (SignupBrandPanel.jsx) -- the same real sequence, continued, not
a new decorative device. A step's marker is a numbered circle
(current), a muted outline (locked), or a checkmark (completed) --
color is never the only signal.

KycStep now takes the customer's real stored `kycStatus` (not just its
own local in-progress-attempt state) and shows a distinct rejection
message with a "Try again" label instead of the generic first-time
prompt. BankLinkStep is locked (no interactive content, no wasted
Plaid link-token fetch) until KYC clears. Once both are done, a line
below the card offers the real next step: "View portfolio options",
linking straight to /portfolio.

BankLinkStep's heading is now conditional (`showTitle`, default true)
-- OnboardingPage suppresses it since StepRow supplies the title, but
FundingPage's own usage (a bare Card with no title of its own) needed
it kept, which a first pass at this missed and would have shipped
title-less. Caught and fixed via a direct screenshot comparison before
committing, not asserted from reading the diff.

Verified against the real running app, not the diff: all four
reachable states (never started, rejected, KYC-done/bank-pending, both
complete) exercised via direct DB manipulation of a real registered
test account -- including generating a validly-encrypted placeholder
Plaid token through the app's own cipher rather than a raw byte string,
since the encrypted column round-trips through decryption even when
the field is never serialized. Confirmed light and dark themes (dark
mode's completion button correctly inverts), 375px mobile, and the
completion link's real navigation to /portfolio. npm run lint and
build clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 7, 2026 00:42

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Badri-Rajendran
Badri-Rajendran merged commit 7f32337 into main Sep 7, 2026
6 checks 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