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:
- 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.
- 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
Userrecord (owner) - A
Companyrecord in aDRAFTstate withname = nulland 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:
- The action is allowed to start optimistically (e.g. they can build a full listing draft).
- On the actual gating action (Publish, Enable bookings…), intercept with a slide-over panel or modal, not a full-page redirect.
- 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.
- 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
Companyfields 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
profileCompletionPercentfor 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-uppage (Step 0 only). - A set of small, reusable "completion modals," one per group in Section 5, each built from the existing
BilingualInputand form components (these don't need to be rebuilt, just reorganized).
- A short
- Introduce a
useCompanyState()hook that fetchesGET /company/stateand 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
- 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.
- Phase 2 — State machine + gating. Add
companyState, therequireCompanyStateguard, and theGET /company/stateendpoint. Gate "Publish" and "Enable bookings" only. - Phase 3 — Contextual modals. Replace the static Settings page prompts with the in-context slide-over modals described in Section 6.
- 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 |