Perfect! Now I have enough information to compile a comprehensive document. Let me create the detailed contractor flow documentation:

# RateRight v2 — Complete Contractor User Flow Documentation

## Overview
RateRight is an AI-first construction hiring marketplace where contractors post jobs and hire workers. The contractor flow involves 10 key stages from initial signup to job completion and rating.

---

## 1. INITIAL SIGNUP (Public Route)
**Route:** `/auth/signup`
**File:** `/home/ccuser/the-50-dollar-app/src/app/auth/signup/page.tsx`
**Type:** Client Component

### Screen Flow
1. User selects "Contractor" (vs "Worker") with icon button selection
2. Enters email and password (min 6 characters)
3. Agrees to Terms of Service and Privacy Policy (required checkbox)
4. Submits form to create account

### Data Collection
- **Email:** Standard email validation
- **Password:** Min 6 characters (client-side only)
- **User Type:** Set to `"contractor"` in user metadata
- **Terms Agreement:** Checkbox validation

### Supabase Calls
```typescript
supabase.auth.signUp({
  email,
  password,
  options: {
    data: { type: "contractor" },
    emailRedirectTo: `${window.location.origin}/auth/callback`,
  },
})
```

### Result
- **Success:** Redirect to `/auth/verify-email`
- **Auth Trigger:** `handle_new_user` function auto-creates profile in `profiles` table
- **Email:** Verification email sent with callback link

---

## 2. CONTRACTOR ONBOARDING (Authenticated Route)
**Route:** `/contractor/signup`
**File:** `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/contractor/signup/page.tsx`
**Type:** Client Component
**Prerequisites:** Must be authenticated + not have completed onboarding

### Multi-Step Flow (4 steps)

#### Step 1: Auth (Only if not yet authenticated)
- If already logged in, skips to Step 2
- Otherwise, shows email/password form (same as `/auth/signup`)
- Handles already-registered accounts by attempting login

#### Step 2: Company Search via ABN
**Data Input:**
- **ABN:** 11-digit Australian Business Number
- Validation: Checksum validation using `validateABN()` utility

**API Call:**
```
GET /api/abn-lookup?abn={cleanedABN}
```
**ABN Lookup Process:**
1. Validates ABN checksum (locally)
2. Cleans ABN (removes spaces)
3. Calls ABR JSON API at `https://abr.business.gov.au/json/AbnDetails.aspx`
4. Returns company data: name, ABN, address (postcode + state), industry, size, website
5. Fetches company logo via `/api/company-logo` (tries domain guessing)
6. Implements multi-layer director verification:
   - Layer 1: Automated checks (GST registration, entity type, ABN status, email domain, company age, missing ACN)
   - Layer 2: Risk scoring (0-25 low/auto-verify, 26-50 manual review, 51+ requires manual approval)
   - Returns `directorVerification` object with status and risk factors

**Response Example:**
```json
{
  "success": true,
  "data": {
    "name": "COMPANY NAME PTY LTD",
    "abn": "12 345 678 901",
    "address": "2000 NSW",
    "industry": "Construction",
    "size": "Unknown",
    "website": "",
    "logoUrl": "https://...",
    "logoSource": "clearbit|google|none"
  }
}
```

#### Step 3: Verification/Loading State
Shows animated loading screen with:
- Sparkles icon animation
- Three-step progress (Checking ABR, Verifying company, Done)
- Automatically transitions to Step 4

#### Step 4: Review & Confirm
**Editable Fields:**
- Company name (with logo preview if available)
- ABN (formatted)
- Address
- Industry
- Company size
- Website

Each field is clickable to edit. User can modify any field before submission.

**Verification Badge:**
Green banner shows: "Verified via Australian Business Register"

