Do a very thorough analysis of the frontend code in /home/ccuser/the-50-dollar-app.

1. Find all page routes (app/**/page.tsx) and list them - this tells us the app's pages
2. Check for any components that reference API endpoints or database calls
3. Look for client-side Supabase usage (createBrowserClient)
4. Check for broken imports or missing components
5. Look for any TODO/FIXME/HACK/BUG comments
6. Check error boundaries and error handling on the frontend
7. Look for any hardcoded URLs, API keys, or secrets in client-side code
8. Check forms - do they have proper validation?
9. Look for any console.error or console.warn patterns that suggest known issues
10. Check if there are any TypeScript errors (look for @ts-ignore, @ts-expect-error, any casts)

Report ALL findings with file paths and line numbers. Flag anything broken or suspicious.


I'll conduct a thorough analysis of the frontend code in /home/ccuser/the-50-dollar-app. Let me start by exploring the structure and then systematically check each area.


Bash

find /home/ccuser/the-50-dollar-app -type f -name "*.tsx" -o -name "*.ts" -o -name "*.json" | head -30


Let me explore the actual source structure first:


ls -la /home/ccuser/the-50-dollar-app/ | head -40


total 1752
drwxrwxr-x  10 ccuser ccuser    4096 Feb  8 12:04 .
drwxr-x---  20 ccuser ccuser    4096 Feb  8 19:55 ..
-rw-r--r--   1 ccuser ccuser    2190 Feb  8 11:21 .env.local
drwxrwxr-x   8 ccuser ccuser    4096 Feb  8 19:32 .git
-rw-rw-r--   1 ccuser ccuser     496 Feb  8 11:18 .gitignore
drwxrwxr-x   8 ccuser ccuser    4096 Feb  8 19:24 .next
-rw-rw-r--   1 ccuser ccuser    4319 Feb  8 11:18 AI-PROFILE-BUILDER.md
-rw-rw-r--   1 ccuser ccuser    6216 Feb  8 11:18 APPLICATION-CODE-UPDATES-FOR-RLS-FIX.md
-rw-rw-r--   1 ccuser ccuser    5238 Feb  8 11:18 AUDIT-C3-COMPLETION.md
-rw-rw-r--   1 ccuser ccuser    2213 Feb  8 11:18 AUDIT-C4-COMPLETION.md
-rw-rw-r--   1 ccuser ccuser    6276 Feb  8 11:18 AUDIT-C5-COMPLETION.md
-rw-rw-r--   1 ccuser ccuser    5447 Feb  8 11:18 AUDIT-H1-H5-H11-H14-M16-M17-COMPLETION.md
-rw-rw-r--   1 ccuser ccuser    4394 Feb  8 11:18 CLAUDE.md
-rw-rw-r--   1 ccuser ccuser    3115 Feb  8 11:18 CSRF_IMPLEMENTATION.md
-rw-rw-r--   1 ccuser ccuser   13785 Feb  8 11:18 PRODUCT-SPEC.md
-rw-rw-r--   1 ccuser ccuser    2179 Feb  8 11:18 README.md
-rw-rw-r--   1 ccuser ccuser    9184 Feb  8 11:18 SUPABASE-KEY-ROTATION-GUIDE.md
-rw-rw-r--   1 ccuser ccuser    8977 Feb  8 11:18 UX-IMPLEMENTATION-GUIDE.md
-rw-rw-r--   1 ccuser ccuser    3480 Feb  8 11:18 VERCEL-DEPLOYMENT.md
-rw-rw-r--   1 ccuser ccuser     467 Feb  8 11:18 components.json
drwxrwxr-x   2 ccuser ccuser    4096 Feb  8 11:18 docs
-rw-rw-r--   1 ccuser ccuser     465 Feb  8 11:18 eslint.config.mjs
-rw-rw-r--   1 ccuser ccuser     247 Feb  8 11:23 next-env.d.ts
-rw-rw-r--   1 ccuser ccuser    1651 Feb  8 11:18 next.config.ts
drwxr-xr-x 321 ccuser ccuser   12288 Feb  7 20:58 node_modules
-rw-rw-r--   1 ccuser ccuser  305740 Feb  8 11:18 package-lock.json
-rw-rw-r--   1 ccuser ccuser    1080 Feb  8 11:18 package.json
-rw-rw-r--   1 ccuser ccuser      94 Feb  8 11:18 postcss.config.mjs
drwxrwxr-x   2 ccuser ccuser    4096 Feb  8 11:18 public
drwxrwxr-x   2 ccuser ccuser    4096 Feb  8 11:18 scripts
drwxrwxr-x   5 ccuser ccuser    4096 Feb  8 11:18 src
drwxrwxr-x   4 ccuser ccuser    4096 Feb  8 11:18 supabase
-rw-rw-r--   1 ccuser ccuser     753 Feb  8 11:18 tsconfig.json
-rw-rw-r--   1 ccuser ccuser 1289934 Feb  8 19:24 tsconfig.tsbuildinfo
-rw-rw-r--   1 ccuser ccuser     474 Feb  8 11:18 vercel.json


Now let me find all page routes and the source structure:


find /home/ccuser/the-50-dollar-app/src -type d | head -30


/home/ccuser/the-50-dollar-app/src
/home/ccuser/the-50-dollar-app/src/app
/home/ccuser/the-50-dollar-app/src/app/auth
/home/ccuser/the-50-dollar-app/src/app/auth/callback
/home/ccuser/the-50-dollar-app/src/app/auth/login
/home/ccuser/the-50-dollar-app/src/app/auth/signup
/home/ccuser/the-50-dollar-app/src/app/auth/verify-email
/home/ccuser/the-50-dollar-app/src/app/faq
/home/ccuser/the-50-dollar-app/src/app/legal
/home/ccuser/the-50-dollar-app/src/app/legal/terms-of-service
/home/ccuser/the-50-dollar-app/src/app/legal/privacy-policy
/home/ccuser/the-50-dollar-app/src/app/api
/home/ccuser/the-50-dollar-app/src/app/api/auth
/home/ccuser/the-50-dollar-app/src/app/api/auth/csrf
/home/ccuser/the-50-dollar-app/src/app/api/auth/verify-email
/home/ccuser/the-50-dollar-app/src/app/api/payments
/home/ccuser/the-50-dollar-app/src/app/api/payments/create
/home/ccuser/the-50-dollar-app/src/app/api/ai
/home/ccuser/the-50-dollar-app/src/app/api/ai/transcribe-voice
/home/ccuser/the-50-dollar-app/src/app/api/ai/parse-profile-voice
/home/ccuser/the-50-dollar-app/src/app/api/ai/generate-profile
/home/ccuser/the-50-dollar-app/src/app/api/webhooks
/home/ccuser/the-50-dollar-app/src/app/api/webhooks/stripe
/home/ccuser/the-50-dollar-app/src/app/api/company-logo
/home/ccuser/the-50-dollar-app/src/app/api/match
/home/ccuser/the-50-dollar-app/src/app/api/match/find-matches
/home/ccuser/the-50-dollar-app/src/app/api/abn-lookup
/home/ccuser/the-50-dollar-app/src/app/api/messages
/home/ccuser/the-50-dollar-app/src/app/api/messages/start
/home/ccuser/the-50-dollar-app/src/app/api/messages/[id]


Good! Now let me search for all the critical patterns. I'll check for API usage, Supabase clients, and issues:


find /home/ccuser/the-50-dollar-app/src -type f \( -name "*.tsx" -o -name "*.ts" \) | wc -l


Now let me search for the key issues systematically:


