Onboarding
| Route | Purpose | Auth Required |
|---|---|---|
/onboarding | New user setup (2 steps) | Yes |
Users are redirected here by the checkOnboardingCompleted() guard if onboardingCompleted !== true.
Step 1: Goal Selection
Section titled “Step 1: Goal Selection”Users choose their primary purpose on the platform. Single select (click to select, click again to deselect).
| Goal ID | Title | Description | Dashboard Template |
|---|---|---|---|
buyer | Buyer | ”I want to discover, support, and purchase from creators and businesses.” | buyer_default |
seller | Seller | ”I want to build a site and sell products, services, or digital offerings.” | seller_default |
refer-and-earn | Refer and earn | ”I want to share Mindhyv and earn recurring commissions.” | referral_default |
project-management | Project management | ”I want to organize, manage, and collaborate on structured projects.” | pm_default |
On Continue
Section titled “On Continue”- Dashboard ID mapped from goal
dashboardManager.ensureDashboard()creates dashboard atusers/{uid}/dashboards/{dashboardId}- Dashboard populated with template widgets (see Dashboard)
- Step advances to 2
Step 2: Profile Completion
Section titled “Step 2: Profile Completion”Two-panel layout: Form (left) + Live Preview (right).
Fields
Section titled “Fields”| Field | Type | Required | Validation | Notes |
|---|---|---|---|---|
displayName | text | Yes | Non-empty | Full name |
userName | text | Yes | 1-15 chars, unique | Checked against Firestore on submit |
location | dropdown | Yes | Must select | Country selector (249+ countries) |
timeZone | dropdown | Yes | Must select | 150+ timezones |
language | dropdown | Yes | Must select | 100+ languages |
birthDate | date picker | Yes | Valid date, age >= 18 | Calendar picker, years: current-18 to current-100 |
photoURL | image upload | No | JPEG/PNG/GIF/WebP, max 5MB | With crop dialog |
tagline | text | No | Max 30 chars | |
aboutMe | textarea | No | Max 500 chars |
Age Validation (Exact)
Section titled “Age Validation (Exact)”let age = today.getFullYear() - birthDate.getFullYear();const monthDiff = today.getMonth() - birthDate.getMonth();if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { age--;}return age >= 18;Error: “You must be at least 18 years old.”
Username Uniqueness Check
Section titled “Username Uniqueness Check”Queries users collection where userName == value. Only triggers if username changed from initial value.
Error: “Username is already taken. Please choose a different username.”
Avatar Upload & Crop
Section titled “Avatar Upload & Crop”| Setting | Value |
|---|---|
| Crop size | 320x320 px (fixed square) |
| Min zoom | 0.2x (20%) |
| Max zoom | 3x (300%) |
| Zoom step | 0.05 increments |
| Max file size | 5MB |
| Accepted formats | JPEG, PNG, GIF, WebP |
| Storage path | users/{uid}/avatar/avatar_{timestamp}.{ext} |
Crop parameters saved: avatarCropX, avatarCropY, avatarCropScale, avatarCropSize, avatarImageWidth, avatarImageHeight
Live Preview Shows
Section titled “Live Preview Shows”- Avatar (cropped or initials)
- Display name (or “Full Name” placeholder)
- Username (or “@username” placeholder)
- Age calculated from birthDate
- Country, timezone, language (with flag)
- Tagline and About Me text
On Submit
Section titled “On Submit”Firestore Updates
Section titled “Firestore Updates”users/{uid} (update):
{ displayName, userName, location, timeZone, language, birthDate: Timestamp, // Parsed from YYYY-MM-DD photoURL?, // If provided avatarCrop*?, // If crop params != null onboardingCompleted: true, // Completion flag updatedAt: Timestamp.now()}profiles/{uid} (set with merge):
{ tagline: string, aboutMe?: string // If not null}Post-Submit
Section titled “Post-Submit”- Toast: “Profile updated”
- Navigate to
/withreplaceState: true - User is now fully onboarded and guard passes
Blockers
Section titled “Blockers”| Blocker | Description |
|---|---|
| Goal not selected | Continue button disabled |
| Required fields empty | Submit button disabled |
| Age < 18 | Submit blocked with error |
| Username taken | Submit blocked with error |
| File too large | Upload silently rejected |
Dashboard Creation
Section titled “Dashboard Creation”When ensureDashboard() runs, it creates a dashboard document at users/{uid}/dashboards/{dashboardId}:
{ userId: uid, name: 'My Dashboard', layoutVersion: 1, grid: WidgetInstance[], // From template with new UUIDs updatedAt: serverTimestamp()}| Goal | Template | Widgets |
|---|---|---|
| Buyer | buyer_default | Inbox, Active Projects, Recommended Sellers |
| Seller | seller_default | Sales Summary, Orders, Messages, Payouts |
| Refer & Earn | referral_default | Referral Stats, Campaign Links, Payouts |
| Project Management | pm_default | Project Board, Milestones, Team Chat |