Files
carmanagement/docs/progressive-signup-plan.md
root f22e0d45e1
Build & Deploy / Build & Push Docker Image (push) Successful in 7m57s
Test / Type Check (all packages) (push) Successful in 4m27s
Build & Deploy / Deploy to VPS (push) Successful in 7s
Test / API Unit Tests (push) Failing after 3m2s
Test / Homepage Unit Tests (push) Successful in 4m3s
Test / Storefront Unit Tests (push) Successful in 3m32s
Test / Admin Unit Tests (push) Successful in 3m27s
Test / Dashboard Unit Tests (push) Successful in 3m3s
Test / API Integration Tests (push) Failing after 3m53s
fix subscription page
2026-06-29 23:15:55 -04:00

12 KiB

Progressive Account Creation — Redesign Plan

1. Problem Statement

The current sign-up flow (apps/dashboard/src/app/sign-up/[[...sign-up]]/page.tsx) asks for everything at once:

  • Owner identity & credentials
  • Full legal/company identity (RC, ICE, IF, license, issuing authority…)
  • Company contact & address (bilingual)
  • Responsible person details
  • Plan + billing + payment provider

That's ~30 required fields across 4 steps before a user has even seen the product. This is the classic cause of sign-up abandonment: the perceived effort to "just try the app" is too high.

Goal: Let someone create an account with almost nothing, start exploring the dashboard immediately, and only be asked for a specific piece of information right when an action requires it — never before.

This pattern is commonly called progressive profiling or just-in-time (JIT) data collection.


2. Core Principle

Collect the minimum data needed to create an identity. Collect everything else lazily, tied to the action that needs it, not to a step number.

Two reframes that drive the whole design:

  1. Stop thinking in "steps." Replace the 4-step wizard with a single account state machine. The account always exists in some state (Created → Active → Listing-Ready → Payable → Compliant → Subscribed), and each state unlocks more of the product.
  2. Stop thinking in "forms." Replace big forms with micro-prompts: a single field, or a tiny group of 2-3 tightly related fields, asked contextually, usually as a modal/drawer over the action the user just tried to take.

3. New Sign-up Flow (Minimum Viable Signup)

Step 0 — only step that blocks entry

Field Why it's required immediately
Owner email Login identifier, needed to send confirmation
Password Needed to create a session
Preferred language Needed to render anything afterward correctly

That's it. No name, no company, no legal data, no plan.

POST /api/v1/auth/account/start
{
  "email": "string",
  "password": "string (8-128)",
  "preferredLanguage": "en | fr | ar"
}

Response creates:

  • A User record (owner)
  • A Company record in a DRAFT state with name = null and a system-generated placeholder slug
  • A session/JWT so the user is logged in immediately

The user lands directly in the dashboard home, in a clearly "draft workspace" visual state (banner: "Your workspace is being set up — finish anytime").

This single change — going from ~30 required fields to 3 — is the biggest win and should be shipped first even before the rest of the JIT system exists.


4. Account Maturity Model

Replace the rigid 4-step form with an explicit состояние/state on the Company record:

State Unlocked by What it unlocks
DRAFT Step 0 above Browsing dashboard UI, exploring settings, reading docs
IDENTIFIED Owner first/last name + company display name Personalizing UI, inviting teammates
LISTABLE Company contact info + address Creating/saving a fleet item or offer as draft
PUBLISHABLE Legal identity block (legal name, legal form, RC, ICE, IF, license) Publishing a listing live on the storefront
PAYABLE Payment provider config (AmanPay/PayPal) + responsible person Accepting bookings/payments
SUBSCRIBED Plan + billing period selection Past the 30-day trial, billed account

Each state is just a derived boolean/computed field (companyState) based on which fields are filled — not a separate workflow table. The UI reads this state to decide what to show/lock.

This also directly maps the existing legal/compliance fields (RC, ICE, IF, operating license) to the one moment they actually matter: right before something goes live on the storefront. Today they're collected on day 1, before the user has even decided to publish anything.


5. Just-In-Time Prompt Catalog

Map every current form field to the specific user action that should trigger asking for it. This is the heart of the redesign — a table the eng + product team should review and tune together.

Action the user takes Data requested at that moment Fields involved
First login after signup Display name Owner first/last name, company commercial name
Click "Add a vehicle / offer" Basic company contact Phone, company email, address, city, country, zip
Click "Publish" on a listing Legal identity Legal name, legal form, RC, ICE, IF, license #, issue date, issuing authority
Click "Enable bookings" / "Connect payments" Payment + responsible person Payment provider, responsible person name/role/ID/phone/email
Visit /subscription or trial reaches day 75 Plan selection Plan, billing period
Click "Invite teammate" (no new data)
Optional, never blocking Fax, legal representative name/title, qualification/diploma These stay fully optional, surfaced only in Settings

Design rule: never ask for more than 1 group of tightly-related fields in a single prompt. E.g. "Legal identity" can still be ~8 fields, but it's one cohesive concept the user understands ("info needed to verify your business"), not an arbitrary step number.


6. UX Pattern: Contextual Prompt, Not a Form Page