**Database Submission:**
```typescript
// Update profiles table
supabase.from("profiles").update({
  name: companyData.name,
  onboarding_completed: true,
}).eq("id", user.id)

// Insert into companies table
supabase.from("companies").insert({
  profile_id: user.id,
  name: companyData.name,
  abn: companyData.abn,
  address: companyData.address,
  industry: companyData.industry,
  size: companyData.size,
  website: companyData.website,
  ai_research: {
    abr_verified: true,
    abn: companyData.abn,
    verified_at: new Date().toISOString(),
    source: "abr",
  },
})
```

### Result
- Success → Redirect to `/dashboard`
- Failure → Toast error, stay on review screen

---

## 3. CONTRACTOR DASHBOARD
**Route:** `/dashboard`
**File:** `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/dashboard/page.tsx`
**Type:** Server Component
**Access:** Authenticated contractors only

### Data Displayed
- **Greeting:** "G'day, {Company Name}!"
- **Pending Ratings Card:** (if hired matches not yet rated)
- **Active Jobs Count:** Number of `status="active"` jobs in company
- **New Matches Count:** Matches in "applied" or "suggested" status
- **Call-to-Action Buttons:**
  - "Post a Job" → `/contractor/post-job`
  - "View Matches" → `/contractor/matches` (if matchCount > 0)
  - "Company Profile" → `/contractor/signup` (to edit)

### Database Queries
```typescript
// Get contractor's company
supabase.from("companies").select("id").eq("profile_id", user.id)

// Count active jobs
supabase.from("jobs").select("*", { count: "exact" })
  .eq("company_id", company.id)
  .eq("status", "active")

// Count pending matches
supabase.from("matches").select("*, jobs!inner(company_id)", { count: "exact" })
  .eq("jobs.company_id", company.id)
  .in("status", ["applied", "suggested"])

// Get hired matches for pending ratings
supabase.from("matches").select("id, worker_id, jobs(title)")
  .eq("status", "hired")
```

---

## 4. POST A JOB
**Route:** `/contractor/post-job`
**File:** `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/contractor/post-job/page.tsx`
**Type:** Client Component

### Step 1: Select Trade
**Available Trades:**
- Steel Fixer
- Formworker
- Labourer
- Concreter
- Carpenter
- Scaffolder
- Other

**UI:** Grid of 2x4 colored buttons with trade icons

### Step 2: AI-Filled Job Details + Location/Date/Workers (Combined)

#### Job Details Section
**AI Generation API Call:**
```
POST /api/ai/generate-job
Body: { trade: "steel-fixer" }
```

**AI Process:**
1. Uses OpenAI GPT-4o-mini with system prompt: "You are an Australian construction industry expert"
2. Generates JSON response with:
   - title: Concise job title
   - description: 3-4 sentences with Australian English, PPE, early starts
   - certifications: Array of required certs (always includes "White Card")
   - rateMin: Realistic 2026 Australian market rate
   - rateMax: Upper bound
   - rateType: "hourly" or "daily"
3. Validates and clamps rates:
   - Min: $25/hr, Max: $200/hr
   - Ensures $5+ range difference
   - Falls back to hardcoded suggestions if AI fails

**Editable Fields:**
- Job Title
- Description (textarea, 5 rows)
- Pay Rate (min-max with $ icon inputs)
- Rate Type (Per Hour / Per Day buttons)
- Required Certifications (badges with remove buttons)

#### Location Section
- Site Address (text input, e.g., "123 George St, Sydney")
- Suburb (text input for geocoding, e.g., "Parramatta")

#### Schedule Section
- Start Date (date picker)
- Duration (text input, e.g., "2 weeks, ongoing")
- Workers Needed (+/- buttons, large display)

### Step 3: Job Posting

**Validation:**
- Title required
- Suburb required (for location geocoding)

**Geocoding API Call:**
```
POST /api/geocode
Body: { suburb: "Parramatta" }
Response: { location: { lat: number, lng: number } }
```
Converts to PostgreSQL point format: `"SRID=4326;POINT(lng lat)"`