/home/ccuser/the-50-dollar-app/src/app/api/abn-lookup/route.ts-197-  acn?: string,
/home/ccuser/the-50-dollar-app/src/app/api/abn-lookup/route.ts-198-  userEmail?: string
/home/ccuser/the-50-dollar-app/src/app/api/abn-lookup/route.ts-199-): Promise<DirectorVerification> {
/home/ccuser/the-50-dollar-app/src/app/api/abn-lookup/route.ts:200:  // TODO: Implement ASIC Connect API integration
/home/ccuser/the-50-dollar-app/src/app/api/abn-lookup/route.ts-201-  // For now, flag all for manual review
/home/ccuser/the-50-dollar-app/src/app/api/abn-lookup/route.ts-202-
/home/ccuser/the-50-dollar-app/src/app/api/abn-lookup/route.ts-203-  console.log(`[ASIC] Director verification requested for ABN ${abn}, ACN ${acn || 'N/A'}`);
--
/home/ccuser/the-50-dollar-app/src/app/api/messages/[id]/route.ts-95-          name: profile.name,
/home/ccuser/the-50-dollar-app/src/app/api/messages/[id]/route.ts-96-          avatar: profile.avatar_url,
/home/ccuser/the-50-dollar-app/src/app/api/messages/[id]/route.ts-97-          type: profile.type,
/home/ccuser/the-50-dollar-app/src/app/api/messages/[id]/route.ts:98:          isOnline: true // TODO: Implement online status
/home/ccuser/the-50-dollar-app/src/app/api/messages/[id]/route.ts-99-        },
/home/ccuser/the-50-dollar-app/src/app/api/messages/[id]/route.ts-100-        messages: messages?.map(msg => ({
/home/ccuser/the-50-dollar-app/src/app/api/messages/[id]/route.ts-101-          id: msg.id,


/home/ccuser/the-50-dollar-app/src/app/(authenticated)/messages/[conversationId]/page.tsx-63-        },
/home/ccuser/the-50-dollar-app/src/app/(authenticated)/messages/[conversationId]/page.tsx-64-        (payload) => {
/home/ccuser/the-50-dollar-app/src/app/(authenticated)/messages/[conversationId]/page.tsx:65:          const newMessage = payload.new as any;
/home/ccuser/the-50-dollar-app/src/app/(authenticated)/messages/[conversationId]/page.tsx-66-          // Only add if it's not from us (we already added it optimistically)
/home/ccuser/the-50-dollar-app/src/app/(authenticated)/messages/[conversationId]/page.tsx-67-          supabase.auth.getUser().then(({ data: { user } }) => {
--
/home/ccuser/the-50-dollar-app/src/app/api/abn-lookup/route.ts-160-
/home/ccuser/the-50-dollar-app/src/app/api/abn-lookup/route.ts-161-  return {
/home/ccuser/the-50-dollar-app/src/app/api/abn-lookup/route.ts:162:    name: typeof name === 'string' ? name : (name as any).Value || 'Unknown',
/home/ccuser/the-50-dollar-app/src/app/api/abn-lookup/route.ts-163-    abn: formatABN(abr.Abn),
/home/ccuser/the-50-dollar-app/src/app/api/abn-lookup/route.ts-164-    status: abr.AbnStatus,


Now let me check for hardcoded URLs, API keys, console errors, and error boundaries:


<persisted-output>
Output too large (33KB). Full output saved to: /home/ccuser/.claude/projects/-home-ccuser-rateright-growth/ce8d9785-a717-447c-bb05-7625d35a54fe/tool-results/toolu_015PLG5Nx41FHZyLZeUKE5UG.txt

Preview (first 2KB):
/home/ccuser/the-50-dollar-app/src/components/payment/PaymentForm.tsx-52-      }
/home/ccuser/the-50-dollar-app/src/components/payment/PaymentForm.tsx-53-    } catch (error) {
/home/ccuser/the-50-dollar-app/src/components/payment/PaymentForm.tsx:54:      console.error('Payment error:', error);
/home/ccuser/the-50-dollar-app/src/components/payment/PaymentForm.tsx-55-      setErrorMessage('An unexpected error occurred. Please try again.');
/home/ccuser/the-50-dollar-app/src/components/payment/PaymentForm.tsx-56-    } finally {
--
/home/ccuser/the-50-dollar-app/src/components/payment/HirePaymentModal.tsx-70-      });
/home/ccuser/the-50-dollar-app/src/components/payment/HirePaymentModal.tsx-71-    } catch (error) {
/home/ccuser/the-50-dollar-app/src/components/payment/HirePaymentModal.tsx:72:      console.error('Payment initialization error:', error);
/home/ccuser/the-50-dollar-app/src/components/payment/HirePaymentModal.tsx-73-      setPaymentState({
/home/ccuser/the-50-dollar-app/src/components/payment/HirePaymentModal.tsx-74-        type: 'error',
--
/home/ccuser/the-50-dollar-app/src/lib/csrf.ts-76-
/home/ccuser/the-50-dollar-app/src/lib/csrf.ts-77-  if (!cookieToken) {
/home/ccuser/the-50-dollar-app/src/lib/csrf.ts:78:    console.error('CSRF validation failed: No token in cookie');
/home/ccuser/the-50-dollar-app/src/lib/csrf.ts-79-    return false;
/home/ccuser/the-50-dollar-app/src/lib/csrf.ts-80-  }
--
/home/ccuser/the-50-dollar-app/src/lib/csrf.ts-108-
/home/ccuser/the-50-dollar-app/src/lib/csrf.ts-109-  if (!requestToken) {
/home/ccuser/the-50-dollar-app/src/lib/csrf.ts:110:    console.error('CSRF validation failed: No token in request');
/home/ccuser/the-50-dollar-app/src/lib/csrf.ts-111-    return false;
/home/ccuser/the-50-dollar-app/src/lib/csrf.ts-112-  }
--
/home/ccuser/the-50-dollar-app/src/lib/csrf.ts-116-
/home/ccuser/the-50-dollar-app/src/lib/csrf.ts-117-  if (!tokensMatch) {
...
</persisted-output>


/home/ccuser/the-50-dollar-app/src/lib/csrf.ts-154-  try {
/home/ccuser/the-50-dollar-app/src/lib/csrf.ts-155-    const parsedUrl = new URL(checkUrl);
/home/ccuser/the-50-dollar-app/src/lib/csrf.ts:156:    const appUrl = new URL(process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000');
/home/ccuser/the-50-dollar-app/src/lib/csrf.ts-157-
/home/ccuser/the-50-dollar-app/src/lib/csrf.ts-158-    return parsedUrl.origin === appUrl.origin;
--
/home/ccuser/the-50-dollar-app/src/app/api/company-logo/route.ts-130-const ALLOWED_ORIGINS = [
/home/ccuser/the-50-dollar-app/src/app/api/company-logo/route.ts-131-  process.env.NEXT_PUBLIC_APP_URL, // Production URL from env
/home/ccuser/the-50-dollar-app/src/app/api/company-logo/route.ts:132:  'http://localhost:3000',         // Local development
/home/ccuser/the-50-dollar-app/src/app/api/company-logo/route.ts:133:  'https://localhost:3000',        // Local development with HTTPS
/home/ccuser/the-50-dollar-app/src/app/api/company-logo/route.ts-134-].filter(Boolean) as string[];
/home/ccuser/the-50-dollar-app/src/app/api/company-logo/route.ts-135-

[Showing results with pagination = limit: 50, offset: 0]


Found 1 file
/home/ccuser/the-50-dollar-app/src/lib/error-handling.tsx


Let me now read the specific pages and key component files to check for broken imports, validation, and other issues:


1→import { useState, useCallback, useEffect } from "react";
     2→
     3→interface UseErrorBoundaryReturn {
     4→  error: Error | null;
     5→  hasError: boolean;
     6→  resetError: () => void;
     7→  setError: (error: Error) => void;
     8→}
     9→
    10→/**
    11→ * Hook for managing error boundary state
    12→ * Provides a way to catch and handle errors in components
    13→ */
    14→export function useErrorBoundary(): UseErrorBoundaryReturn {
    15→  const [error, setError] = useState<Error | null>(null);
    16→
    17→  const resetError = useCallback(() => {
    18→    setError(null);
    19→  }, []);
    20→
    21→  const handleSetError = useCallback((newError: Error) => {
    22→    console.error("Error caught by boundary:", newError);
    23→    setError(newError);
    24→  }, []);
    25→
    26→  return {
    27→    error,
    28→    hasError: error !== null,
    29→    resetError,
    30→    setError: handleSetError,
    31→  };
    32→}
    33→
    34→interface UseAsyncErrorReturn<T> {
    35→  data: T | null;
    36→  error: Error | null;
    37→  isLoading: boolean;
    38→  execute: (...args: any[]) => Promise<T | null>;
    39→  reset: () => void;
    40→}
    41→
    42→/**
    43→ * Hook for handling async operations with error boundaries
    44→ * Automatically catches and manages errors from async functions
    45→ */
    46→export function useAsyncError<T = any>(
    47→  asyncFunction: (...args: any[]) => Promise<T>,
    48→  deps: any[] = []
    49→): UseAsyncErrorReturn<T> {
    50→  const [data, setData] = useState<T | null>(null);
    51→  const [error, setError] = useState<Error | null>(null);
    52→  const [isLoading, setIsLoading] = useState(false);
    53→
    54→  const execute = useCallback(async (...args: any[]): Promise<T | null> => {
    55→    try {
    56→      setIsLoading(true);
    57→      setError(null);
    58→      const result = await asyncFunction(...args);
    59→      setData(result);
    60→      return result;
    61→    } catch (err) {
    62→      const error = err instanceof Error ? err : new Error(String(err));
    63→      setError(error);
    64→      console.error("Async operation error:", error);
    65→      return null;
    66→    } finally {
    67→      setIsLoading(false);
    68→    }
    69→  }, deps);
    70→
    71→  const reset = useCallback(() => {
    72→    setData(null);
    73→    setError(null);
    74→    setIsLoading(false);
    75→  }, []);
    76→
    77→  return {
    78→    data,
    79→    error,
    80→    isLoading,
    81→    execute,
    82→    reset,
    83→  };
    84→}
    85→
    86→/**
    87→ * Error boundary component that can be used to wrap components
    88→ * This is a simple implementation for functional components
    89→ */
    90→export function withErrorBoundary
(
    91→  Component: React.ComponentType
,
    92→  fallback?: React.ComponentType<{ error: Error; reset: () => void }>
    93→) {
    94→  return function WrappedComponent(props: P) {
    95→    const [error, setError] = useState<Error | null>(null);
    96→
    97→    useEffect(() => {
    98→      const handleError = (event: ErrorEvent) => {
    99→        setError(event.error);
   100→        event.preventDefault();
   101→      };
   102→
   103→      window.addEventListener("error", handleError);
   104→      return () => window.removeEventListener("error", handleError);
   105→    }, []);
   106→
   107→    const reset = useCallback(() => {
   108→      setError(null);
   109→    }, []);
   110→
   111→    if (error) {
   112→      if (fallback) {
   113→        const FallbackComponent = fallback;
   114→        return <FallbackComponent error={error} reset={reset} />;
   115→      }
   116→
   117→      // Default fallback
   118→      return (
   119→

   120→
Something went wrong

   121→
{error.message}

   122→          <button
   123→            onClick={reset}
   124→            className="mt-4 px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
   125→          >
   126→            Try again
   127→          </button>
   128→

   129→      );
   130→    }
   131→
   132→    return <Component {...props} />;
   133→  };
   134→}

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


find /home/ccuser/the-50-dollar-app/src -name "error.tsx" -o -name "*error*" | grep -E "\.(tsx|ts)$"


grep -r "import.*from.*notFound\|import.*from.*redirect" /home/ccuser/the-50-dollar-app/src --include="*.ts" --include="*.tsx" | head -20


Now let me check each page file for issues:


1→import Link from "next/link";
     2→import Image from "next/image";
     3→import { Button } from "@/components/ui/button";
     4→import { Card, CardContent } from "@/components/ui/card";
     5→import {
     6→  Building2,
     7→  HardHat,
     8→  Zap,
     9→  DollarSign,
    10→  Shield,
    11→  CheckCircle2,
    12→  Search,
    13→  ArrowRight,
    14→  Sparkles,
    15→  MapPin,
    16→  Clock,
    17→  Users,
    18→} from "lucide-react";
    19→
    20→// Force static generation
    21→export const dynamic = 'force-static';
    22→
    23→export default function LandingPage() {
    24→  return (
    25→

    26→      {/* Orange accent bar */}
    27→

    28→
    29→      {/* Header */}
    30→
    31→

    32→          <Link href="/" className="flex items-center gap-2">
    33→            <Image src="/logo_black.svg" alt="RateRight" width={140} height={40} className="h-8 w-auto" priority />
    34→          </Link>
    35→          <Link href="/auth/login">
    36→            <Button variant="ghost" size="sm">
    37→              Sign in
    38→            </Button>
    39→          </Link>
    40→

    41→
    42→
    43→      {/* Hero */}
    44→

    45→

    46→

    47→            <MapPin className="h-3.5 w-3.5" />
    48→            Now live in Sydney
    49→

    50→
    51→

    52→            Hire construction workers{" "}
    53→            in seconds
    54→

    55→
    56→

    57→            AI builds profiles and finds matches.
    58→            You just tap hire. $50 flat fee per hire.
    59→

    60→
    61→          {/* CTA Buttons - BIG touch targets */}
    62→

    63→            <Link href="/contractor/signup">
    64→              <Button className="h-14 w-full gap-3 text-base font-semibold shadow-lg shadow-primary/25">
    65→                <Building2 className="h-6 w-6" />
    66→                I'm Hiring
    67→                <ArrowRight className="ml-auto h-5 w-5" />
    68→              </Button>
    69→            </Link>
    70→            <Link href="/worker/signup">
    71→              <Button
    72→                variant="outline"
    73→                className="h-14 w-full gap-3 border-2 text-base font-semibold"
    74→              >
    75→                <HardHat className="h-6 w-6" />
    76→                I'm Looking For Work
    77→                <ArrowRight className="ml-auto h-5 w-5" />
    78→              </Button>
    79→            </Link>
    80→

    81→
    82→

    83→            Free to sign up. Workers always free.
    84→

    85→

    86→

    87→
    88→      {/* How it works */}
    89→

    90→

    91→

    92→            How it works
    93→

    94→

    95→            Three steps. Under two minutes.
    96→

    97→
    98→

    99→            <Step
   100→              number="1"
   101→              icon={<Search className="h-5 w-5" />}
   102→              title="Tell us your company"
   103→              description="Enter your company name or ABN. AI researches your business and builds your profile automatically."
   104→            />
   105→            <Step
   106→              number="2"
   107→              icon={<Sparkles className="h-5 w-5" />}
   108→              title="AI finds the best matches"
   109→              description="Post a job in 60 seconds. Smart matching ranks workers by distance, experience, and ratings."
   110→            />
   111→            <Step
   112→              number="3"
   113→              icon={<CheckCircle2 className="h-5 w-5" />}
   114→              title="One tap to hire"
   115→              description="See top matches, tap hire, worker accepts. $50 flat fee — only charged when you hire."
   116→            />
   117→

   118→

   119→

   120→
   121→      {/* For Contractors */}
   122→

   123→

   124→

   125→            <Building2 className="h-5 w-5 text-primary" />
   126→
For Contractors

   127→

   128→

   129→            Stop wasting time hiring
   130→

   131→
   132→

   133→            <FeatureCard
   134→              icon={<Zap className="h-5 w-5 text-primary" />}
   135→              title="AI does the work"
   136→              description="Company research, job descriptions, worker matching — all automated."
   137→            />
   138→            <FeatureCard
   139→              icon={<DollarSign className="h-5 w-5 text-primary" />}
   140→              title="$50 flat fee per hire"
   141→              description="No subscriptions, no percentages. Free to post. Only pay when you hire."
   142→            />
   143→            <FeatureCard
   144→              icon={<Shield className="h-5 w-5 text-primary" />}
   145→              title="Verified workers"
   146→              description="White Card verification, ratings, and real experience on every profile."
   147→            />
   148→            <FeatureCard
   149→              icon={<Clock className="h-5 w-5 text-primary" />}
   150→              title="Hire by tomorrow"
   151→              description="Post a job now, get matched in minutes. Workers on site tomorrow."
   152→            />
   153→

   154→

   155→

   156→
   157→      {/* For Workers */}
   158→

   159→

   160→

   161→            <HardHat className="h-5 w-5 text-primary" />
   162→
For Workers

   163→

   164→

   165→            Get on a job fast
   166→

   167→

   168→            Sign up free. Get matched to jobs near you. Know what you'll earn.
   169→

   170→
   171→

   172→            <Benefit
   173→              icon={<DollarSign className="h-4 w-4" />}
   174→              text="Always free — $0 to sign up, $0 to get hired"
   175→            />
   176→            <Benefit
   177→              icon={<Clock className="h-4 w-4" />}
   178→              text="Answer 5 questions, your profile is done in 2 minutes"
   179→            />
   180→            <Benefit
   181→              icon={<MapPin className="h-4 w-4" />}
   182→              text="Get matched to jobs within kilometres of you"
   183→            />
   184→            <Benefit
   185→              icon={<Users className="h-4 w-4" />}
   186→              text="See the pay rate upfront — no guessing"
   187→            />
   188→

   189→
   190→          <Link href="/worker/signup">
   191→            <Button className="mt-8 h-12 w-full gap-2 text-base font-semibold">
   192→              <HardHat className="h-5 w-5" />
   193→              Sign up free
   194→            </Button>
   195→          </Link>
   196→

   197→

   198→
   199→      {/* Pricing */}
   200→

   201→

   202→
Simple pricing

   203→

   204→            No hidden fees. No lock-in. No percentages.
   205→

   206→
   207→          <Card className="mt-6 border-2 border-primary/20">
   208→            <CardContent className="pt-8 pb-8">
   209→
$50

   210→

   211→                per hire — that's it
   212→

   213→

   214→                <BenefitCheck text="Free to post jobs" />
   215→                <BenefitCheck text="Free to browse workers" />
   216→                <BenefitCheck text="Free for workers — always" />
   217→                <BenefitCheck text="Only pay when you confirm a hire" />
   218→                <BenefitCheck text="No monthly subscriptions" />
   219→

   220→            </CardContent>
   221→          </Card>
   222→

   223→

   224→
   225→      {/* Final CTA */}
   226→

   227→

   228→
Ready to hire smarter?

   229→

   230→            Join Sydney contractors already using RateRight.
   231→

   232→

   233→            <Link href="/contractor/signup" className="w-full sm:w-auto">
   234→              <Button
   235→                variant="secondary"
   236→                className="h-12 w-full gap-2 text-base font-semibold"
   237→              >
   238→                <Building2 className="h-5 w-5" />
   239→                Start hiring
   240→              </Button>
   241→            </Link>
   242→            <Link href="/worker/signup" className="w-full sm:w-auto">
   243→              <Button
   244→                variant="secondary"
   245→                className="h-12 w-full gap-2 text-base font-semibold"
   246→              >
   247→                <HardHat className="h-5 w-5" />
   248→                Find work
   249→              </Button>
   250→            </Link>
   251→

   252→

   253→

   254→
   255→      {/* Footer */}
   256→
   257→

   258→

   259→

   260→              <Image src="/logo_black.svg" alt="RateRight" width={110} height={32} className="h-6 w-auto" />
   261→

   262→

   263→              <Link href="/faq" className="hover:text-foreground">
   264→                FAQ
   265→              </Link>
   266→
   270→                Contact
   271→
   272→

   273→

   274→

   275→            The simplest way to hire construction workers in Australia.
   276→

   277→

   278→            © {new Date().getFullYear()} RateRight Pty Ltd. All rights
   279→            reserved.
   280→

   281→

   282→
   283→

   284→  );
   285→}
   286→
   287→function Step({
   288→  number,
   289→  icon,
   290→  title,
   291→  description,
   292→}: {
   293→  number: string;
   294→  icon: React.ReactNode;
   295→  title: string;
   296→  description: string;
   297→}) {
   298→  return (
   299→

   300→

   301→        {number}
   302→

   303→

   304→

   305→          {icon}
   306→
{title}

   307→

   308→

   309→          {description}
   310→

   311→

   312→

   313→  );
   314→}
   315→
   316→function FeatureCard({
   317→  icon,
   318→  title,
   319→  description,
   320→}: {
   321→  icon: React.ReactNode;
   322→  title: string;
   323→  description: string;
   324→}) {
   325→  return (
   326→

   327→
{icon}

   328→

   329→
{title}

   330→
{description}

   331→

   332→

   333→  );
   334→}
   335→
   336→function Benefit({
   337→  icon,
   338→  text,
   339→}: {
   340→  icon: React.ReactNode;
   341→  text: string;
   342→}) {
   343→  return (
   344→

   345→
{icon}

   346→      {text}
   347→

   348→  );
   349→}
   350→
   351→function BenefitCheck({ text }: { text: string }) {
   352→  return (
   353→

   354→      <CheckCircle2 className="h-4 w-4 shrink-0 text-primary" />
   355→      {text}
   356→

   357→  );
   358→}
   359→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


1→"use client";
     2→
     3→import { useState } from "react";
     4→import { useRouter } from "next/navigation";
     5→import Link from "next/link";
     6→import { createClient } from "@/lib/supabase/client";
     7→import { Button } from "@/components/ui/button";
     8→import { Input } from "@/components/ui/input";
     9→import { Label } from "@/components/ui/label";
    10→import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
    11→
    12→export default function LoginPage() {
    13→  const [email, setEmail] = useState("");
    14→  const [password, setPassword] = useState("");
    15→  const [error, setError] = useState<string | null>(null);
    16→  const [loading, setLoading] = useState(false);
    17→  const router = useRouter();
    18→  const supabase = createClient();
    19→
    20→  async function handleLogin(e: React.FormEvent) {
    21→    e.preventDefault();
    22→    setLoading(true);
    23→    setError(null);
    24→
    25→    const { data, error } = await supabase.auth.signInWithPassword({
    26→      email,
    27→      password,
    28→    });
    29→
    30→    if (error) {
    31→      setError(error.message);
    32→      setLoading(false);
    33→      return;
    34→    }
    35→
    36→    // Check if email is verified before allowing dashboard access
    37→    if (data.user && !data.user.email_confirmed_at) {
    38→      router.push("/auth/verify-email");
    39→      router.refresh();
    40→      return;
    41→    }
    42→
    43→    router.push("/dashboard");
    44→    router.refresh();
    45→  }
    46→
    47→  return (
    48→

    49→      <Card className="w-full max-w-md">
    50→        <CardHeader className="text-center">
    51→          <CardTitle className="text-2xl font-bold">Welcome back</CardTitle>
    52→          <CardDescription>Sign in to your RateRight account</CardDescription>
    53→        </CardHeader>
    54→        <CardContent>
    55→          <form onSubmit={handleLogin} className="space-y-4">
    56→

    57→              Email
    58→              <Input
    59→                id="email"
    60→                type="email"
    61→                placeholder="you@example.com"
    62→                value={email}
    63→                onChange={(e) => setEmail(e.target.value)}
    64→                required
    65→                autoComplete="email"
    66→                className="h-12 text-base"
    67→              />
    68→

    69→

    70→              Password
    71→              <Input
    72→                id="password"
    73→                type="password"
    74→                placeholder="Your password"
    75→                value={password}
    76→                onChange={(e) => setPassword(e.target.value)}
    77→                required
    78→                autoComplete="current-password"
    79→                className="h-12 text-base"
    80→              />
    81→

    82→            {error && (
    83→

    84→
{error}

    85→

    86→            )}
    87→            <Button type="submit" className="w-full h-12 text-base font-semibold" disabled={loading}>
    88→              {loading ? "Signing in..." : "Sign in"}
    89→            </Button>
    90→          </form>
    91→

    92→            Don't have an account?{" "}
    93→            <Link href="/auth/signup" className="text-primary underline hover:no-underline">
    94→              Sign up
    95→            </Link>
    96→

    97→        </CardContent>
    98→      </Card>
    99→

   100→  );
   101→}
   102→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


1→"use client";
     2→import { useState } from "react";
     3→import { useRouter } from "next/navigation";
     4→import Link from "next/link";
     5→import { createClient } from "@/lib/supabase/client";
     6→import { Button } from "@/components/ui/button";
     7→import { Input } from "@/components/ui/input";
     8→import { Label } from "@/components/ui/label";
     9→import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
    10→import { HardHat, Building2 } from "lucide-react";
    11→import type { UserType } from "@/lib/types";
    12→
    13→export default function SignupPage() {
    14→  const [email, setEmail] = useState("");
    15→  const [password, setPassword] = useState("");
    16→  const [userType, setUserType] = useState<UserType | null>(null);
    17→  const [agreedToTerms, setAgreedToTerms] = useState(false);
    18→  const [error, setError] = useState<string | null>(null);
    19→  const [loading, setLoading] = useState(false);
    20→  const router = useRouter();
    21→  const supabase = createClient();
    22→
    23→  async function handleSignup(e: React.FormEvent) {
    24→    e.preventDefault();
    25→    if (!userType) {
    26→      setError("Please select whether you're hiring or looking for work");
    27→      return;
    28→    }
    29→    if (!agreedToTerms) {
    30→      setError("Please agree to the Terms of Service and Privacy Policy");
    31→      return;
    32→    }
    33→
    34→    setLoading(true);
    35→    setError(null);
    36→
    37→    const { error } = await supabase.auth.signUp({
    38→      email,
    39→      password,
    40→      options: {
    41→        data: { type: userType },
    42→        emailRedirectTo: `${window.location.origin}/auth/callback`,
    43→      },
    44→    });
    45→
    46→    if (error) {
    47→      setError(error.message);
    48→      setLoading(false);
    49→      return;
    50→    }
    51→
    52→    // Redirect to email verification page instead of dashboard
    53→    router.push("/auth/verify-email");
    54→    router.refresh();
    55→  }
    56→
    57→  return (
    58→

    59→      <Card className="w-full max-w-md">
    60→        <CardHeader className="text-center">
    61→          <CardTitle className="text-2xl font-bold">Create your account</CardTitle>
    62→          <CardDescription>
    63→            Sign up to find work or hire workers
    64→          </CardDescription>
    65→        </CardHeader>
    66→        <CardContent>
    67→          <form onSubmit={handleSignup} className="space-y-4">
    68→

    69→

    70→                Email
    71→                <Input
    72→                  id="email"
    73→                  type="email"
    74→                  value={email}
    75→                  onChange={(e) => setEmail(e.target.value)}
    76→                  required
    77→                  placeholder="you@example.com"
    78→                  className="h-12 text-base"
    79→                />
    80→

    81→

    82→                Password
    83→                <Input
    84→                  id="password"
    85→                  type="password"
    86→                  value={password}
    87→                  onChange={(e) => setPassword(e.target.value)}
    88→                  required
    89→                  placeholder="Create a password"
    90→                  minLength={6}
    91→                  className="h-12 text-base"
    92→                />
    93→

    94→

    95→
    96→

    97→              I am a...
    98→

    99→                <Button
   100→                  type="button"
   101→                  variant={userType === "contractor" ? "default" : "outline"}
   102→                  onClick={() => setUserType("contractor")}
   103→                  className="h-24 flex-col gap-2"
   104→                >
   105→                  <Building2 className="h-6 w-6" />
   106→                  Contractor
   107→                  Hiring workers
   108→                </Button>
   109→                <Button
   110→                  type="button"
   111→                  variant={userType === "worker" ? "default" : "outline"}
   112→                  onClick={() => setUserType("worker")}
   113→                  className="h-24 flex-col gap-2"
   114→                >
   115→                  <HardHat className="h-6 w-6" />
   116→                  Worker
   117→                  Looking for work
   118→                </Button>
   119→

   120→

   121→
   122→

   123→              <input
   124→                type="checkbox"
   125→                id="terms"
   126→                checked={agreedToTerms}
   127→                onChange={(e) => setAgreedToTerms(e.target.checked)}
   128→                className="mt-1 h-4 w-4 rounded border-border accent-primary"
   129→              />
   130→
   131→                I agree to the{" "}
   132→                <Link href="/legal/terms-of-service" className="text-primary underline hover:no-underline" target="_blank">
   133→                  Terms of Service
   134→                </Link>{" "}
   135→                and{" "}
   136→                <Link href="/legal/privacy-policy" className="text-primary underline hover:no-underline" target="_blank">
   137→                  Privacy Policy
   138→                </Link>
   139→
   140→

   141→
   142→            {error && (
   143→

   144→
{error}

   145→

   146→            )}
   147→
   148→            <Button type="submit" className="w-full h-12 text-base font-semibold" disabled={loading || !agreedToTerms}>
   149→              {loading ? "Creating account..." : "Create account"}
   150→            </Button>
   151→
   152→

   153→              Already have an account?{" "}
   154→              <Link href="/auth/login" className="text-primary underline hover:no-underline">
   155→                Sign in
   156→              </Link>
   157→

   158→          </form>
   159→        </CardContent>
   160→      </Card>
   161→

   162→  );
   163→}
   164→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


1→import { redirect } from "next/navigation";
     2→import Link from "next/link";
     3→import { createClient } from "@/lib/supabase/server";
     4→import { Button } from "@/components/ui/button";
     5→import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
     6→import { Badge } from "@/components/ui/badge";
     7→import { Building2, HardHat, Plus, Users } from "lucide-react";
     8→import { SignOutButton } from "@/components/sign-out-button";
     9→import { BottomNavWrapper } from "@/components/bottom-nav-wrapper";
    10→
    11→export default async function DashboardPage() {
    12→  const supabase = await createClient();
    13→
    14→  const {
    15→    data: { user },
    16→  } = await supabase.auth.getUser();
    17→
    18→  if (!user) {
    19→    redirect("/auth/login");
    20→  }
    21→
    22→  const { data: profile } = await supabase
    23→    .from("profiles")
    24→    .select("*")
    25→    .eq("id", user.id)
    26→    .single();
    27→
    28→  const isContractor = profile?.type === "contractor";
    29→
    30→  // Fetch stats
    31→  let jobCount = 0;
    32→  let matchCount = 0;
    33→
    34→  if (isContractor) {
    35→    const { data: company } = await supabase
    36→      .from("companies")
    37→      .select("id")
    38→      .eq("profile_id", user.id)
    39→      .single();
    40→
    41→    if (company) {
    42→      const { count } = await supabase
    43→        .from("jobs")
    44→        .select("*", { count: "exact", head: true })
    45→        .eq("company_id", company.id)
    46→        .eq("status", "active");
    47→      jobCount = count || 0;
    48→
    49→      const { count: mCount } = await supabase
    50→        .from("matches")
    51→        .select("*, jobs!inner(company_id)", { count: "exact", head: true })
    52→        .eq("jobs.company_id", company.id)
    53→        .in("status", ["applied", "suggested"]);
    54→      matchCount = mCount || 0;
    55→    }
    56→  }
    57→
    58→  return (
    59→

    60→
    61→

    62→          G'day{profile?.name ? `, ${profile.name}` : ""}!
    63→

    64→

    65→          {isContractor
    66→            ? "Ready to find your next worker?"
    67→            : "Let's get you on a job."}
    68→

    69→
    70→

    71→          {isContractor ? (
    72→            <>
    73→              {/* Quick stats */}
    74→

    75→                <Card>
    76→                  <CardContent className="flex flex-col items-center p-4">
    77→                    {jobCount}
    78→                    Active Jobs
    79→                  </CardContent>
    80→                </Card>
    81→                <Card>
    82→                  <CardContent className="flex flex-col items-center p-4">
    83→                    {matchCount}
    84→                    New Matches
    85→                  </CardContent>
    86→                </Card>
    87→

    88→
    89→              {/* Post a Job CTA */}
    90→              <Card className="border-primary/30 bg-primary/5">
    91→                <CardHeader className="pb-3">
    92→                  <CardTitle className="flex items-center gap-2 text-lg">
    93→                    <Plus className="h-6 w-6" />
    94→                    Post a Job
    95→                  </CardTitle>
    96→                  <CardDescription className="text-base">
    97→                    AI writes the post. You just tap.
    98→                  </CardDescription>
    99→                </CardHeader>
   100→                <CardContent>
   101→                  <Button asChild className="w-full h-16 text-lg font-semibold">
   102→                    <Link href="/contractor/post-job">Post a Job</Link>
   103→                  </Button>
   104→                </CardContent>
   105→              </Card>
   106→
   107→              {/* View Matches */}
   108→              {matchCount > 0 && (
   109→                <Card>
   110→                  <CardHeader className="pb-3">
   111→                    <CardTitle className="flex items-center gap-2 text-lg">
   112→                      <Users className="h-6 w-6" />
   113→                      {matchCount} Worker{matchCount > 1 ? "s" : ""} Matched
   114→                    </CardTitle>
   115→                    <CardDescription className="text-base">
   116→                      Workers have applied to your jobs
   117→                    </CardDescription>
   118→                  </CardHeader>
   119→                  <CardContent>
   120→                    <Button asChild variant="outline" className="w-full h-14 text-base font-semibold">
   121→                      <Link href="/contractor/matches">View Matches</Link>
   122→                    </Button>
   123→                  </CardContent>
   124→                </Card>
   125→              )}
   126→
   127→              {/* Company profile */}
   128→              <Card>
   129→                <CardHeader className="pb-3">
   130→                  <CardTitle className="flex items-center gap-2 text-lg">
   131→                    <Building2 className="h-6 w-6" />
   132→                    Company Profile
   133→                  </CardTitle>
   134→                  <CardDescription className="text-base">
   135→                    Manage your company details
   136→                  </CardDescription>
   137→                </CardHeader>
   138→                <CardContent>
   139→                  <Button asChild variant="outline" className="w-full h-14 text-base">
   140→                    <Link href="/contractor/signup">Edit Profile</Link>
   141→                  </Button>
   142→                </CardContent>
   143→              </Card>
   144→            </>
   145→          ) : (
   146→            <>
   147→              {/* Browse Jobs CTA */}
   148→              <Card className="border-primary/30 bg-primary/5">
   149→                <CardHeader className="pb-3">
   150→                  <CardTitle className="flex items-center gap-2 text-lg">
   151→                    <HardHat className="h-6 w-6" />
   152→                    Find Work
   153→                  </CardTitle>
   154→                  <CardDescription className="text-base">
   155→                    Browse available construction jobs near you
   156→                  </CardDescription>
   157→                </CardHeader>
   158→                <CardContent>
   159→                  <Button asChild className="w-full h-16 text-lg font-semibold">
   160→                    <Link href="/worker/jobs">Browse Jobs</Link>
   161→                  </Button>
   162→                </CardContent>
   163→              </Card>
   164→
   165→              {/* Profile */}
   166→              <Card>
   167→                <CardHeader className="pb-3">
   168→                  <CardTitle className="flex items-center gap-2 text-lg">
   169→                    <HardHat className="h-6 w-6" />
   170→                    Your Profile
   171→                  </CardTitle>
   172→                  <CardDescription className="text-base">
   173→                    A strong profile gets you more matches
   174→                  </CardDescription>
   175→                </CardHeader>
   176→                <CardContent>
   177→                  <Button asChild variant="outline" className="w-full h-14 text-base">
   178→                    <Link href="/worker/profile/build">Edit Profile</Link>
   179→                  </Button>
   180→                </CardContent>
   181→              </Card>
   182→            </>
   183→          )}
   184→

   185→
   186→
   187→      <BottomNavWrapper userType={profile?.type || "worker"} />
   188→

   189→  );
   190→}
   191→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


Now let me check form validation on several pages and look for broken imports:


1→"use client";
     2→
     3→import { useState, useEffect } from "react";
     4→import { useRouter } from "next/navigation";
     5→import Link from "next/link";
     6→import Image from "next/image";
     7→import { createClient } from "@/lib/supabase/client";
     8→import { Button } from "@/components/ui/button";
     9→import { Input } from "@/components/ui/input";
    10→import { Label } from "@/components/ui/label";
    11→import { Card, CardContent } from "@/components/ui/card";
    12→import {
    13→  Building2,
    14→  Search,
    15→  Sparkles,
    16→  CheckCircle2,
    17→  ArrowLeft,
    18→  ArrowRight,
    19→  Loader2,
    20→  Edit3,
    21→  Globe,
    22→  MapPin,
    23→  Users,
    24→  FileText,
    25→} from "lucide-react";
    26→import { guessCompanyDomain } from "@/lib/company-utils";
    27→import { validateABN, formatABN } from "@/lib/abn-utils";
    28→
    29→type Step = "auth" | "company" | "researching" | "review";
    30→
    31→interface CompanyData {
    32→  name: string;
    33→  abn: string;
    34→  address: string;
    35→  industry: string;
    36→  size: string;
    37→  website: string;
    38→  logoUrl?: string | null;
    39→  logoSource?: 'clearbit' | 'google' | 'none';
    40→}
    41→
    42→async function lookupABN(abn: string): Promise<CompanyData> {
    43→  // Clean the ABN - remove spaces
    44→  const cleanAbn = abn.replace(/\s/g, '');
    45→
    46→  // Call the ABN lookup API
    47→  const response = await fetch(`/api/abn-lookup?abn=${cleanAbn}`);
    48→
    49→  if (!response.ok) {
    50→    const errData = await response.json().catch(() => null);
    51→    throw new Error(errData?.error || 'Failed to lookup ABN');
    52→  }
    53→
    54→  const result = await response.json();
    55→
    56→  if (!result.success || !result.data) {
    57→    throw new Error(result.error || 'Unable to lookup ABN');
    58→  }
    59→
    60→  const data = result.data;
    61→
    62→  // Map the ABR data to our CompanyData format
    63→  const companyData: CompanyData = {
    64→    name: data.name || 'Unknown Company',
    65→    abn: formatABN(data.abn || cleanAbn),
    66→    address: [data.postcode, data.state].filter(Boolean).join(' ') || 'Address not available',
    67→    industry: 'Construction',
    68→    size: 'Unknown',
    69→    website: '',
    70→  };
    71→
    72→  // Try to find company logo
    73→  await fetchCompanyLogo(companyData);
    74→
    75→  return companyData;
    76→}
    77→
    78→async function fetchCompanyLogo(companyData: CompanyData): Promise<void> {
    79→  try {
    80→    // If we have an explicit website, try that first
    81→    if (companyData.website) {
    82→      const domain = extractDomainFromUrl(companyData.website);
    83→      if (domain) {
    84→        const logoResponse = await fetch(`/api/company-logo?domain=${domain}`);
    85→        if (logoResponse.ok) {
    86→          const logoData = await logoResponse.json();
    87→          companyData.logoUrl = logoData.logoUrl;
    88→          companyData.logoSource = logoData.source;
    89→          return;
    90→        }
    91→      }
    92→    }
    93→
    94→    // Otherwise, try to guess domains from company name
    95→    const possibleDomains = guessCompanyDomain(companyData.name);
    96→
    97→    // Try each domain until we find a logo
    98→    for (const domain of possibleDomains) {
    99→      const logoResponse = await fetch(`/api/company-logo?domain=${domain}`);
   100→      if (logoResponse.ok) {
   101→        const logoData = await logoResponse.json();
   102→        if (logoData.logoUrl) {
   103→          companyData.logoUrl = logoData.logoUrl;
   104→          companyData.logoSource = logoData.source;
   105→          return;
   106→        }
   107→      }
   108→    }
   109→
   110→    // No logo found
   111→    companyData.logoUrl = null;
   112→    companyData.logoSource = 'none';
   113→  } catch (error) {
   114→    console.error('Logo fetch failed:', error);
   115→    companyData.logoUrl = null;
   116→    companyData.logoSource = 'none';
   117→  }
   118→}
   119→
   120→function extractDomainFromUrl(url: string): string | null {
   121→  try {
   122→    // Add protocol if missing
   123→    if (!url.match(/^https?:\/\//)) {
   124→      url = 'https://' + url;
   125→    }
   126→
   127→    const urlObj = new URL(url);
   128→    return urlObj.hostname.replace(/^www\./, '');
   129→  } catch {
   130→    return null;
   131→  }
   132→}
   133→
   134→export default function ContractorSignupPage() {
   135→  const [step, setStep] = useState<Step>("auth");
   136→  const [isAuthenticated, setIsAuthenticated] = useState(false);
   137→  const [loading, setLoading] = useState(true);
   138→
   139→  // Auth state
   140→  const [email, setEmail] = useState("");
   141→  const [password, setPassword] = useState("");
   142→  const [authError, setAuthError] = useState<string | null>(null);
   143→  const [authLoading, setAuthLoading] = useState(false);
   144→
   145→  // Company state
   146→  const [companyInput, setCompanyInput] = useState("");
   147→  const [companyData, setCompanyData] = useState<CompanyData | null>(null);
   148→  const [companyError, setCompanyError] = useState<string | null>(null);
   149→  const [editField, setEditField] = useState<string | null>(null);
   150→  const [saving, setSaving] = useState(false);
   151→
   152→  const router = useRouter();
   153→  const supabase = createClient();
   154→
   155→  useEffect(() => {
   156→    async function checkAuth() {
   157→      const {
   158→        data: { user },
   159→      } = await supabase.auth.getUser();
   160→      if (user) {
   161→        setIsAuthenticated(true);
   162→        setStep("company");
   163→      }
   164→      setLoading(false);
   165→    }
   166→    checkAuth();
   167→  }, [supabase.auth]);
   168→
   169→  async function handleAuth(e: React.FormEvent) {
   170→    e.preventDefault();
   171→    setAuthLoading(true);
   172→    setAuthError(null);
   173→
   174→    const { error } = await supabase.auth.signUp({
   175→      email,
   176→      password,
   177→      options: {
   178→        data: { type: "contractor" },
   179→        emailRedirectTo: `${window.location.origin}/auth/callback`,
   180→      },
   181→    });
   182→
   183→    if (error) {
   184→      if (error.message.includes("already registered")) {
   185→        const { error: loginError } = await supabase.auth.signInWithPassword({
   186→          email,
   187→          password,
   188→        });
   189→        if (loginError) {
   190→          setAuthError(loginError.message);
   191→          setAuthLoading(false);
   192→          return;
   193→        }
   194→        // If login successful, check if email is verified
   195→        const { data: { user } } = await supabase.auth.getUser();
   196→        if (user && !user.email_confirmed_at) {
   197→          // Email not verified, redirect to verification page
   198→          router.push("/auth/verify-email");
   199→          return;
   200→        }
   201→      } else {
   202→        setAuthError(error.message);
   203→        setAuthLoading(false);
   204→        return;
   205→      }
   206→    }
   207→
   208→    // Check if email verification is required
   209→    const { data: { user } } = await supabase.auth.getUser();
   210→    if (user && !user.email_confirmed_at) {
   211→      // Email not verified, redirect to verification page
   212→      router.push("/auth/verify-email");
   213→      return;
   214→    }
   215→
   216→    setIsAuthenticated(true);
   217→    setStep("company");
   218→    setAuthLoading(false);
   219→  }
   220→
   221→  async function handleCompanySearch(e: React.FormEvent) {
   222→    e.preventDefault();
   223→    if (!companyInput.trim()) return;
   224→    setCompanyError(null);
   225→
   226→    const input = companyInput.trim();
   227→
   228→    // Validate ABN checksum using the shared utility
   229→    if (!validateABN(input)) {
   230→      setCompanyError("Please enter a valid 11-digit ABN. Check the number and try again.");
   231→      return;
   232→    }
   233→
   234→    setStep("researching");
   235→
   236→    try {
   237→      const data = await lookupABN(input);
   238→      setCompanyData(data);
   239→      setStep("review");
   240→    } catch (error) {
   241→      setCompanyError(error instanceof Error ? error.message : 'Failed to lookup ABN');
   242→      setStep("company");
   243→    }
   244→  }
   245→
   246→  async function handleConfirm() {
   247→    if (!companyData) return;
   248→    setSaving(true);
   249→
   250→    const {
   251→      data: { user },
   252→    } = await supabase.auth.getUser();
   253→    if (!user) return;
   254→
   255→    await supabase.from("profiles").update({
   256→      name: companyData.name,
   257→      onboarding_completed: true,
   258→    }).eq("id", user.id);
   259→
   260→    await supabase.from("companies").insert({
   261→      profile_id: user.id,
   262→      name: companyData.name,
   263→      abn: companyData.abn,
   264→      address: companyData.address,
   265→      industry: companyData.industry,
   266→      size: companyData.size,
   267→      website: companyData.website,
   268→      ai_research: {
   269→        abr_verified: true,
   270→        abn: companyData.abn,
   271→        verified_at: new Date().toISOString(),
   272→        source: "abr",
   273→      },
   274→    });
   275→
   276→    router.push("/dashboard");
   277→    router.refresh();
   278→  }
   279→
   280→  function updateCompanyField(field: keyof CompanyData, value: string) {
   281→    if (!companyData) return;
   282→    setCompanyData({ ...companyData, [field]: value });
   283→    setEditField(null);
   284→  }
   285→
   286→  const totalSteps = isAuthenticated ? 3 : 4;
   287→  const currentStep =
   288→    step === "auth" ? 0 : step === "company" ? 1 : step === "researching" ? 2 : 3;
   289→
   290→  if (loading) {
   291→    return (
   292→

   293→        <Loader2 className="h-8 w-8 animate-spin text-primary" />
   294→

   295→    );
   296→  }
   297→
   298→  return (
   299→

   300→      {/* Header */}
   301→
   302→

   303→          <Link href="/" className="flex items-center gap-2">
   304→            <Image src="/logo_black.svg" alt="RateRight" width={120} height={34} className="h-7 w-auto" />
   305→          </Link>
   306→

   307→
   308→
   309→
   310→        {/* Progress bar */}
   311→

   312→          {Array.from({ length: totalSteps }, (_, i) => (
   313→            <div
   314→              key={i}
   315→              className={`h-1.5 flex-1 rounded-full transition-colors ${
   316→                i <= currentStep - (isAuthenticated ? 1 : 0)
   317→                  ? "bg-primary"
   318→                  : "bg-muted"
   319→              }`}
   320→            />
   321→          ))}
   322→

   323→
   324→        {/* Step: Auth */}
   325→        {step === "auth" && (
   326→

   327→

   328→

   329→                <Building2 className="h-5 w-5" />
   330→

   331→

   332→
Create your account

   333→

   334→                  Quick signup to get started
   335→

   336→

   337→

   338→
   339→            <form onSubmit={handleAuth} className="mt-6 space-y-4">
   340→

   341→                Email
   342→                <Input
   343→                  id="email"
   344→                  type="email"
   345→                  placeholder="you@company.com.au"
   346→                  value={email}
   347→                  onChange={(e) => setEmail(e.target.value)}
   348→                  required
   349→                  autoComplete="email"
   350→                  className="h-12 text-base"
   351→                />
   352→

   353→

   354→                Password
   355→                <Input
   356→                  id="password"
   357→                  type="password"
   358→                  placeholder="Create a password"
   359→                  value={password}
   360→                  onChange={(e) => setPassword(e.target.value)}
   361→                  required
   362→                  minLength={6}
   363→                  autoComplete="new-password"
   364→                  className="h-12 text-base"
   365→                />
   366→

   367→
   368→              {authError && (
   369→
{authError}

   370→              )}
   371→
   372→              <Button
   373→                type="submit"
   374→                className="h-12 w-full gap-2 text-base font-semibold"
   375→                disabled={authLoading}
   376→              >
   377→                {authLoading ? (
   378→                  <>
   379→                    <Loader2 className="h-5 w-5 animate-spin" />
   380→                    Creating account...
   381→                  </>
   382→                ) : (
   383→                  <>
   384→                    Continue
   385→                    <ArrowRight className="h-5 w-5" />
   386→                  </>
   387→                )}
   388→              </Button>
   389→            </form>
   390→
   391→

   392→              Already have an account?{" "}
   393→              <Link
   394→                href="/auth/login"
   395→                className="text-primary underline hover:no-underline"
   396→              >
   397→                Sign in
   398→              </Link>
   399→

   400→

   401→        )}
   402→
   403→        {/* Step: Enter company name/ABN */}
   404→        {step === "company" && (
   405→

   406→

   407→

   408→                <Search className="h-5 w-5" />
   409→

   410→

   411→
Your company

   412→

   413→                  We'll verify it via ABR
   414→

   415→

   416→

   417→
   418→            <form onSubmit={handleCompanySearch} className="mt-6 space-y-4">
   419→

   420→                ABN
   421→                <Input
   422→                  id="company"
   423→                  type="text"
   424→                  placeholder="Enter your 11-digit ABN"
   425→                  value={companyInput}
   426→                  onChange={(e) => setCompanyInput(e.target.value)}
   427→                  required
   428→                  autoFocus
   429→                  className="h-12 text-base"
   430→                />
   431→

   432→                  Enter your 11-digit ABN for verification via the Australian Business Register.
   433→

   434→

   435→
   436→              {companyError && (
   437→
{companyError}

   438→              )}
   439→
   440→              <Button
   441→                type="submit"
   442→                className="h-12 w-full gap-2 text-base font-semibold"
   443→              >
   444→                <Search className="h-5 w-5" />
   445→                Verify my ABN
   446→              </Button>
   447→            </form>
   448→
   449→            {!isAuthenticated && (
   450→              <button
   451→                onClick={() => setStep("auth")}
   452→                className="mt-4 flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground"
   453→              >
   454→                <ArrowLeft className="h-4 w-4" />
   455→                Back
   456→              </button>
   457→            )}
   458→

   459→        )}
   460→
   461→        {/* Step: ABR Verification */}
   462→        {step === "researching" && (
   463→

   464→

   465→              <Sparkles className="h-10 w-10 animate-pulse text-primary" />
   466→

   467→
Verifying your business

   468→

   469→              Checking ABR for your business details...
   470→

   471→
   472→

   473→              <ResearchStep label="Checking ABR..." />
   474→              <ResearchStep label="Verifying company..." />
   475→              <ResearchStep label="Done" />
   476→

   477→

   478→        )}
   479→
   480→        {/* Step: Review profile */}
   481→        {step === "review" && companyData && (
   482→

   483→

   484→

   485→                <CheckCircle2 className="h-5 w-5" />
   486→

   487→

   488→
Confirm your profile

   489→

   490→                  Tap any field to edit
   491→

   492→

   493→

   494→
   495→            <Card className="mt-6">
   496→              <CardContent className="space-y-4 pt-6">
   497→                <EditableField
   498→                  icon={<Building2 className="h-4 w-4" />}
   499→                  label="Company name"
   500→                  value={companyData.name}
   501→                  fieldKey="name"
   502→                  editField={editField}
   503→                  setEditField={setEditField}
   504→                  onSave={(v) => updateCompanyField("name", v)}
   505→                  logoUrl={companyData.logoUrl}
   506→                />
   507→                <EditableField
   508→                  icon={<FileText className="h-4 w-4" />}
   509→                  label="ABN"
   510→                  value={companyData.abn}
   511→                  fieldKey="abn"
   512→                  editField={editField}
   513→                  setEditField={setEditField}
   514→                  onSave={(v) => updateCompanyField("abn", v)}
   515→                />
   516→                <EditableField
   517→                  icon={<MapPin className="h-4 w-4" />}
   518→                  label="Address"
   519→                  value={companyData.address}
   520→                  fieldKey="address"
   521→                  editField={editField}
   522→                  setEditField={setEditField}
   523→                  onSave={(v) => updateCompanyField("address", v)}
   524→                />
   525→                <EditableField
   526→                  icon={<Building2 className="h-4 w-4" />}
   527→                  label="Industry"
   528→                  value={companyData.industry}
   529→                  fieldKey="industry"
   530→                  editField={editField}
   531→                  setEditField={setEditField}
   532→                  onSave={(v) => updateCompanyField("industry", v)}
   533→                />
   534→                <EditableField
   535→                  icon={<Users className="h-4 w-4" />}
   536→                  label="Company size"
   537→                  value={companyData.size}
   538→                  fieldKey="size"
   539→                  editField={editField}
   540→                  setEditField={setEditField}
   541→                  onSave={(v) => updateCompanyField("size", v)}
   542→                />
   543→                <EditableField
   544→                  icon={<Globe className="h-4 w-4" />}
   545→                  label="Website"
   546→                  value={companyData.website}
   547→                  fieldKey="website"
   548→                  editField={editField}
   549→                  setEditField={setEditField}
   550→                  onSave={(v) => updateCompanyField("website", v)}
   551→                />
   552→              </CardContent>
   553→            </Card>
   554→
   555→

   556→

   557→                <CheckCircle2 className="mt-0.5 h-4 w-4 shrink-0 text-green-600" />
   558→

   559→

   560→                    Verified via Australian Business Register
   561→

   562→

   563→                    Your ABN has been verified. Tap any field to correct it.
   564→

   565→

   566→

   567→

   568→
   569→            <Button
   570→              onClick={handleConfirm}
   571→              className="mt-6 h-14 w-full gap-2 text-base font-semibold"
   572→              disabled={saving}
   573→            >
   574→              {saving ? (
   575→                <>
   576→                  <Loader2 className="h-5 w-5 animate-spin" />
   577→                  Saving...
   578→                </>
   579→              ) : (
   580→                <>
   581→                  <CheckCircle2 className="h-5 w-5" />
   582→                  Looks good — create my profile
   583→                </>
   584→              )}
   585→            </Button>
   586→
   587→            <button
   588→              onClick={() => setStep("company")}
   589→              className="mt-4 flex w-full items-center justify-center gap-1 text-sm text-muted-foreground hover:text-foreground"
   590→            >
   591→              <ArrowLeft className="h-4 w-4" />
   592→              Search again
   593→            </button>
   594→

   595→        )}
   596→
   597→

   598→  );
   599→}
   600→
   601→function ResearchStep({ label }: { label: string }) {
   602→  return (
   603→

   604→      <Loader2 className="h-4 w-4 animate-spin text-primary" />
   605→      {label}
   606→

   607→  );
   608→}
   609→
   610→function EditableField({
   611→  icon,
   612→  label,
   613→  value,
   614→  fieldKey,
   615→  editField,
   616→  setEditField,
   617→  onSave,
   618→  logoUrl,
   619→}: {
   620→  icon: React.ReactNode;
   621→  label: string;
   622→  value: string;
   623→  fieldKey: string;
   624→  editField: string | null;
   625→  setEditField: (f: string | null) => void;
   626→  onSave: (value: string) => void;
   627→  logoUrl?: string | null;
   628→}) {
   629→  const [editValue, setEditValue] = useState(value);
   630→  const isEditing = editField === fieldKey;
   631→
   632→  if (isEditing) {
   633→    return (
   634→

   635→        {label}
   636→

   637→          {fieldKey === 'name' && logoUrl ? (
   638→

   639→              <img
   640→                src={logoUrl}
   641→                alt={`${value} logo`}
   642→                className="h-8 w-8 rounded-full object-cover"
   643→                onError={(e) => {
   644→                  e.currentTarget.style.display = 'none';
   645→                }}
   646→              />
   647→              <Input
   648→                value={editValue}
   649→                onChange={(e) => setEditValue(e.target.value)}
   650→                className="h-10 text-base"
   651→                autoFocus
   652→              />
   653→

   654→          ) : (
   655→            <Input
   656→              value={editValue}
   657→              onChange={(e) => setEditValue(e.target.value)}
   658→              className="h-10 text-base"
   659→              autoFocus
   660→            />
   661→          )}
   662→          <Button
   663→            size="sm"
   664→            onClick={() => onSave(editValue)}
   665→            className="h-10 shrink-0"
   666→          >
   667→            Save
   668→          </Button>
   669→

   670→

   671→    );
   672→  }
   673→
   674→  return (
   675→    <button
   676→      onClick={() => {
   677→        setEditValue(value);
   678→        setEditField(fieldKey);
   679→      }}
   680→      className="flex w-full items-start gap-3 rounded-lg p-2 text-left transition-colors hover:bg-muted"
   681→    >
   682→      {fieldKey === 'name' && logoUrl ? (
   683→

   684→          <img
   685→            src={logoUrl}
   686→            alt={`${value} logo`}
   687→            className="h-6 w-6 rounded-full object-cover"
   688→            onError={(e) => {
   689→              // If image fails to load, fall back to icon
   690→              e.currentTarget.style.display = 'none';
   691→            }}
   692→          />
   693→

   694→      ) : (
   695→
{icon}

   696→      )}
   697→

   698→
{label}

   699→
{value}

   700→

   701→      <Edit3 className="mt-1 h-3.5 w-3.5 shrink-0 text-muted-foreground" />
   702→    </button>
   703→  );
   704→}
   705→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


1→"use client";
     2→
     3→import { useState, useEffect, useCallback } from "react";
     4→import { useRouter } from "next/navigation";
     5→import Link from "next/link";
     6→import toast from "react-hot-toast";
     7→import { createClient } from "@/lib/supabase/client";
     8→import { Button } from "@/components/ui/button";
     9→import { Input } from "@/components/ui/input";
    10→import { Label } from "@/components/ui/label";
    11→import { Card, CardContent } from "@/components/ui/card";
    12→import VoiceRecorder from "@/components/VoiceRecorder";
    13→import { csrfFetch, addCSRFTokenToFormData, initializeCSRF } from "@/lib/csrf-client";
    14→import {
    15→  HardHat,
    16→  ArrowLeft,
    17→  ArrowRight,
    18→  Loader2,
    19→  CheckCircle2,
    20→  Sparkles,
    21→  MapPin,
    22→  Clock,
    23→  Shield,
    24→  Edit3,
    25→  Wrench,
    26→  Calendar,
    27→  Zap,
    28→  Mic,
    29→} from "lucide-react";
    30→
    31→type Step = "trade" | "experience" | "location" | "availability" | "generating" | "review";
    32→
    33→const TRADES = [
    34→  "Labourer",
    35→  "Steel Fixer",
    36→  "Formworker",
    37→  "Carpenter",
    38→  "Electrician",
    39→  "Plumber",
    40→  "Concreter",
    41→  "Bricklayer",
    42→  "Painter",
    43→  "Roofer",
    44→  "Scaffolder",
    45→  "Tiler",
    46→  "Plasterer",
    47→  "Welder",
    48→  "Crane Operator",
    49→  "Excavator Operator",
    50→];
    51→
    52→const EXPERIENCE_OPTIONS = [
    53→  { label: "Less than 1 year", value: 0 },
    54→  { label: "1-3 years", value: 2 },
    55→  { label: "3-5 years", value: 4 },
    56→  { label: "5-10 years", value: 7 },
    57→  { label: "10+ years", value: 12 },
    58→];
    59→
    60→const AVAILABILITY_OPTIONS = [
    61→  { label: "Full-time", value: "full-time" as const, desc: "Mon-Fri, regular hours" },
    62→  { label: "Casual", value: "casual" as const, desc: "As needed, flexible" },
    63→  { label: "Weekends", value: "weekends" as const, desc: "Saturday & Sunday" },
    64→  { label: "Flexible", value: "flexible" as const, desc: "Any time, any days" },
    65→];
    66→
    67→interface ProfileData {
    68→  trade: string;
    69→  experience: number;
    70→  location: string;
    71→  availability: "full-time" | "casual" | "weekends" | "flexible";
    72→}
    73→
    74→interface GeneratedProfile {
    75→  bio: string;
    76→  skills: string[];
    77→  certifications: string[];
    78→}
    79→
    80→function closestExperience(years: number): number {
    81→  const values = EXPERIENCE_OPTIONS.map((o) => o.value);
    82→  return values.reduce((prev, curr) =>
    83→    Math.abs(curr - years) < Math.abs(prev - years) ? curr : prev
    84→  );
    85→}
    86→
    87→export default function ProfileBuildPage() {
    88→  const [step, setStep] = useState<Step>("trade");
    89→  const [loading, setLoading] = useState(true);
    90→  const [generating, setGenerating] = useState(false);
    91→  const [saving, setSaving] = useState(false);
    92→
    93→  // Form data
    94→  const [trade, setTrade] = useState<string>("");
    95→  const [experience, setExperience] = useState<number | null>(null);
    96→  const [location, setLocation] = useState("");
    97→  const [availability, setAvailability] = useState<ProfileData["availability"] | null>(null);
    98→
    99→  // Generated data
   100→  const [generatedBio, setGeneratedBio] = useState("");
   101→  const [generatedSkills, setGeneratedSkills] = useState<string[]>([]);
   102→  const [generatedCerts, setGeneratedCerts] = useState<string[]>([]);
   103→
   104→  // Editable states
   105→  const [editedBio, setEditedBio] = useState("");
   106→  const [editedSkills, setEditedSkills] = useState<string[]>([]);
   107→  const [editedCerts, setEditedCerts] = useState<string[]>([]);
   108→
   109→  // Voice flow
   110→  const [showVoice, setShowVoice] = useState(false);
   111→  const [voiceProcessing, setVoiceProcessing] = useState(false);
   112→  const [voiceStatus, setVoiceStatus] = useState("");
   113→  const [voiceError, setVoiceError] = useState<string | null>(null);
   114→
   115→  const router = useRouter();
   116→  const supabase = createClient();
   117→
   118→  useEffect(() => {
   119→    async function checkAuth() {
   120→      const {
   121→        data: { user },
   122→      } = await supabase.auth.getUser();
   123→      if (!user) {
   124→        router.push("/auth/login");
   125→        return;
   126→      }
   127→      // Initialize CSRF token for API calls
   128→      await initializeCSRF();
   129→      setLoading(false);
   130→    }
   131→    checkAuth();
   132→  }, [supabase.auth, router]);
   133→
   134→  async function generateProfile() {
   135→    if (!trade || experience === null || !location || !availability) return;
   136→
   137→    setGenerating(true);
   138→    setStep("generating");
   139→
   140→    try {
   141→      const response = await csrfFetch("/api/ai/generate-profile", {
   142→        method: "POST",
   143→        headers: { "Content-Type": "application/json" },
   144→        body: JSON.stringify({
   145→          trade,
   146→          experience,
   147→          location,
   148→          availability,
   149→        }),
   150→      });
   151→
   152→      if (!response.ok) {
   153→        const errorData = await response.json().catch(() => ({}));
   154→        if (response.status === 401) {
   155→          toast.error("Session expired. Please log in again.");
   156→          router.push("/auth/login");
   157→          return;
   158→        }
   159→        if (response.status === 429) {
   160→          toast.error("Too many requests. Please wait a few minutes and try again.");
   161→          setStep("availability");
   162→          return;
   163→        }
   164→        throw new Error(errorData.error || "Failed to generate profile");
   165→      }
   166→
   167→      const data: GeneratedProfile = await response.json();
   168→
   169→      setGeneratedBio(data.bio);
   170→      setGeneratedSkills(data.skills);
   171→      setGeneratedCerts(data.certifications);
   172→
   173→      // Initialize editable versions
   174→      setEditedBio(data.bio);
   175→      setEditedSkills(data.skills);
   176→      setEditedCerts(data.certifications);
   177→
   178→      setStep("review");
   179→    } catch (error) {
   180→      console.error("Generation error:", error);
   181→      const message = error instanceof Error ? error.message : "Failed to generate profile";
   182→      toast.error(`${message}. Please try again.`);
   183→      setStep("availability");
   184→    } finally {
   185→      setGenerating(false);
   186→    }
   187→  }
   188→
   189→  const handleVoiceComplete = useCallback(async (blob: Blob) => {
   190→    setVoiceProcessing(true);
   191→    setVoiceError(null);
   192→    setVoiceStatus("Transcribing your voice...");
   193→
   194→    try {
   195→      // Step 1: Transcribe (FormData with CSRF token)
   196→      const formData = new FormData();
   197→      formData.append("audio", blob, "recording.webm");
   198→      addCSRFTokenToFormData(formData);
   199→
   200→      const transcribeRes = await fetch("/api/ai/transcribe-voice", {
   201→        method: "POST",
   202→        body: formData,
   203→      });
   204→
   205→      if (!transcribeRes.ok) {
   206→        if (transcribeRes.status === 429) {
   207→          throw new Error("Too many requests. Please wait a few minutes.");
   208→        }
   209→        const errData = await transcribeRes.json().catch(() => ({}));
   210→        throw new Error(errData.error || "Failed to transcribe audio");
   211→      }
   212→
   213→      const { text } = await transcribeRes.json();
   214→      if (!text) throw new Error("No speech detected. Please try again.");
   215→
   216→      setVoiceStatus("Understanding your details...");
   217→
   218→      // Step 2: Parse profile fields (JSON with CSRF header)
   219→      const parseRes = await csrfFetch("/api/ai/parse-profile-voice", {
   220→        method: "POST",
   221→        headers: { "Content-Type": "application/json" },
   222→        body: JSON.stringify({ transcript: text }),
   223→      });
   224→
   225→      if (!parseRes.ok) {
   226→        const errData = await parseRes.json().catch(() => ({}));
   227→        throw new Error(errData.error || "Failed to parse your details");
   228→      }
   229→
   230→      const parsed = await parseRes.json();
   231→
   232→      // Pre-fill fields
   233→      if (parsed.trade) setTrade(parsed.trade);
   234→      if (parsed.experience_years !== null && parsed.experience_years !== undefined) {
   235→        setExperience(closestExperience(parsed.experience_years));
   236→      }
   237→      if (parsed.location) setLocation(parsed.location);
   238→      if (parsed.availability) setAvailability(parsed.availability);
   239→
   240→      // Check what we got and move to the right step
   241→      setShowVoice(false);
   242→      setVoiceStatus("");
   243→
   244→      const hasTrade = !!parsed.trade;
   245→      const hasExp = parsed.experience_years !== null && parsed.experience_years !== undefined;
   246→      const hasLoc = !!parsed.location;
   247→      const hasAvail = !!parsed.availability;
   248→
   249→      if (hasTrade && hasExp && hasLoc && hasAvail) {
   250→        // All fields filled — go straight to generating
   251→        // Need to call generateProfile after state updates, so use a timeout
   252→        setTimeout(() => {
   253→          setStep("generating");
   254→        }, 0);
   255→      } else if (!hasTrade) {
   256→        setStep("trade");
   257→      } else if (!hasExp) {
   258→        setStep("experience");
   259→      } else if (!hasLoc) {
   260→        setStep("location");
   261→      } else {
   262→        setStep("availability");
   263→      }
   264→    } catch (error) {
   265→      console.error("Voice processing error:", error);
   266→      setVoiceError(error instanceof Error ? error.message : "Something went wrong. Please try again.");
   267→      setVoiceStatus("");
   268→    } finally {
   269→      setVoiceProcessing(false);
   270→    }
   271→  }, []);
   272→
   273→  // When step changes to "generating" from voice flow, trigger profile generation
   274→  useEffect(() => {
   275→    if (step === "generating" && !generating && trade && experience !== null && location && availability) {
   276→      generateProfile();
   277→    }
   278→    // eslint-disable-next-line react-hooks/exhaustive-deps
   279→  }, [step]);
   280→
   281→  async function handleSave() {
   282→    setSaving(true);
   283→
   284→    try {
   285→      const {
   286→        data: { user },
   287→      } = await supabase.auth.getUser();
   288→      if (!user) throw new Error("Not authenticated");
   289→
   290→      // Update or create worker profile
   291→      const { data: existingProfile } = await supabase
   292→        .from("worker_profiles")
   293→        .select("id")
   294→        .eq("profile_id", user.id)
   295→        .single();
   296→
   297→      const profileData = {
   298→        profile_id: user.id,
   299→        trades: [trade],
   300→        experience_years: experience,
   301→        availability,
   302→        certifications: editedCerts,
   303→        bio: editedBio,
   304→        ai_extracted: {
   305→          skills: editedSkills,
   306→          generated_at: new Date().toISOString(),
   307→          original_bio: generatedBio,
   308→          original_skills: generatedSkills,
   309→          original_certs: generatedCerts,
   310→        },
   311→      };
   312→
   313→      if (existingProfile) {
   314→        await supabase
   315→          .from("worker_profiles")
   316→          .update(profileData)
   317→          .eq("profile_id", user.id);
   318→      } else {
   319→        await supabase.from("worker_profiles").insert(profileData);
   320→      }
   321→
   322→      // Update main profile
   323→      await supabase
   324→        .from("profiles")
   325→        .update({
   326→          suburb: location,
   327→          onboarding_completed: true,
   328→        })
   329→        .eq("id", user.id);
   330→
   331→      router.push("/dashboard");
   332→      router.refresh();
   333→    } catch (error) {
   334→      console.error("Save error:", error);
   335→      toast.error("Failed to save profile. Please try again.");
   336→    } finally {
   337→      setSaving(false);
   338→    }
   339→  }
   340→
   341→  const stepOrder: Step[] = ["trade", "experience", "location", "availability", "generating", "review"];
   342→  const currentIndex = stepOrder.indexOf(step);
   343→  const totalSteps = 4; // Only count user-facing steps
   344→
   345→  if (loading) {
   346→    return (
   347→

   348→        <Loader2 className="h-8 w-8 animate-spin text-primary" />
   349→

   350→    );
   351→  }
   352→
   353→  return (
   354→

   355→
   356→        {/* Progress bar */}
   357→        {step !== "generating" && step !== "review" && (
   358→

   359→            {Array.from({ length: totalSteps }, (_, i) => (
   360→              <div
   361→                key={i}
   362→                className={`h-1.5 flex-1 rounded-full transition-colors ${
   363→                  i <= currentIndex ? "bg-primary" : "bg-muted"
   364→                }`}
   365→              />
   366→            ))}
   367→

   368→        )}
   369→
   370→        {/* Step: Trade */}
   371→        {step === "trade" && (
   372→

   373→            <StepHeader
   374→              icon={<Wrench className="h-5 w-5" />}
   375→              title="What's your trade?"
   376→              subtitle="Pick your primary trade"
   377→            />
   378→
   379→            {/* Voice option */}
   380→            {!showVoice && (
   381→              <button
   382→                onClick={() => setShowVoice(true)}
   383→                className="mt-6 flex w-full items-center justify-center gap-3 rounded-xl border-2 border-dashed border-primary/40 bg-primary/5 px-5 py-4 text-base font-medium text-primary transition-colors hover:border-primary hover:bg-primary/10 active:scale-95"
   384→                style={{ minHeight: "64px" }}
   385→              >
   386→                <Mic className="h-6 w-6" />
   387→                Or tell us with your voice
   388→              </button>
   389→            )}
   390→
   391→            {showVoice && (
   392→              <Card className="mt-6">
   393→                <CardContent className="flex flex-col items-center gap-4 pt-6">
   394→

   395→                    Say something like: “I'm a chippie with 8 years experience in Parramatta, looking for full-time work”
   396→

   397→
   398→                  {voiceProcessing ? (
   399→

   400→                      <Loader2 className="h-10 w-10 animate-spin text-primary" />
   401→
{voiceStatus}

   402→

   403→                  ) : (
   404→                    <VoiceRecorder onRecordingComplete={handleVoiceComplete} />
   405→                  )}
   406→
   407→                  {voiceError && (
   408→
{voiceError}

   409→                  )}
   410→
   411→                  <button
   412→                    onClick={() => {
   413→                      setShowVoice(false);
   414→                      setVoiceError(null);
   415→                    }}
   416→                    className="text-sm text-muted-foreground hover:text-foreground"
   417→                  >
   418→                    Cancel — I'll tap instead
   419→                  </button>
   420→                </CardContent>
   421→              </Card>
   422→            )}
   423→
   424→

   425→              {TRADES.map((t) => (
   426→                <button
   427→                  key={t}
   428→                  onClick={() => {
   429→                    setTrade(t);
   430→                    setStep("experience");
   431→                  }}
   432→                  className={`rounded-xl border-2 px-4 py-4 text-left text-base font-medium transition-colors active:scale-95 ${
   433→                    trade === t
   434→                      ? "border-primary bg-primary/10 text-primary"
   435→                      : "border-border hover:border-primary/50"
   436→                  }`}
   437→                  style={{ minHeight: "64px" }}
   438→                >
   439→                  {t}
   440→                </button>
   441→              ))}
   442→

   443→

   444→        )}
   445→
   446→        {/* Step: Experience */}
   447→        {step === "experience" && (
   448→

   449→            <StepHeader
   450→              icon={<Clock className="h-5 w-5" />}
   451→              title="Your experience?"
   452→              subtitle="Years in construction"
   453→            />
   454→
   455→

   456→              {EXPERIENCE_OPTIONS.map((opt) => (
   457→                <button
   458→                  key={opt.value}
   459→                  onClick={() => {
   460→                    setExperience(opt.value);
   461→                    setStep("location");
   462→                  }}
   463→                  className={`flex w-full items-center justify-between rounded-xl border-2 px-5 text-left text-base font-medium transition-colors active:scale-95 ${
   464→                    experience === opt.value
   465→                      ? "border-primary bg-primary/10 text-primary"
   466→                      : "border-border hover:border-primary/50"
   467→                  }`}
   468→                  style={{ minHeight: "64px" }}
   469→                >
   470→                  {opt.label}
   471→                  <ArrowRight className="h-5 w-5 text-muted-foreground" />
   472→                </button>
   473→              ))}
   474→

   475→
   476→            <button
   477→              onClick={() => setStep("trade")}
   478→              className="mt-4 flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground"
   479→            >
   480→              <ArrowLeft className="h-4 w-4" />
   481→              Back
   482→            </button>
   483→

   484→        )}
   485→
   486→        {/* Step: Location */}
   487→        {step === "location" && (
   488→

   489→            <StepHeader
   490→              icon={<MapPin className="h-5 w-5" />}
   491→              title="Where are you based?"
   492→              subtitle="Your suburb or area"
   493→            />
   494→
   495→            <form
   496→              onSubmit={(e) => {
   497→                e.preventDefault();
   498→                if (location.trim()) setStep("availability");
   499→              }}
   500→              className="mt-6 space-y-4"
   501→            >
   502→

   503→                Suburb or Area
   504→                <Input
   505→                  id="location"
   506→                  placeholder="e.g. Parramatta, Blacktown"
   507→                  value={location}
   508→                  onChange={(e) => setLocation(e.target.value)}
   509→                  required
   510→                  autoFocus
   511→                  className="h-14 text-base"
   512→                />
   513→

   514→

   515→                We'll match you to nearby jobs
   516→

   517→
   518→

   519→                <Button
   520→                  type="button"
   521→                  variant="outline"
   522→                  onClick={() => setStep("experience")}
   523→                  className="h-14 w-14 p-0"
   524→                >
   525→                  <ArrowLeft className="h-5 w-5" />
   526→                </Button>
   527→                <Button
   528→                  type="submit"
   529→                  disabled={!location.trim()}
   530→                  className="h-14 flex-1 gap-2 text-base font-semibold"
   531→                >
   532→                  Continue
   533→                  <ArrowRight className="h-5 w-5" />
   534→                </Button>
   535→

   536→            </form>
   537→

   538→        )}
   539→
   540→        {/* Step: Availability */}
   541→        {step === "availability" && (
   542→

   543→            <StepHeader
   544→              icon={<Calendar className="h-5 w-5" />}
   545→              title="Your availability?"
   546→              subtitle="When can you work?"
   547→            />
   548→
   549→

   550→              {AVAILABILITY_OPTIONS.map((opt) => (
   551→                <button
   552→                  key={opt.value}
   553→                  onClick={() => {
   554→                    setAvailability(opt.value);
   555→                    generateProfile();
   556→                  }}
   557→                  className={`flex w-full items-start justify-between rounded-xl border-2 px-5 py-4 text-left transition-colors active:scale-95 ${
   558→                    availability === opt.value
   559→                      ? "border-primary bg-primary/10"
   560→                      : "border-border hover:border-primary/50"
   561→                  }`}
   562→                  style={{ minHeight: "64px" }}
   563→                >
   564→

   565→                    <p className={`text-base font-semibold ${availability === opt.value ? "text-primary" : ""}`}>
   566→                      {opt.label}
   567→

   568→
{opt.desc}

   569→

   570→                  <Zap className="h-5 w-5 shrink-0 text-primary" />
   571→                </button>
   572→              ))}
   573→

   574→
   575→            <button
   576→              onClick={() => setStep("location")}
   577→              className="mt-4 flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground"
   578→            >
   579→              <ArrowLeft className="h-4 w-4" />
   580→              Back
   581→            </button>
   582→

   583→        )}
   584→
   585→        {/* Step: Generating */}
   586→        {step === "generating" && (
   587→

   588→

   589→              <Sparkles className="h-10 w-10 animate-pulse text-primary" />
   590→

   591→
Building your profile...

   592→

   593→              AI is generating your bio, skills, and certifications
   594→

   595→

   596→

   597→

   598→

   599→

   600→

   601→        )}
   602→
   603→        {/* Step: Review */}
   604→        {step === "review" && (
   605→

   606→

   607→

   608→                <Sparkles className="h-5 w-5" />
   609→

   610→

   611→
Review & Edit

   612→

   613→                  AI generated this — customise as needed
   614→

   615→

   616→

   617→
   618→            {/* Your Answers Summary */}
   619→            <Card className="mt-6">
   620→              <CardContent className="space-y-3 pt-6">
   621→

   622→

   623→                    <Wrench className="h-4 w-4 text-muted-foreground" />
   624→                    {trade}
   625→

   626→                  <button
   627→                    onClick={() => setStep("trade")}
   628→                    className="text-xs text-primary hover:underline"
   629→                  >
   630→                    Change
   631→                  </button>
   632→

   633→

   634→

   635→                    <Clock className="h-4 w-4 text-muted-foreground" />
   636→
   637→                      {EXPERIENCE_OPTIONS.find((o) => o.value === experience)?.label}
   638→
   639→

   640→                  <button
   641→                    onClick={() => setStep("experience")}
   642→                    className="text-xs text-primary hover:underline"
   643→                  >
   644→                    Change
   645→                  </button>
   646→

   647→

   648→

   649→                    <MapPin className="h-4 w-4 text-muted-foreground" />
   650→                    {location}
   651→

   652→                  <button
   653→                    onClick={() => setStep("location")}
   654→                    className="text-xs text-primary hover:underline"
   655→                  >
   656→                    Change
   657→                  </button>
   658→

   659→              </CardContent>
   660→            </Card>
   661→
   662→            {/* Bio */}
   663→

   664→

   665→
   666→                  <Sparkles className="h-4 w-4 text-primary" />
   667→                  Your Bio
   668→
   669→                AI-generated
   670→

   671→              <textarea
   672→                value={editedBio}
   673→                onChange={(e) => setEditedBio(e.target.value)}
   674→                className="min-h-[120px] w-full rounded-xl border-2 border-border bg-background px-4 py-3 text-base focus:border-primary focus:outline-none"
   675→                placeholder="Your professional bio..."
   676→              />
   677→

   678→
   679→            {/* Skills */}
   680→

   681→

   682→
   683→                  <Zap className="h-4 w-4 text-primary" />
   684→                  Skills
   685→
   686→                Tap to remove
   687→

   688→

   689→                {editedSkills.map((skill, idx) => (
   690→                  <button
   691→                    key={idx}
   692→                    onClick={() => setEditedSkills(editedSkills.filter((_, i) => i !== idx))}
   693→                    className="rounded-full border-2 border-primary bg-primary/10 px-4 py-2 text-sm font-medium text-primary transition-colors hover:bg-primary/20 active:scale-95"
   694→                  >
   695→                    {skill} ×
   696→                  </button>
   697→                ))}
   698→

   699→

   700→
   701→            {/* Certifications */}
   702→

   703→

   704→
   705→                  <Shield className="h-4 w-4 text-primary" />
   706→                  Certifications
   707→
   708→                Tap to remove
   709→

   710→

   711→                {editedCerts.map((cert, idx) => (
   712→                  <button
   713→                    key={idx}
   714→                    onClick={() => setEditedCerts(editedCerts.filter((_, i) => i !== idx))}
   715→                    className="flex w-full items-center justify-between rounded-xl border-2 border-border bg-background px-4 py-3 text-left text-sm font-medium transition-colors hover:border-destructive hover:bg-destructive/5 active:scale-95"
   716→                  >
   717→                    {cert}
   718→                    Remove
   719→                  </button>
   720→                ))}
   721→

   722→

   723→

   724→        )}
   725→
   726→
   727→      {/* Bottom sticky CTA - only show on review step */}
   728→      {step === "review" && (
   729→

   730→

   731→            <Button
   732→              onClick={handleSave}
   733→              className="h-16 w-full gap-2 text-lg font-semibold"
   734→              disabled={saving}
   735→            >
   736→              {saving ? (
   737→                <>
   738→                  <Loader2 className="h-6 w-6 animate-spin" />
   739→                  Saving profile...
   740→                </>
   741→              ) : (
   742→                <>
   743→                  <CheckCircle2 className="h-6 w-6" />
   744→                  Save My Profile
   745→                </>
   746→              )}
   747→            </Button>
   748→

   749→

   750→      )}
   751→

   752→  );
   753→}
   754→
   755→function StepHeader({
   756→  icon,
   757→  title,
   758→  subtitle,
   759→}: {
   760→  icon: React.ReactNode;
   761→  title: string;
   762→  subtitle: string;
   763→}) {
   764→  return (
   765→

   766→

   767→        {icon}
   768→

   769→

   770→
{title}

   771→
{subtitle}

   772→

   773→

   774→  );
   775→}
   776→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>