When a user triggers an action that needs missing data:

  1. The action is allowed to start optimistically (e.g. they can build a full listing draft).
  2. On the actual gating action (Publish, Enable bookings…), intercept with a slide-over panel or modal, not a full-page redirect.
  3. The modal:
    • States why in one line: "We need your business registration details before this listing can go public."
    • Shows only the missing fields (if some were already filled earlier, don't ask again).
    • Has a "Save and publish" primary action, and a "Save as draft, finish later" secondary action.
  4. On submit, persist via a generic incremental-update endpoint, recompute companyState, and continue the original action automatically.

This keeps the user inside their task instead of bouncing them to a separate "complete your profile" flow.

A persistent, low-key "Workspace setup: 60% complete" indicator (e.g. in the sidebar) lets motivated users finish everything in one sitting if they prefer — progressive disclosure should never block someone who wants to do it all at once.


7. Backend Changes

7.1 Schema

  • Make nearly all Company fields nullable (legalName, registrationNumber, iceNumber, etc. all become optional at the DB level — required-ness becomes a business rule per action, not a DB constraint).
  • Add companyState (computed/cached enum) or compute on read.
  • Add profileCompletionPercent for the UI progress indicator.

7.2 API

Replace the single big POST /auth/company/signup with:

POST /api/v1/auth/account/start          → minimal signup (Section 3)
PATCH /api/v1/company/profile            → generic partial update, accepts any subset of fields
PATCH /api/v1/company/legal-identity      → group endpoint for legal fields (own validation rules)
PATCH /api/v1/company/payment-setup       → group endpoint for payment + responsible person
PATCH /api/v1/company/plan                → plan/billing selection
GET   /api/v1/company/state               → returns companyState + missing fields per state

Each PATCH endpoint validates only its own slice with its own Zod schema (split auth.company.schemas.ts into per-group schemas), instead of one monolithic companySignupSchema.

7.3 Gating middleware

Add a small middleware/guard used on the actions that require a given state, e.g.:

requireCompanyState("PUBLISHABLE") // on POST /listings/:id/publish
requireCompanyState("PAYABLE")     // on POST /bookings, /payments/connect

If the guard fails, return a structured error listing exactly which fields are missing, so the frontend can render the right contextual modal without guessing.

7.4 Email confirmation

Confirmation email still sends immediately after Step 0, but its copy changes from "your account is ready" to "your account is ready — finish setup anytime to start publishing," since there's much less to confirm.


8. Frontend Changes

  • Replace the 937-line multi-step page with:
    • A short ~/sign-up page (Step 0 only).
    • A set of small, reusable "completion modals," one per group in Section 5, each built from the existing BilingualInput and form components (these don't need to be rebuilt, just reorganized).
  • Introduce a useCompanyState() hook that fetches GET /company/state and exposes {state, missingFields, completionPercent} to any component that needs to gate an action.
  • Add a generic <RequireCompanyState state="PUBLISHABLE"> wrapper component used to gate the relevant buttons/actions, opening the right modal when triggered.

9. Edge Cases & Risks

Risk Mitigation
Users abandon before ever reaching PUBLISHABLE/PAYABLE That's expected and fine — they were never going to finish the old form either. Track funnel by state instead of all-or-nothing.
Storefront ends up with unpublishable "ghost" companies DRAFT/IDENTIFIED companies are simply never shown publicly — only PUBLISHABLE+ companies appear, so this is a non-issue by construction.
Legal/compliance requirements might mandate some data before any account exists, in some jurisdictions Confirm with legal/compliance which fields (if any) are truly required by regulation before allowing a transaction vs. before allowing signup at all. Likely only the PAYABLE gate has real regulatory weight.
Users get repeatedly interrupted by modals Batch by group (Section 5), not by individual field; show the completion indicator so users see how close they are; never re-ask for data already given.
Existing accounts (already fully filled out under the old flow) No migration needed — they'll simply compute as SUBSCRIBED/fully complete already.
Analytics/CRM relying on full data at signup Will need to adjust to track partial profiles; add a scheduled job/report on stalled accounts per state for sales/CS follow-up.

10. Suggested Rollout Phases

  1. Phase 1 — Minimal signup. Ship Section 3 (3-field signup) behind a flag. Move all other fields to a single post-signup "Settings → Complete your profile" page (not yet contextual, just relocated). This alone should meaningfully cut signup abandonment.
  2. Phase 2 — State machine + gating. Add companyState, the requireCompanyState guard, and the GET /company/state endpoint. Gate "Publish" and "Enable bookings" only.
  3. Phase 3 — Contextual modals. Replace the static Settings page prompts with the in-context slide-over modals described in Section 6.
  4. Phase 4 — Polish. Add the sidebar completion indicator, refine copy per modal, A/B test prompt timing (e.g. ask for legal identity right after first draft listing vs. only at publish time).

11. Summary of File-Level Changes

File Change
apps/dashboard/src/app/sign-up/[[...sign-up]]/page.tsx Shrink to Step 0 only (~50-100 lines instead of 937)
apps/api/src/modules/auth/auth.company.routes.ts Replace single signup route with account/start + add new PATCH group routes
apps/api/src/modules/auth/auth.company.service.ts Split createCompanyAndOwner into minimal creation + separate update-group methods
apps/api/src/modules/auth/auth.company.schemas.ts Split into accountStartSchema, companyContactSchema, legalIdentitySchema, paymentSetupSchema, planSchema
New: apps/api/src/modules/company/company.state.ts Computes companyState + missing fields from a Company record
New: apps/api/src/middleware/requireCompanyState.ts Gating middleware described in 7.3
New: apps/dashboard/src/components/profile/CompletionModal*.tsx Set of contextual prompt modals, one per group
New: apps/dashboard/src/hooks/useCompanyState.ts Frontend hook described in Section 8