**Database Insertion:**
```typescript
supabase.from("jobs").insert({
  company_id: company.id,
  title,
  description,
  trade: selectedTrade,
  suburb,
  location, // PostgreSQL point or null
  site_address: siteAddress,
  rate_min: parseFloat(rateMin) || null,
  rate_max: parseFloat(rateMax) || null,
  rate_type: rateType,
  start_date: startDate || null,
  duration: duration || null,
  workers_needed: parseInt(workersNeeded) || 1,
  certifications_required: certs,
  status: "active",
  ai_generated: true,
})
```

### Step 4: Success Screen
- Green checkmark
- Message: "Job Posted! We're matching you with the best workers now. You'll get notified when we find matches."
- Buttons: "View Matches" → `/contractor/matches` or "Back to Dashboard"

### Auto-Matching
After job is posted, contractors can trigger matching via `/api/match/find-matches`:
- Uses database RPC function `find_matching_workers()`
- Scoring weights:
  - Trade match: 40%
  - Availability: 20%
  - Experience: 15%
  - Certifications: 15%
  - Location proximity: 10%
  - Ratings: 10%
- Creates match records with status="suggested"

---

## 5. VIEW COMPANY PROFILE
**Route:** `/contractor/profile`
**File:** `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/contractor/profile/page.tsx`
**Type:** Server Component

### Display
- Company logo (avatar upload)
- Company name (large heading)
- Location, industry, ratings summary
- Company size badge
- Company details cards:
  - ABN
  - Website (clickable link)
  - About section (from `ai_research` field)

### Ratings Section
Displays:
- Average rating (rounded to 1 decimal)
- Total number of ratings
- Latest 3 recent reviews with:
  - Reviewer name/avatar
  - Star rating
  - Comment (if provided)
  - Created date

**Database Query:**
```typescript
supabase.from("ratings").select(`
  *,
  from_profile:profiles!ratings_from_id_fkey(
    id, name, avatar_url
  )
`).eq("to_id", user.id).order("created_at", { ascending: false }).limit(3)
```

### Edit Button
Links to `/contractor/signup` to edit company details

---

## 6. BROWSE AND SWIPE MATCHES
**Route:** `/contractor/matches`
**File:** `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/contractor/matches/page.tsx`
**Type:** Client Component

### Initial Load
1. Fetches all active/filled jobs for contractor's company
2. Shows horizontal scroll job selector (pills)
3. Selected job highlighted in primary color

### Match Card Display
For each match, displays:
- Worker avatar (initials circle)
- Worker name
- AI summary (e.g., "Steel Fixer · 5yr exp · Parramatta · Full-time · White Card, Working at Heights")
- Match score (%) with star icon
- Experience years (with clock icon)
- Location/suburb (with pin icon)
- Certifications (up to 3, as small badges)

### Swipe Interactions
**Swipe Right (Hire):**
- Triggers haptic feedback (50ms vibration)
- Shows green background + "Release to hire" message
- On release > 100px: Opens HirePaymentModal

**Swipe Left (Pass):**
- Triggers haptic feedback (30ms, 20ms pause, 30ms)
- Shows red background + "Release to pass" message
- On release > 100px: Updates match status to `contractor_action="rejected"`

**Tap Hire Button:**
- Directly opens HirePaymentModal

### Match Data Structure
```typescript
interface MatchWithWorker {
  id: string;
  job_id: string;
  worker_id: string;
  status: string;
  match_score: number | null;
  ai_summary: string | null;
  contractor_action: string;
  worker_action: string;
  hired_at: string | null;
  fee_charged: boolean;
  worker: { id, name, suburb, avatar_url };
  worker_profile: { trades, experience_years, certifications, availability };
}
```

### Pagination
- Loads 10 matches per page
- Shows Previous/Next buttons if totalPages > 1
- Pagination controls reset when job selection changes

---

## 7. PAYMENT FLOW
**Modal Component:** `/home/ccuser/the-50-dollar-app/src/components/payment/HirePaymentModal.tsx`
**Form Component:** `/home/ccuser/the-50-dollar-app/src/components/payment/PaymentForm.tsx`

### Payment Modal States
1. **Initial:** Empty state (waiting for trigger)
2. **Loading:** "Initializing payment..." spinner
3. **Ready:** Stripe PaymentElement rendered
4. **Processing:** "Processing payment..." spinner
5. **Success:** Green checkmark + "Payment Successful!"
6. **Error:** Red error icon + error message

### Payment Initialization
**Trigger:** User clicks "Hire — $50 Fee" button or swipes right

**API Call:**
```
POST /api/payments/create
Headers: { 'Content-Type': 'application/json' }
Body: {
  match_id: string,
  company_id: string,
  amount: 50
}
```

**Rate Limiting:** 10 requests per 15 minutes (by user ID)

**Payment Creation Process:**
1. Verifies user owns the company (RLS check)
2. Verifies match exists and is not rejected
3. Checks if payment already charged (prevents duplicates)
4. Creates Stripe PaymentIntent with $50 AUD amount
5. Inserts payment record into `payments` table with status="pending"
6. Uses atomic upsert to prevent race conditions
7. Returns `clientSecret` and `publishableKey`

**Database Upsert:**
```typescript
supabaseService.from('payments').upsert({
  match_id: matchId,
  company_id: companyId,
  amount: 50,
  status: 'pending',
  stripe_payment_id: paymentIntentId,
}, { onConflict: 'match_id' })
```

### Payment Form
**Stripe Elements:**
- PaymentElement (handles all payment methods: card, Apple Pay, Google Pay, etc.)
- Layout: Tabs with default expanded
- Shows fee breakdown: "$50.00 AUD" with credit card icon
- "One-time fee to hire this worker. No additional charges."

**User Actions:**
- Cancel: Closes modal (no payment attempted)
- Pay $50.00: Confirms payment via Stripe

### Stripe Integration
**Stripe SDK:** `stripe-js` + `@stripe/react-stripe-js`
**StripeProvider Component:** `/home/ccuser/the-50-dollar-app/src/components/payment/StripeProvider.tsx`

**Payment Confirmation Flow:**
```typescript
stripe.confirmPayment({
  elements,
  confirmParams: {
    return_url: `${window.location.origin}/contractor/payment-success`,
  },
  redirect: 'if_required',
})
```

**Status Handling:**
- Success: Calls `onSuccess()` callback
- Requires Action: Stripe handles 3D Secure redirect
- Other: Shows error message

### Payment Success
After successful payment:
1. Modal shows success screen (green checkmark)
2. After 2 seconds, calls `onSuccess()` callback
3. Callback updates match status in database to "hired"
4. Match display updated to show "Hired" badge
5. Modal closes and resets

**Match Update:**
```typescript
supabase.from("matches").update({
  status: "hired",
  contractor_action: "accepted",
  hired_at: hiredAt,
  fee_charged: true,
}).eq("id", selectedMatch.id)
```

### Payment Success Redirect
**Route:** `/contractor/payment-success`
**File:** `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/contractor/payment-success/page.tsx`
**Type:** Client Component

**Query Parameters:**
- `payment_intent`: Stripe PaymentIntent ID
- `payment_intent_client_secret`: Stripe client secret

**Flow:**
1. Waits 2 seconds for webhook processing
2. Calls `/api/payments/verify?payment_intent={id}` to get Stripe status
3. Verifies payment record in database
4. Shows appropriate message based on status:
   - Success (status="succeeded"): Green checkmark + success message
   - Processing: Yellow warning + "Being processed..."
   - Failed/Canceled: Red error + specific error message

**Buttons:**
- "Back to Matches" → `/contractor/matches`
- "Go to Dashboard" → `/dashboard`

---

## 8. MESSAGING WITH HIRED WORKER
**Route:** `/messages`
**File:** `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/messages/page.tsx`
**Type:** Client Component

### Conversation List
**API Call:**
```
GET /api/messages
```
Returns list of conversations with:
- `conversation_id`: UUID
- `other_participant_name`: Worker's name
- `other_participant_avatar`: Avatar URL
- `last_message`: Latest message text
- `last_message_time`: Timestamp
- `unread_count`: Number of unread messages
- `other_participant_is_online`: Boolean

### Real-Time Updates
Subscribes to Supabase realtime for new messages:
```typescript
supabase.channel('messages')
  .on('postgres_changes',
    { event: 'INSERT', schema: 'public', table: 'messages' },
    () => { fetchConversations(); }
  ).subscribe()
```

**Note:** Contact details (phone/email) are revealed through messaging conversation or after hire completion (via invitation in message).

---

## 9. CONTACT REVEAL - AFTER HIRE CONFIRMATION
**Timing:** After payment is confirmed and match status = "hired"

**Contact Information Made Available To:**
- Contractor gets access to worker's phone number (via messages/conversation)
- Worker gets access to contractor's company contact info
- Both notified via notification system

**Database Fields Accessible:**
- Worker profile: `phone` (if provided during signup)
- Contractor company: `address`, contact info from company profile

**No Explicit Screen:** Contact is revealed implicitly through:
1. Messaging conversation with hired worker
2. Contractor profile visible to worker
3. Match record shows `hired_at` timestamp

---

## 10. RATING AND COMPLETION
**Route:** `/rate/[matchId]`
**File:** `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/rate/[matchId]/page.tsx`
**Type:** Client Component

### Rating Screen
**Accessible:** Only for hired matches that haven't been rated yet

**Components:**
- Star rating (1-5, clickable, hover preview)
- Score text feedback: "Poor" (1) → "Excellent" (5)
- Optional comment textarea (max 500 characters)
- Submit button (disabled until score selected)
- Skip for now link

### Rating Submission
**Database Insert:**
```typescript
supabase.from("ratings").insert({
  match_id: matchId,
  from_id: user.id,  // Contractor
  to_id: toId,        // Worker's profile_id
  score,
  comment: comment.trim() || null,
})
```

### Success
- Shows success screen with checkmark
- Message: "Thanks for rating! Your feedback helps build trust on RateRight."
- Auto-redirects to `/dashboard` after 2 seconds

### Validation
- Checks if match status = "hired"
- Prevents duplicate ratings (checks existing rating for this match + user)
- Shows "Already rated" screen if duplicate

---

## Complete Data Flows Summary

### Contractor Signup Flow
```
/auth/signup
  → Email/Password/Type
  → Supabase Auth SignUp
  → /auth/verify-email
  → Click email link
  → /auth/callback
  → /contractor/signup (onboarding)
  → Step 1: Auth (if needed)
  → Step 2: ABN Lookup
  → /api/abn-lookup (ABR verification)
  → /api/company-logo (logo fetch)
  → Step 3: Verification loading
  → Step 4: Review & confirm
  → Supabase: profiles.update + companies.insert
  → /dashboard (success)
```

### Job Posting Flow
```
/contractor/post-job
  → Step 1: Select trade
  → Step 2: AI generation (/api/ai/generate-job)
  → Edit job details, location, schedule
  → Step 2: Geocoding (/api/geocode) for suburb
  → Step 3: Post job (jobs.insert)
  → Step 4: Success + View Matches
  → Auto-match triggered via /api/match/find-matches
```

### Hiring Flow
```
/contractor/matches
  → View swipeable match cards
  → Swipe right or tap "Hire"
  → HirePaymentModal opens
  → /api/payments/create
  → Stripe PaymentElement
  → User confirms payment
  → stripe.confirmPayment()
  → /contractor/payment-success (redirect_if_required)
  → /api/payments/verify (status check)
  → matches.update(status="hired", fee_charged=true)
  → Success screen
  → Back to matches or dashboard
```

### Rating Flow
```
/dashboard (see pending ratings)
  → /rate/[matchId]
  → Select star rating + optional comment
  → ratings.insert(from_id=contractor, to_id=worker)
  → Success screen
  → /dashboard (auto-redirect after 2s)
```

---

## Security & Validation

### Authentication
- All authenticated routes check `supabase.auth.getUser()`
- Redirect to `/auth/login` if not authenticated
- Email verification required for contractors

### Authorization
- Contractors can only view/manage their own company jobs
- Contractors can only create matches for their jobs
- Contractors can only process payments for their matches
- RLS policies enforce all data access

### Rate Limiting
- **ABN Lookup:** 10 requests per 15 minutes (by IP)
- **Payment Creation:** 10 requests per 15 minutes (by user)
- **Match Finding:** 10 requests per 15 minutes (by IP)
- **AI Job Generation:** 5 requests per 15 minutes (by IP)

### CSRF Protection
- `/api/payments/create` protected with `withCSRFProtection`
- `/api/match/find-matches` protected with `withCSRFProtection`
- `/api/ai/generate-job` protected with `withCSRFProtection`

### Input Validation
- ABN: Checksum validation + length validation
- Email: Zod schema validation
- Job title: Required, max length
- Suburb: Required for geocoding
- Rates: Clamped to $25-$200/hr range

### Payment Security
- Amount hardcoded to $50 AUD (client cannot override)
- Stripe handles PCI compliance (no card storage)
- Upsert prevents duplicate charges
- Webhook verifies payment before marking as charged

---

## Key Database Tables

### profiles
- `id`, `type`, `name`, `email`, `avatar_url`, `location`, `suburb`, `verified`, `onboarding_completed`, `created_at`, `updated_at`, `last_seen_at`, `ai_generated`

### companies
- `id`, `profile_id`, `name`, `abn`, `address`, `industry`, `size`, `website`, `logo_url`, `ai_research`, `created_at`, `updated_at`

### jobs
- `id`, `company_id`, `title`, `description`, `trade`, `location`, `suburb`, `site_address`, `rate_min`, `rate_max`, `rate_type`, `start_date`, `duration`, `workers_needed`, `certifications_required`, `status`, `ai_generated`, `created_at`, `updated_at`

### matches
- `id`, `job_id`, `worker_id`, `status`, `match_score`, `ai_summary`, `contractor_action`, `worker_action`, `hired_at`, `fee_charged`, `created_at`, `updated_at`

### payments
- `id`, `match_id`, `company_id`, `amount`, `status`, `stripe_payment_id`, `created_at`

### ratings
- `id`, `match_id`, `from_id`, `to_id`, `score`, `comment`, `created_at`

### conversations
- `id`, `participant_ids`, `created_at`

### messages
- `id`, `conversation_id`, `sender_id`, `content`, `created_at`, `read`

---

## API Routes Summary

| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/api/abn-lookup` | GET | Lookup ABN via Australian Business Register |
| `/api/company-logo` | GET | Fetch company logo from Clearbit/Google |
| `/api/geocode` | POST | Convert suburb to lat/lng coordinates |
| `/api/ai/generate-job` | POST | Generate job details using OpenAI |
| `/api/match/find-matches` | POST | Find and create worker matches for job |
| `/api/payments/create` | POST | Initialize Stripe PaymentIntent |
| `/api/payments/verify` | GET | Verify Stripe payment status |
| `/api/messages` | GET | Fetch conversation list |

---

## Component Architecture

### Payment Components
- `HirePaymentModal`: State machine managing payment flow
- `StripeProvider`: Stripe context wrapper
- `PaymentForm`: Stripe PaymentElement form

### Layout Components
- `(authenticated)/layout.tsx`: Protected wrapper with header + footer nav
- `BottomNavWrapper`: Mobile navigation
- `NotificationBell`: Notification icon in header
- `SignOutButton`: Logout button

### Reusable UI
- Button, Input, Card, Label, Badge, Dialog (shadcn/ui components)

---

This documentation maps every screen, API call, and data flow in the contractor journey. All information is current as of the latest codebase review.