Audit all page components and layouts in /home/ccuser/workdir/the-50-dollar-app-audit for UI/UX bugs.

Read ALL of these files completely:
- src/app/layout.tsx
- src/app/(authenticated)/layout.tsx
- src/app/page.tsx
- src/app/(authenticated)/dashboard/page.tsx
- src/app/(authenticated)/contractor/matches/page.tsx
- src/app/(authenticated)/contractor/post-job/page.tsx
- src/app/(authenticated)/worker/signup/page.tsx
- src/app/(authenticated)/worker/profile/build/page.tsx
- src/app/(authenticated)/worker/jobs/page.tsx
- src/app/(authenticated)/messages/page.tsx
- src/app/(authenticated)/messages/[conversationId]/page.tsx
- src/app/auth/login/page.tsx
- src/app/auth/signup/page.tsx
- src/app/auth/verify-email/page.tsx
- src/components/bottom-nav.tsx
- src/components/bottom-nav-wrapper.tsx
- src/components/VoiceRecorder.tsx
- src/components/ConversationPreview.tsx
- src/components/MessageBubble.tsx
- src/components/payment/HirePaymentModal.tsx
- src/components/payment/PaymentForm.tsx
- src/components/payment/StripeProvider.tsx
- src/components/ErrorDisplay.tsx
- src/components/error-display.tsx
- src/components/LoadingState.tsx
- src/components/loading-error.tsx

Check for:
1. **Duplicate headers/navs**: The authenticated layout provides a header and bottom nav. Do any pages also render their own header or nav? That would create duplicates.
2. **Missing "use client"**: Pages that use hooks (useState, useEffect, useRouter) but don't have "use client" directive.
3. **Sticky positioning**: Are sticky elements positioned correctly considering the layout header?
4. **Duplicate component files**: Are there duplicate components (e.g., ErrorDisplay.tsx AND error-display.tsx)? If so, which one is actually used?
5. **Loading states**: Do async operations (fetch, form submit) show loading indicators?
6. **Mobile responsive**: Do components use responsive Tailwind classes? Any hard-coded widths that would break on mobile?
7. **Touch targets**: Are buttons/links at least min-h-[44px] for mobile touch targets?

For each issue found: file path, line number, the problem, and suggested fix.


I'll conduct a thorough audit of all the specified files. Let me start by reading them all.


TodoWrite

Read all layout and page files

Read all component files

Analyze for duplicate headers/navs

Analyze for missing 'use client' directives

Analyzing for missing 'use client' directives

Analyze for sticky positioning issues

Analyze for duplicate component files

Analyze for loading state issues

Analyze for mobile responsive issues

Analyze for touch target issues

Compile final audit report


Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable


     1→import type { Metadata, Viewport } from "next";
     2→import { Geist, Geist_Mono } from "next/font/google";
     3→import { Toaster } from "react-hot-toast";
     4→import "./globals.css";
     5→
     6→const geistSans = Geist({
     7→  variable: "--font-geist-sans",
     8→  subsets: ["latin"],
     9→});
    10→
    11→const geistMono = Geist_Mono({
    12→  variable: "--font-geist-mono",
    13→  subsets: ["latin"],
    14→});
    15→
    16→export const metadata: Metadata = {
    17→  title: "RateRight — Hire Construction Workers Fast",
    18→  description:
    19→    "The simplest way to hire construction workers in Australia. AI does the work, you just tap. $50 flat fee per hire.",
    20→  keywords: [
    21→    "construction hiring",
    22→    "hire labourers",
    23→    "construction workers Australia",
    24→    "hire tradies",
    25→    "construction jobs Sydney",
    26→  ],
    27→  manifest: "/manifest.json",
    28→  appleWebApp: {
    29→    capable: true,
    30→    statusBarStyle: "default",
    31→    title: "RateRight",
    32→  },
    33→  other: {
    34→    "mobile-web-app-capable": "yes",
    35→    "apple-mobile-web-app-capable": "yes",
    36→    "application-name": "RateRight",
    37→    "msapplication-starturl": "/",
    38→    "msapplication-TileColor": "#2563eb",
    39→    "theme-color": "#2563eb",
    40→  },
    41→};
    42→
    43→export const viewport: Viewport = {
    44→  width: "device-width",
    45→  initialScale: 1,
    46→  maximumScale: 1,
    47→  themeColor: "#2563eb",
    48→};
    49→
    50→export default function RootLayout({
    51→  children,
    52→}: Readonly<{
    53→  children: React.ReactNode;
    54→}>) {
    55→  return (
    56→    <html lang="en-AU">
    57→      <head>
    58→        <link rel="manifest" href="/manifest.json" />
    59→        <link rel="apple-touch-icon" href="/apple-touch-icon.svg" />
    60→        <link rel="icon" type="image/svg+xml" href="/icon-192x192.svg" />
    61→        <meta name="apple-mobile-web-app-status-bar-style" content="default" />
    62→        <meta name="apple-mobile-web-app-title" content="RateRight" />
    63→        <meta name="mobile-web-app-capable" content="yes" />
    64→        <meta name="apple-mobile-web-app-capable" content="yes" />
    65→        <meta name="application-name" content="RateRight" />
    66→        <meta name="msapplication-starturl" content="/" />
    67→        <meta name="msapplication-TileColor" content="#2563eb" />
    68→        <meta name="theme-color" content="#2563eb" />
    69→      </head>
    70→      <body
    71→        className={`${geistSans.variable} ${geistMono.variable} font-sans antialiased`}
    72→      >
    73→        {children}
    74→        <Toaster position="top-right" />
    75→        <script
    76→          dangerouslySetInnerHTML={{
    77→            __html: `
    78→              if ('serviceWorker' in navigator) {
    79→                window.addEventListener('load', () => {
    80→                  navigator.serviceWorker.register('/sw.js').then(
    81→                    (registration) => {
    82→                      console.log('SW registered:', registration);
    83→                    },
    84→                    (err) => {
    85→                      console.log('SW registration failed:', err);
    86→                    }
    87→                  );
    88→                });
    89→              }
    90→            `,
    91→          }}
    92→        />
    93→      </body>
    94→    </html>
    95→  );
    96→}
    97→

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

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

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

    31→          <Link href="/" className="flex items-center gap-2">
    32→

    33→              <HardHat className="h-5 w-5 text-primary-foreground" />
    34→

    35→            RateRight
    36→          </Link>
    37→          <Link href="/auth/login">
    38→            <Button variant="ghost" size="sm">
    39→              Sign in
    40→            </Button>
    41→          </Link>
    42→

    43→
    44→
    45→      {/* Hero */}
    46→

    47→

    48→

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

    52→
    53→

    54→            Hire construction workers{" "}
    55→            in seconds
    56→

    57→
    58→

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

    62→
    63→          {/* CTA Buttons - BIG touch targets */}
    64→

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

    83→
    84→

    85→            Free to sign up. Workers always free.
    86→

    87→

    88→

    89→
    90→      {/* How it works */}
    91→

    92→

    93→

    94→            How it works
    95→

    96→

    97→            Three steps. Under two minutes.
    98→

    99→
   100→

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

   120→

   121→

   122→
   123→      {/* For Contractors */}
   124→

   125→

   126→

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

   129→

   130→

   131→            Stop wasting time hiring
   132→

   133→
   134→

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

   156→

   157→

   158→
   159→      {/* For Workers */}
   160→

   161→

   162→

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

   165→

   166→

   167→            Get on a job fast
   168→

   169→

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

   172→
   173→

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

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

   199→

   200→
   201→      {/* Pricing */}
   202→

   203→

   204→
Simple pricing

   205→

   206→            No hidden fees. No lock-in. No percentages.
   207→

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

   212→

   213→                per hire — that's it
   214→

   215→

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

   222→            </CardContent>
   223→          </Card>
   224→

   225→

   226→
   227→      {/* Final CTA */}
   228→

   229→

   230→
Ready to hire smarter?

   231→

   232→            Join Sydney contractors already using RateRight.
   233→

   234→

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

   254→

   255→

   256→
   257→      {/* Footer */}
   258→
   259→

   260→

   261→

   262→              <HardHat className="h-4 w-4 text-primary-foreground" />
   263→

   264→            RateRight
   265→

   266→

   267→            The simplest way to hire construction workers in Australia.
   268→

   269→

   270→            © {new Date().getFullYear()} RateRight Pty Ltd. All rights
   271→            reserved.
   272→

   273→

   274→
   275→

   276→  );
   277→}
   278→
   279→function Step({
   280→  number,
   281→  icon,
   282→  title,
   283→  description,
   284→}: {
   285→  number: string;
   286→  icon: React.ReactNode;
   287→  title: string;
   288→  description: string;
   289→}) {
   290→  return (
   291→

   292→

   293→        {number}
   294→

   295→

   296→

   297→          {icon}
   298→
{title}

   299→

   300→

   301→          {description}
   302→

   303→

   304→

   305→  );
   306→}
   307→
   308→function FeatureCard({
   309→  icon,
   310→  title,
   311→  description,
   312→}: {
   313→  icon: React.ReactNode;
   314→  title: string;
   315→  description: string;
   316→}) {
   317→  return (
   318→

   319→
{icon}

   320→

   321→
{title}

   322→
{description}

   323→

   324→

   325→  );
   326→}
   327→
   328→function Benefit({
   329→  icon,
   330→  text,
   331→}: {
   332→  icon: React.ReactNode;
   333→  text: string;
   334→}) {
   335→  return (
   336→

   337→
{icon}

   338→      {text}
   339→

   340→  );
   341→}
   342→
   343→function BenefitCheck({ text }: { text: string }) {
   344→  return (
   345→

   346→      <CheckCircle2 className="h-4 w-4 shrink-0 text-primary" />
   347→      {text}
   348→

   349→  );
   350→}
   351→

<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, Briefcase, 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 variant="outline" className="w-full h-14 text-base">
   140→                    Edit Profile
   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→                    <Briefcase 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 variant="outline" className="w-full h-14 text-base">
   178→                    Edit Profile
   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>


1→"use client";
     2→
     3→import { useEffect, useState, useRef } from "react";
     4→import Link from "next/link";
     5→import { createClient } from "@/lib/supabase/client";
     6→import { Card, CardContent } from "@/components/ui/card";
     7→import { Badge } from "@/components/ui/badge";
     8→import { Button } from "@/components/ui/button";
     9→import { BottomNav } from "@/components/bottom-nav";
    10→import { HirePaymentModal } from "@/components/payment/HirePaymentModal";
    11→import {
    12→  ArrowLeft,
    13→  Loader2,
    14→  Star,
    15→  MapPin,
    16→  Clock,
    17→  CheckCircle2,
    18→  UserCheck,
    19→  Briefcase,
    20→  AlertCircle,
    21→} from "lucide-react";
    22→import type { Job } from "@/lib/types";
    23→import { postgresPointToJson } from "@/lib/location-utils";
    24→
    25→interface MatchWithWorker {
    26→  id: string;
    27→  job_id: string;
    28→  worker_id: string;
    29→  status: string;
    30→  match_score: number | null;
    31→  ai_summary: string | null;
    32→  contractor_action: string;
    33→  worker_action: string;
    34→  hired_at: string | null;
    35→  fee_charged: boolean;
    36→  created_at: string;
    37→  worker: {
    38→    id: string;
    39→    name: string | null;
    40→    suburb: string | null;
    41→    avatar_url: string | null;
    42→  } | null;
    43→  worker_profile: {
    44→    trades: string[];
    45→    experience_years: number | null;
    46→    certifications: string[];
    47→    availability: string | null;
    48→  } | null;
    49→}
    50→
    51→interface SwipeableMatchCardProps {
    52→  match: MatchWithWorker;
    53→  isHired: boolean;
    54→  justHired: boolean;
    55→  hiring: boolean;
    56→  onHire: (match: MatchWithWorker) => void;
    57→  onReject: (match: MatchWithWorker) => void;
    58→}
    59→
    60→function SwipeableMatchCard({ match, isHired, justHired, hiring, onHire, onReject }: SwipeableMatchCardProps) {
    61→  const [swipeX, setSwipeX] = useState(0);
    62→  const [swiping, setSwiping] = useState(false);
    63→  const startX = useRef(0);
    64→  const currentX = useRef(0);
    65→
    66→  const summary = match.ai_summary || generateAiSummary(match.worker, match.worker_profile);
    67→
    68→  const handleTouchStart = (e: React.TouchEvent) => {
    69→    if (isHired) return;
    70→    startX.current = e.touches[0].clientX;
    71→    setSwiping(true);
    72→  };
    73→
    74→  const handleTouchMove = (e: React.TouchEvent) => {
    75→    if (!swiping || isHired) return;
    76→    currentX.current = e.touches[0].clientX;
    77→    const diff = currentX.current - startX.current;
    78→    // Allow both directions
    79→    setSwipeX(Math.max(-150, Math.min(diff, 150)));
    80→  };
    81→
    82→  const handleTouchEnd = () => {
    83→    if (!swiping || isHired) return;
    84→    setSwiping(false);
    85→
    86→    // If swiped right > 100px, hire
    87→    if (swipeX > 100) {
    88→      onHire(match);
    89→    }
    90→    // If swiped left > 100px, reject
    91→    else if (swipeX < -100) {
    92→      onReject(match);
    93→    }
    94→
    95→    // Reset
    96→    setSwipeX(0);
    97→  };
    98→
    99→  const swipeProgress = Math.abs(swipeX) / 100;
   100→  const swipeDirection = swipeX > 0 ? "right" : "left";
   101→
   102→  return (
   103→

   104→      {/* Swipe indicator backgrounds */}
   105→      {!isHired && swipeX !== 0 && (
   106→        <>
   107→          {/* Right swipe (hire) */}
   108→          {swipeX > 0 && (
   109→

   116→              <UserCheck className="h-8 w-8 text-green-600" />
   117→
   118→                {swipeProgress >= 1 ? "Release to hire" : "Swipe to hire"}
   119→
   120→

   121→          )}
   122→          {/* Left swipe (reject) */}
   123→          {swipeX < 0 && (
   124→

   131→
   132→                {swipeProgress >= 1 ? "Release to pass" : "Swipe to pass"}
   133→
   134→              <AlertCircle className="h-8 w-8 text-red-600" />
   135→

   136→          )}
   137→        </>
   138→      )}
   139→
   140→      <Card
   141→        className={`transition-all touch-pan-y ${justHired ? "ring-2 ring-green-500" : ""}`}
   142→        style={{
   143→          transform: `translateX(${swipeX}px)`,
   144→          transition: swiping ? 'none' : 'transform 0.2s ease-out',
   145→        }}
   146→        onTouchStart={handleTouchStart}
   147→        onTouchMove={handleTouchMove}
   148→        onTouchEnd={handleTouchEnd}
   149→      >
   150→        <CardContent className="p-5">
   151→

   152→            {/* Avatar */}
   153→

   154→              {match.worker?.name?.[0]?.toUpperCase() || "?"}
   155→

   156→
   157→

   158→

   159→

   160→                  {match.worker?.name || "Worker"}
   161→

   162→                {isHired && (
   163→                  <Badge className="bg-green-600 text-xs shrink-0">Hired</Badge>
   164→                )}
   165→

   166→
   167→              {/* AI Summary */}
   168→

   169→                {summary}
   170→

   171→
   172→              {/* Details */}
   173→

   174→                {match.worker_profile?.experience_years && (
   175→
   176→                    <Clock className="h-4 w-4" />
   177→                    {match.worker_profile.experience_years}yr exp
   178→
   179→                )}
   180→                {match.worker?.suburb && (
   181→
   182→                    <MapPin className="h-4 w-4" />
   183→                    {match.worker.suburb}
   184→
   185→                )}
   186→                {match.match_score && (
   187→
   188→                    <Star className="h-4 w-4 fill-amber-400 text-amber-400" />
   189→                    {Math.round(match.match_score)}% match
   190→
   191→                )}
   192→

   193→
   194→              {/* Certs */}
   195→              {match.worker_profile?.certifications && match.worker_profile.certifications.length > 0 && (
   196→

   197→                  {match.worker_profile.certifications.slice(0, 3).map((cert) => (
   198→                    <Badge key={cert} variant="outline" className="text-xs px-2 py-1">
   199→                      {cert}
   200→                    </Badge>
   201→                  ))}
   202→

   203→              )}
   204→

   205→

   206→
   207→          {/* Action */}
   208→          {!isHired && (
   209→            <Button
   210→              onClick={() => onHire(match)}
   211→              className="mt-4 w-full h-14 text-base font-semibold"
   212→              disabled={hiring}
   213→            >
   214→              {hiring ? (
   215→                <>
   216→                  <Loader2 className="mr-2 h-5 w-5 animate-spin" />
   217→                  Processing...
   218→                </>
   219→              ) : (
   220→                <>
   221→                  <UserCheck className="mr-2 h-5 w-5" />
   222→                  Hire — $50 Fee
   223→                </>
   224→              )}
   225→            </Button>
   226→          )}
   227→
   228→          {justHired && (
   229→

   230→              <CheckCircle2 className="h-5 w-5" />
   231→              Hired! Worker has been notified.
   232→

   233→          )}
   234→
   235→          {isHired && !justHired && (
   236→

   237→              <CheckCircle2 className="h-5 w-5 text-green-600" />
   238→              Hired on{" "}
   239→              {match.hired_at
   240→                ? new Date(match.hired_at).toLocaleDateString("en-AU", {
   241→                    day: "numeric",
   242→                    month: "short",
   243→                  })
   244→                : "recently"}
   245→

   246→          )}
   247→        </CardContent>
   248→      </Card>
   249→

   250→  );
   251→}
   252→
   253→const TRADE_LABEL: Record<string, string> = {
   254→  "steel-fixer": "Steel Fixer",
   255→  "formworker": "Formworker",
   256→  "labourer": "Labourer",
   257→  "concreter": "Concreter",
   258→  "carpenter": "Carpenter",
   259→  "scaffolder": "Scaffolder",
   260→  "other": "Other",
   261→};
   262→
   263→function generateAiSummary(
   264→  worker: MatchWithWorker["worker"],
   265→  profile: MatchWithWorker["worker_profile"]
   266→): string {
   267→  const parts: string[] = [];
   268→  if (profile?.trades?.length) {
   269→    parts.push(profile.trades.map((t) => TRADE_LABEL[t] || t).join(", "));
   270→  }
   271→  if (profile?.experience_years) {
   272→    parts.push(`${profile.experience_years} years exp`);
   273→  }
   274→  if (worker?.suburb) {
   275→    parts.push(worker.suburb);
   276→  }
   277→  if (profile?.availability) {
   278→    parts.push(profile.availability);
   279→  }
   280→  if (profile?.certifications?.length) {
   281→    parts.push(profile.certifications.slice(0, 2).join(", "));
   282→  }
   283→  return parts.join(" \u00B7 ") || "Worker profile";
   284→}
   285→
   286→export default function ContractorMatchesPage() {
   287→  const supabase = createClient();
   288→  const [jobs, setJobs] = useState<Job[]>([]);
   289→  const [selectedJobId, setSelectedJobId] = useState<string | null>(null);
   290→  const [matches, setMatches] = useState<MatchWithWorker[]>([]);
   291→  const [loading, setLoading] = useState(true);
   292→  const [loadingMatches, setLoadingMatches] = useState(false);
   293→  const [hiring, setHiring] = useState<string | null>(null);
   294→  const [hireSuccess, setHireSuccess] = useState<string | null>(null);
   295→  const [error, setError] = useState("");
   296→
   297→  // Payment modal state
   298→  const [companyId, setCompanyId] = useState<string | null>(null);
   299→  const [selectedMatch, setSelectedMatch] = useState<MatchWithWorker | null>(null);
   300→  const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
   301→
   302→  // Load contractor's jobs
   303→  useEffect(() => {
   304→    async function load() {
   305→      const { data: { user } } = await supabase.auth.getUser();
   306→      if (!user) return;
   307→
   308→      const { data: company } = await supabase
   309→        .from("companies")
   310→        .select("id")
   311→        .eq("profile_id", user.id)
   312→        .single();
   313→
   314→      if (!company) {
   315→        setLoading(false);
   316→        return;
   317→      }
   318→
   319→      setCompanyId(company.id);
   320→
   321→      const { data: jobsData } = await supabase
   322→        .from("jobs")
   323→        .select("*")
   324→        .eq("company_id", company.id)
   325→        .in("status", ["active", "filled"])
   326→        .order("created_at", { ascending: false });
   327→
   328→      if (jobsData && jobsData.length > 0) {
   329→        // Convert PostgreSQL point to JSON format
   330→        const convertedJobs = jobsData.map((job: any) => ({
   331→          ...job,
   332→          location: postgresPointToJson(job.location)
   333→        }));
   334→        setJobs(convertedJobs);
   335→        setSelectedJobId(jobsData[0].id);
   336→      }
   337→      setLoading(false);
   338→    }
   339→    load();
   340→  }, [supabase]);
   341→
   342→  // Load matches for selected job
   343→  useEffect(() => {
   344→    if (!selectedJobId) return;
   345→    async function loadMatches() {
   346→      setLoadingMatches(true);
   347→
   348→      // Get matches for this job
   349→      const { data: matchData } = await supabase
   350→        .from("matches")
   351→        .select("*")
   352→        .eq("job_id", selectedJobId)
   353→        .order("match_score", { ascending: false });
   354→
   355→      if (!matchData || matchData.length === 0) {
   356→        setMatches([]);
   357→        setLoadingMatches(false);
   358→        return;
   359→      }
   360→
   361→      // Fetch worker profiles and base profiles
   362→      const workerIds = matchData.map((m: { worker_id: string }) => m.worker_id);
   363→
   364→      const [{ data: profiles }, { data: workerProfiles }] = await Promise.all([
   365→        supabase.from("profiles").select("id, name, suburb, avatar_url").in("id", workerIds),
   366→        supabase.from("worker_profiles").select("profile_id, trades, experience_years, certifications, availability").in("profile_id", workerIds),
   367→      ]);
   368→
   369→      type ProfileRow = { id: string; name: string | null; suburb: string | null; avatar_url: string | null };
   370→      type WPRow = { profile_id: string; trades: string[]; experience_years: number | null; certifications: string[]; availability: string | null };
   371→
   372→      const profileMap = new Map((profiles || []).map((p: ProfileRow) => [p.id, p]));
   373→      const wpMap = new Map((workerProfiles || []).map((w: WPRow) => [w.profile_id, w]));
   374→
   375→      const enriched: MatchWithWorker[] = matchData.map((m: Record<string, unknown>) => ({
   376→        id: m.id as string,
   377→        job_id: m.job_id as string,
   378→        worker_id: m.worker_id as string,
   379→        status: m.status as string,
   380→        match_score: m.match_score as number | null,
   381→        ai_summary: m.ai_summary as string | null,
   382→        contractor_action: m.contractor_action as string,
   383→        worker_action: m.worker_action as string,
   384→        hired_at: m.hired_at as string | null,
   385→        fee_charged: m.fee_charged as boolean,
   386→        created_at: m.created_at as string,
   387→        worker: profileMap.get(m.worker_id as string) || null,
   388→        worker_profile: wpMap.get(m.worker_id as string) || null,
   389→      }));
   390→
   391→      setMatches(enriched);
   392→      setLoadingMatches(false);
   393→    }
   394→    loadMatches();
   395→  }, [selectedJobId, supabase]);
   396→
   397→  async function handleHire(match: MatchWithWorker) {
   398→    // Haptic feedback
   399→    if (navigator.vibrate) {
   400→      navigator.vibrate(50);
   401→    }
   402→
   403→    if (!companyId) {
   404→      setError("Company not found. Please complete your profile.");
   405→      return;
   406→    }
   407→
   408→    // Open payment modal
   409→    setSelectedMatch(match);
   410→    setIsPaymentModalOpen(true);
   411→  }
   412→
   413→  async function handlePaymentSuccess() {
   414→    if (!selectedMatch) return;
   415→
   416→    // Update local state to show hired
   417→    setMatches((prev) =>
   418→      prev.map((m) =>
   419→        m.id === selectedMatch.id
   420→          ? { ...m, status: "hired", contractor_action: "accepted", hired_at: new Date().toISOString(), fee_charged: true }
   421→          : m
   422→      )
   423→    );
   424→
   425→    setHireSuccess(selectedMatch.id);
   426→    setIsPaymentModalOpen(false);
   427→    setSelectedMatch(null);
   428→    setTimeout(() => setHireSuccess(null), 3000);
   429→  }
   430→
   431→  async function handleReject(match: MatchWithWorker) {
   432→    // Haptic feedback
   433→    if (navigator.vibrate) {
   434→      navigator.vibrate([30, 20, 30]);
   435→    }
   436→
   437→    // Remove from list (soft delete - just update status)
   438→    await supabase
   439→      .from("matches")
   440→      .update({
   441→        contractor_action: "rejected",
   442→      })
   443→      .eq("id", match.id);
   444→
   445→    // Remove from UI
   446→    setMatches((prev) => prev.filter((m) => m.id !== match.id));
   447→  }
   448→
   449→  if (loading) {
   450→    return (
   451→

   452→        <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
   453→

   454→    );
   455→  }
   456→
   457→  return (
   458→

   459→
   460→        {jobs.length === 0 ? (
   461→

   462→            <Briefcase className="h-16 w-16 text-muted-foreground/50" />
   463→
No jobs posted yet

   464→

   465→              Post a job to start seeing matched workers.
   466→

   467→            <Button asChild className="mt-8 h-16 px-8 text-lg font-semibold">
   468→              <Link href="/contractor/post-job">Post a Job</Link>
   469→            </Button>
   470→

   471→        ) : (
   472→          <>
   473→            {/* Job selector — horizontal scroll */}
   474→            {jobs.length > 1 && (
   475→

   476→                {jobs.map((job) => (
   477→                  <button
   478→                    key={job.id}
   479→                    onClick={() => setSelectedJobId(job.id)}
   480→                    className={`shrink-0 rounded-full px-5 py-3 text-base font-semibold transition-all active:scale-95 min-h-[48px] ${
   481→                      selectedJobId === job.id
   482→                        ? "bg-primary text-primary-foreground"
   483→                        : "bg-muted text-muted-foreground"
   484→                    }`}
   485→                  >
   486→                    {job.title}
   487→                  </button>
   488→                ))}
   489→

   490→            )}
   491→
   492→            {/* Selected job summary */}
   493→            {selectedJobId && (
   494→

   495→                {(() => {
   496→                  const job = jobs.find((j) => j.id === selectedJobId);
   497→                  if (!job) return null;
   498→                  return (
   499→

   500→                      <Badge variant="secondary">{TRADE_LABEL[job.trade] || job.trade}</Badge>
   501→                      {job.suburb && (
   502→
   503→                          <MapPin className="h-3.5 w-3.5" />
   504→                          {job.suburb}
   505→
   506→                      )}
   507→
   508→                        {matches.length} match{matches.length !== 1 ? "es" : ""}
   509→
   510→

   511→                  );
   512→                })()}
   513→

   514→            )}
   515→
   516→            {/* Matches */}
   517→            {loadingMatches ? (
   518→

   519→                <Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
   520→

   521→            ) : matches.length === 0 ? (
   522→

   523→                <UserCheck className="h-12 w-12 text-muted-foreground/50" />
   524→
No matches yet

   525→

   526→                  Workers will show up here as they apply. We'll notify you.
   527→

   528→

   529→            ) : (
   530→

   531→                {error && (
   532→

   533→                    <AlertCircle className="h-4 w-4 shrink-0" />
   534→                    {error}
   535→

   536→                )}
   537→
   538→                {/* Swipe hint */}
   539→                {matches.length > 0 && matches.some(m => m.status !== "hired") && (
   540→

   541→

   542→                      👉 Swipe right to hire • Swipe left to pass
   543→

   544→

   545→                )}
   546→
   547→                {matches.map((match) => (
   548→                  <SwipeableMatchCard
   549→                    key={match.id}
   550→                    match={match}
   551→                    isHired={match.status === "hired"}
   552→                    justHired={hireSuccess === match.id}
   553→                    hiring={hiring === match.id}
   554→                    onHire={handleHire}
   555→                    onReject={handleReject}
   556→                  />
   557→                ))}
   558→

   559→            )}
   560→          </>
   561→        )}
   562→
   563→
   564→      <BottomNav userType="contractor" />
   565→
   566→      {/* Payment Modal */}
   567→      {selectedMatch && companyId && (
   568→        <HirePaymentModal
   569→          isOpen={isPaymentModalOpen}
   570→          onClose={() => {
   571→            setIsPaymentModalOpen(false);
   572→            setSelectedMatch(null);
   573→          }}
   574→          onSuccess={handlePaymentSuccess}
   575→          matchId={selectedMatch.id}
   576→          companyId={companyId}
   577→          amount={50.0}
   578→          workerName={selectedMatch.worker?.name || undefined}
   579→        />
   580→      )}
   581→

   582→  );
   583→}
   584→

<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 { Card, CardContent } from "@/components/ui/card";
     9→import { Input } from "@/components/ui/input";
    10→import { Label } from "@/components/ui/label";
    11→import { Badge } from "@/components/ui/badge";
    12→import {
    13→  ArrowLeft,
    14→  ArrowRight,
    15→  Loader2,
    16→  Sparkles,
    17→  Check,
    18→  MapPin,
    19→  Calendar,
    20→  Users,
    21→  DollarSign,
    22→  HardHat,
    23→  Wrench,
    24→  Hammer,
    25→  Blocks,
    26→  Fence,
    27→  CircleDot,
    28→  Shovel,
    29→} from "lucide-react";
    30→import { jsonToPostgresPoint } from "@/lib/location-utils";
    31→
    32→const TRADE_CATEGORIES = [
    33→  { id: "steel-fixer", label: "Steel Fixer", icon: Wrench, color: "bg-blue-500/10 border-blue-500/30 text-blue-700" },
    34→  { id: "formworker", label: "Formworker", icon: Blocks, color: "bg-amber-500/10 border-amber-500/30 text-amber-700" },
    35→  { id: "labourer", label: "Labourer", icon: Shovel, color: "bg-green-500/10 border-green-500/30 text-green-700" },
    36→  { id: "concreter", label: "Concreter", icon: CircleDot, color: "bg-gray-500/10 border-gray-500/30 text-gray-700" },
    37→  { id: "carpenter", label: "Carpenter", icon: Hammer, color: "bg-orange-500/10 border-orange-500/30 text-orange-700" },
    38→  { id: "scaffolder", label: "Scaffolder", icon: Fence, color: "bg-purple-500/10 border-purple-500/30 text-purple-700" },
    39→  { id: "other", label: "Other", icon: HardHat, color: "bg-muted border-border text-foreground" },
    40→];
    41→
    42→const AI_SUGGESTIONS: Record<string, { title: string; description: string; certs: string[]; rateMin: number; rateMax: number; rateType: "hourly" | "daily" }> = {
    43→  "steel-fixer": {
    44→    title: "Steel Fixer Needed",
    45→    description: "Experienced steel fixer required for commercial construction project. Must be able to read drawings, tie rebar, and work with mesh. PPE required on site at all times. Early starts — 6:30am.",
    46→    certs: ["White Card", "Working at Heights"],
    47→    rateMin: 45,
    48→    rateMax: 65,
    49→    rateType: "hourly",
    50→  },
    51→  "formworker": {
    52→    title: "Formworker Required",
    53→    description: "Skilled formworker needed for concrete pours on a residential build. Experience with timber and steel formwork systems. Must be comfortable with early starts and physical work.",
    54→    certs: ["White Card"],
    55→    rateMin: 42,
    56→    rateMax: 58,
    57→    rateType: "hourly",
    58→  },
    59→  "labourer": {
    60→    title: "General Labourer Needed",
    61→    description: "Reliable labourer needed for site clean-up, material handling, and general duties. No specific trade experience needed but must be fit and punctual. PPE and boots required.",
    62→    certs: ["White Card"],
    63→    rateMin: 32,
    64→    rateMax: 42,
    65→    rateType: "hourly",
    66→  },
    67→  "concreter": {
    68→    title: "Concreter Required",
    69→    description: "Experienced concreter needed for slab pours and finishing work. Must know how to screed, float, and finish concrete to a high standard. Early morning pours expected.",
    70→    certs: ["White Card"],
    71→    rateMin: 45,
    72→    rateMax: 65,
    73→    rateType: "hourly",
    74→  },
    75→  "carpenter": {
    76→    title: "Carpenter Needed",
    77→    description: "Qualified carpenter required for framing and fit-out work. Must have own basic tools. Experience with residential or commercial builds preferred.",
    78→    certs: ["White Card", "Trade Certificate"],
    79→    rateMin: 45,
    80→    rateMax: 65,
    81→    rateType: "hourly",
    82→  },
    83→  "scaffolder": {
    84→    title: "Scaffolder Required",
    85→    description: "Licensed scaffolder needed for erection and dismantling of scaffolding systems. Must hold current scaffolding licence. Safety-first attitude essential.",
    86→    certs: ["White Card", "Scaffolding Licence", "Working at Heights"],
    87→    rateMin: 48,
    88→    rateMax: 68,
    89→    rateType: "hourly",
    90→  },
    91→  "other": {
    92→    title: "Construction Worker Needed",
    93→    description: "Worker needed for construction project. Please specify the role and requirements in the description.",
    94→    certs: ["White Card"],
    95→    rateMin: 35,
    96→    rateMax: 55,
    97→    rateType: "hourly",
    98→  },
    99→};
   100→
   101→export default function PostJobPage() {
   102→  const router = useRouter();
   103→  const supabase = createClient();
   104→
   105→  const [step, setStep] = useState(1);
   106→  const [loading, setLoading] = useState(false);
   107→  const [aiLoading, setAiLoading] = useState(false);
   108→  const [error, setError] = useState("");
   109→
   110→  // Step 1: Trade
   111→  const [selectedTrade, setSelectedTrade] = useState("");
   112→
   113→  // Step 2: AI-filled job details + location/date/workers (COMBINED)
   114→  const [title, setTitle] = useState("");
   115→  const [description, setDescription] = useState("");
   116→  const [certs, setCerts] = useState<string[]>([]);
   117→  const [rateMin, setRateMin] = useState("");
   118→  const [rateMax, setRateMax] = useState("");
   119→  const [rateType, setRateType] = useState<"hourly" | "daily">("hourly");
   120→  const [suburb, setSuburb] = useState("");
   121→  const [siteAddress, setSiteAddress] = useState("");
   122→  const [startDate, setStartDate] = useState("");
   123→  const [duration, setDuration] = useState("");
   124→  const [workersNeeded, setWorkersNeeded] = useState("1");
   125→
   126→  async function handleTradeSelect(tradeId: string) {
   127→    setSelectedTrade(tradeId);
   128→    setAiLoading(true);
   129→    setStep(2);
   130→
   131→    // Simulate AI generating job details
   132→    await new Promise((r) => setTimeout(r, 1500));
   133→
   134→    const suggestion = AI_SUGGESTIONS[tradeId];
   135→    if (suggestion) {
   136→      setTitle(suggestion.title);
   137→      setDescription(suggestion.description);
   138→      setCerts(suggestion.certs);
   139→      setRateMin(String(suggestion.rateMin));
   140→      setRateMax(String(suggestion.rateMax));
   141→      setRateType(suggestion.rateType);
   142→    }
   143→    setAiLoading(false);
   144→  }
   145→
   146→  async function handlePost() {
   147→    setLoading(true);
   148→    setError("");
   149→
   150→    try {
   151→      const { data: { user } } = await supabase.auth.getUser();
   152→      if (!user) {
   153→        router.push("/auth/login");
   154→        return;
   155→      }
   156→
   157→      // Get contractor's company
   158→      const { data: company } = await supabase
   159→        .from("companies")
   160→        .select("id")
   161→        .eq("profile_id", user.id)
   162→        .single();
   163→
   164→      if (!company) {
   165→        setError("Please complete your company profile first.");
   166→        setLoading(false);
   167→        return;
   168→      }
   169→
   170→      const { error: insertError } = await supabase.from("jobs").insert({
   171→        company_id: company.id,
   172→        title,
   173→        description,
   174→        trade: selectedTrade,
   175→        suburb,
   176→        site_address: siteAddress,
   177→        rate_min: parseFloat(rateMin) || null,
   178→        rate_max: parseFloat(rateMax) || null,
   179→        rate_type: rateType,
   180→        start_date: startDate || null,
   181→        duration: duration || null,
   182→        workers_needed: parseInt(workersNeeded) || 1,
   183→        certifications_required: certs,
   184→        status: "active",
   185→        ai_generated: true,
   186→      });
   187→
   188→      if (insertError) {
   189→        setError(insertError.message);
   190→        setLoading(false);
   191→        return;
   192→      }
   193→
   194→      setStep(3); // Success (was step 5, now step 3)
   195→    } catch {
   196→      setError("Something went wrong. Please try again.");
   197→      setLoading(false);
   198→    }
   199→  }
   200→
   201→  return (
   202→

   203→
   204→        {/* STEP 1: Select Trade */}
   205→        {step === 1 && (
   206→

   207→
What trade do you need?

   208→

   209→              Tap to select. AI will write the job post for you.
   210→

   211→

   212→              {TRADE_CATEGORIES.map((trade) => {
   213→                const Icon = trade.icon;
   214→                return (
   215→                  <button
   216→                    key={trade.id}
   217→                    onClick={() => handleTradeSelect(trade.id)}
   218→                    className={`flex flex-col items-center justify-center gap-3 rounded-2xl border-2 p-6 min-h-[120px] text-center transition-all active:scale-95 ${trade.color}`}
   219→                  >
   220→                    <Icon className="h-10 w-10" />
   221→                    {trade.label}
   222→                  </button>
   223→                );
   224→              })}
   225→

   226→

   227→        )}
   228→
   229→        {/* STEP 2: AI-filled Job Details + Location/Date/Workers (COMBINED) */}
   230→        {step === 2 && (
   231→

   232→            {aiLoading ? (
   233→

   234→

   235→                  <Sparkles className="h-12 w-12 text-primary animate-pulse" />
   236→

   237→
AI writing your job post...

   238→

   239→                  Based on market rates and common requirements
   240→

   241→

   242→            ) : (
   243→

   244→

   245→                  <Sparkles className="h-5 w-5 text-primary" />
   246→
Job Details

   247→

   248→

   249→                  AI pre-filled these. Edit anything you like.
   250→

   251→
   252→

   253→                  {/* Job Details Section */}
   254→

   255→                    Job Title
   256→                    <Input
   257→                      id="title"
   258→                      value={title}
   259→                      onChange={(e) => setTitle(e.target.value)}
   260→                      className="mt-2 h-14 text-base"
   261→                    />
   262→

   263→
   264→

   265→                    Description
   266→                    <textarea
   267→                      id="description"
   268→                      value={description}
   269→                      onChange={(e) => setDescription(e.target.value)}
   270→                      rows={5}
   271→                      className="mt-2 w-full rounded-md border border-input bg-background px-4 py-3 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
   272→                    />
   273→

   274→
   275→

   276→                    Pay Rate (AUD)
   277→

   278→

   279→                        <DollarSign className="absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-muted-foreground" />
   280→                        <Input
   281→                          type="number"
   282→                          value={rateMin}
   283→                          onChange={(e) => setRateMin(e.target.value)}
   284→                          placeholder="Min"
   285→                          className="h-14 pl-10 text-base"
   286→                        />
   287→

   288→                      to
   289→

   290→                        <DollarSign className="absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-muted-foreground" />
   291→                        <Input
   292→                          type="number"
   293→                          value={rateMax}
   294→                          onChange={(e) => setRateMax(e.target.value)}
   295→                          placeholder="Max"
   296→                          className="h-14 pl-10 text-base"
   297→                        />
   298→

   299→

   300→

   301→                      <button
   302→                        onClick={() => setRateType("hourly")}
   303→                        className={`rounded-xl border-2 py-3.5 text-base font-semibold transition-colors ${
   304→                          rateType === "hourly"
   305→                            ? "border-primary bg-primary/10 text-primary"
   306→                            : "border-border text-muted-foreground"
   307→                        }`}
   308→                      >
   309→                        Per Hour
   310→                      </button>
   311→                      <button
   312→                        onClick={() => setRateType("daily")}
   313→                        className={`rounded-xl border-2 py-3.5 text-base font-semibold transition-colors ${
   314→                          rateType === "daily"
   315→                            ? "border-primary bg-primary/10 text-primary"
   316→                            : "border-border text-muted-foreground"
   317→                        }`}
   318→                      >
   319→                        Per Day
   320→                      </button>
   321→

   322→

   323→
   324→                  {/* Location Section */}
   325→

   326→

   327→                      <MapPin className="h-5 w-5" />
   328→                      Location
   329→

   330→
   331→

   332→

   333→                        Site Address
   334→                        <Input
   335→                          id="siteAddress"
   336→                          value={siteAddress}
   337→                          onChange={(e) => setSiteAddress(e.target.value)}
   338→                          placeholder="e.g. 123 George St, Sydney"
   339→                          className="mt-2 h-14 text-base"
   340→                        />
   341→

   342→
   343→

   344→                        Suburb
   345→                        <Input
   346→                          id="suburb"
   347→                          value={suburb}
   348→                          onChange={(e) => setSuburb(e.target.value)}
   349→                          placeholder="e.g. Parramatta"
   350→                          className="mt-2 h-14 text-base"
   351→                        />
   352→

   353→

   354→

   355→
   356→                  {/* Date & Workers Section */}
   357→

   358→

   359→                      <Calendar className="h-5 w-5" />
   360→                      Schedule
   361→

   362→
   363→

   364→

   365→                        Start Date
   366→                        <Input
   367→                          id="startDate"
   368→                          type="date"
   369→                          value={startDate}
   370→                          onChange={(e) => setStartDate(e.target.value)}
   371→                          className="mt-2 h-14 text-base"
   372→                        />
   373→

   374→
   375→

   376→                        Duration
   377→                        <Input
   378→                          id="duration"
   379→                          value={duration}
   380→                          onChange={(e) => setDuration(e.target.value)}
   381→                          placeholder="e.g. 2 weeks, ongoing"
   382→                          className="mt-2 h-14 text-base"
   383→                        />
   384→

   385→
   386→

   387→
   388→                          <Users className="h-5 w-5" />
   389→                          Workers Needed
   390→
   391→

   392→                          <button
   393→                            onClick={() =>
   394→                              setWorkersNeeded(String(Math.max(1, parseInt(workersNeeded) - 1)))
   395→                            }
   396→                            className="flex h-16 w-16 items-center justify-center rounded-xl border-2 text-2xl font-bold active:scale-95 active:bg-muted transition-all"
   397→                          >
   398→                            -
   399→                          </button>
   400→
   401→                            {workersNeeded}
   402→
   403→                          <button
   404→                            onClick={() =>
   405→                              setWorkersNeeded(String(parseInt(workersNeeded) + 1))
   406→                            }
   407→                            className="flex h-16 w-16 items-center justify-center rounded-xl border-2 text-2xl font-bold active:scale-95 active:bg-muted transition-all"
   408→                          >
   409→                            +
   410→                          </button>
   411→

   412→

   413→

   414→

   415→
   416→                  {/* Required Certifications */}
   417→                  {certs.length > 0 && (
   418→

   419→                      Required Certifications
   420→

   421→                        {certs.map((cert) => (
   422→                          <Badge key={cert} variant="secondary" className="text-sm py-1.5 px-3">
   423→                            {cert}
   424→                            <button
   425→                              onClick={() => setCerts(certs.filter((c) => c !== cert))}
   426→                              className="ml-2 text-muted-foreground hover:text-foreground"
   427→                            >
   428→                              ×
   429→                            </button>
   430→                          </Badge>
   431→                        ))}
   432→

   433→

   434→                  )}
   435→

   436→

   437→            )}
   438→

   439→        )}
   440→
   441→        {/* STEP 3: Success */}
   442→        {step === 3 && (
   443→

   444→

   445→              <Check className="h-12 w-12 text-green-600" />
   446→

   447→
Job Posted!

   448→

   449→              We're matching you with the best workers now.
   450→              You'll get notified when we find matches.
   451→

   452→

   453→              <Button
   454→                onClick={() => router.push("/contractor/matches")}
   455→                className="w-full h-16 text-lg font-semibold"
   456→              >
   457→                View Matches
   458→              </Button>
   459→              <Button
   460→                onClick={() => router.push("/dashboard")}
   461→                variant="outline"
   462→                className="w-full h-16 text-lg"
   463→              >
   464→                Back to Dashboard
   465→              </Button>
   466→

   467→

   468→        )}
   469→
   470→
   471→      {/* Sticky Bottom CTA (Step 2 only) */}
   472→      {step === 2 && !aiLoading && (
   473→

   474→

   475→            {error && (
   476→
{error}

   477→            )}
   478→            <Button
   479→              onClick={handlePost}
   480→              className="w-full h-16 text-lg font-semibold"
   481→              disabled={loading || !title || !suburb}
   482→            >
   483→              {loading ? (
   484→                <>
   485→                  <Loader2 className="mr-2 h-6 w-6 animate-spin" />
   486→                  Posting...
   487→                </>
   488→              ) : (
   489→                <>
   490→                  Post Job Now
   491→                  <Check className="ml-2 h-6 w-6" />
   492→                </>
   493→              )}
   494→            </Button>
   495→

   496→              Free to post. You only pay $50 when you hire someone.
   497→

   498→

   499→

   500→      )}
   501→

   502→  );
   503→}
   504→

<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 } 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 {
    13→  HardHat,
    14→  Keyboard,
    15→  Mic,
    16→  ArrowLeft,
    17→  ArrowRight,
    18→  Loader2,
    19→  CheckCircle2,
    20→  Sparkles,
    21→  MapPin,
    22→  Clock,
    23→  Shield,
    24→  Edit3,
    25→  Wrench,
    26→  Calendar,
    27→  Globe2,
    28→} from "lucide-react";
    29→
    30→type Step = "auth" | "method" | "trade" | "experience" | "location" | "availability" | "certs" | "review";
    31→
    32→const TRADES = [
    33→  "Labourer",
    34→  "Steel Fixer",
    35→  "Formworker",
    36→  "Carpenter",
    37→  "Electrician",
    38→  "Plumber",
    39→  "Concreter",
    40→  "Bricklayer",
    41→  "Painter",
    42→  "Roofer",
    43→  "Scaffolder",
    44→  "Tiler",
    45→  "Plasterer",
    46→  "Welder",
    47→  "Crane Operator",
    48→  "Excavator Operator",
    49→];
    50→
    51→const EXPERIENCE_OPTIONS = [
    52→  { label: "Less than 1 year", value: 0 },
    53→  { label: "1-3 years", value: 2 },
    54→  { label: "3-5 years", value: 4 },
    55→  { label: "5-10 years", value: 7 },
    56→  { label: "10+ years", value: 12 },
    57→];
    58→
    59→const AVAILABILITY_OPTIONS = [
    60→  { label: "Full-time", value: "full-time" as const, desc: "Mon-Fri, regular hours" },
    61→  { label: "Casual", value: "casual" as const, desc: "As needed, flexible" },
    62→  { label: "Weekends", value: "weekends" as const, desc: "Saturday & Sunday" },
    63→  { label: "Flexible", value: "flexible" as const, desc: "Any time, any days" },
    64→];
    65→
    66→const CERT_OPTIONS = [
    67→  "White Card",
    68→  "Forklift Licence",
    69→  "EWP (Elevated Work Platform)",
    70→  "Working at Heights",
    71→  "First Aid",
    72→  "Traffic Control",
    73→  "Rigging",
    74→  "Dogging",
    75→  "Confined Spaces",
    76→];
    77→
    78→interface WorkerData {
    79→  trades: string[];
    80→  experience: number;
    81→  suburb: string;
    82→  availability: "full-time" | "casual" | "weekends" | "flexible";
    83→  certifications: string[];
    84→}
    85→
    86→function generateBio(data: WorkerData): string {
    87→  const tradeStr = data.trades.join(" and ");
    88→  const expStr =
    89→    data.experience === 0
    90→      ? "under 1 year"
    91→      : data.experience >= 12
    92→        ? "10+ years"
    93→        : `${data.experience} years`;
    94→  const certStr =
    95→    data.certifications.length > 0
    96→      ? ` Holds ${data.certifications.join(", ")}.`
    97→      : "";
    98→
    99→  return `Experienced ${tradeStr.toLowerCase()} with ${expStr} in the construction industry. Based in ${data.suburb}, available for ${data.availability} work.${certStr}`;
   100→}
   101→
   102→export default function WorkerSignupPage() {
   103→  const [step, setStep] = useState<Step>("auth");
   104→  const [isAuthenticated, setIsAuthenticated] = useState(false);
   105→  const [loading, setLoading] = useState(true);
   106→
   107→  // Auth state
   108→  const [email, setEmail] = useState("");
   109→  const [password, setPassword] = useState("");
   110→  const [authError, setAuthError] = useState<string | null>(null);
   111→  const [authLoading, setAuthLoading] = useState(false);
   112→
   113→  // Worker data
   114→  const [selectedTrades, setSelectedTrades] = useState<string[]>([]);
   115→  const [experience, setExperience] = useState<number | null>(null);
   116→  const [suburb, setSuburb] = useState("");
   117→  const [availability, setAvailability] = useState<WorkerData["availability"] | null>(null);
   118→  const [certifications, setCertifications] = useState<string[]>([]);
   119→
   120→  const [saving, setSaving] = useState(false);
   121→
   122→  const router = useRouter();
   123→  const supabase = createClient();
   124→
   125→  useEffect(() => {
   126→    async function checkAuth() {
   127→      const {
   128→        data: { user },
   129→      } = await supabase.auth.getUser();
   130→      if (user) {
   131→        setIsAuthenticated(true);
   132→        setStep("method");
   133→      }
   134→      setLoading(false);
   135→    }
   136→    checkAuth();
   137→  }, [supabase.auth]);
   138→
   139→  async function handleAuth(e: React.FormEvent) {
   140→    e.preventDefault();
   141→    setAuthLoading(true);
   142→    setAuthError(null);
   143→
   144→    const { error } = await supabase.auth.signUp({
   145→      email,
   146→      password,
   147→      options: {
   148→        data: { type: "worker" },
   149→        emailRedirectTo: `${window.location.origin}/auth/callback`,
   150→      },
   151→    });
   152→
   153→    if (error) {
   154→      if (error.message.includes("already registered")) {
   155→        const { error: loginError } = await supabase.auth.signInWithPassword({
   156→          email,
   157→          password,
   158→        });
   159→        if (loginError) {
   160→          setAuthError(loginError.message);
   161→          setAuthLoading(false);
   162→          return;
   163→        }
   164→      } else {
   165→        setAuthError(error.message);
   166→        setAuthLoading(false);
   167→        return;
   168→      }
   169→    }
   170→
   171→    setIsAuthenticated(true);
   172→    setStep("method");
   173→    setAuthLoading(false);
   174→  }
   175→
   176→  function toggleTrade(trade: string) {
   177→    setSelectedTrades((prev) =>
   178→      prev.includes(trade) ? prev.filter((t) => t !== trade) : [...prev, trade]
   179→    );
   180→  }
   181→
   182→  function toggleCert(cert: string) {
   183→    setCertifications((prev) =>
   184→      prev.includes(cert) ? prev.filter((c) => c !== cert) : [...prev, cert]
   185→    );
   186→  }
   187→
   188→  async function handleConfirm() {
   189→    if (!availability || experience === null) return;
   190→    setSaving(true);
   191→
   192→    try {
   193→      const {
   194→        data: { user },
   195→      } = await supabase.auth.getUser();
   196→      if (!user) {
   197→        toast.error("Not logged in. Please sign in again.");
   198→        setSaving(false);
   199→        return;
   200→      }
   201→
   202→      const workerData: WorkerData = {
   203→        trades: selectedTrades,
   204→        experience,
   205→        suburb,
   206→        availability,
   207→        certifications,
   208→      };
   209→
   210→      const bio = generateBio(workerData);
   211→
   212→      console.log("Updating profile...", { userId: user.id, suburb });
   213→      const profileResult = await supabase.from("profiles").update({
   214→        name: user.email?.split("@")[0] ?? "",
   215→        suburb,
   216→        onboarding_completed: true,
   217→      }).eq("id", user.id);
   218→
   219→      if (profileResult.error) {
   220→        console.error("Profile update failed:", profileResult.error);
   221→        toast.error(`Profile update failed: ${profileResult.error.message}`);
   222→        setSaving(false);
   223→        return;
   224→      }
   225→
   226→      console.log("Inserting worker profile...", { profile_id: user.id });
   227→      const workerResult = await supabase.from("worker_profiles").insert({
   228→        profile_id: user.id,
   229→        trades: selectedTrades,
   230→        experience_years: experience,
   231→        availability,
   232→        certifications,
   233→        bio,
   234→        ai_extracted: {
   235→          source: "typed_answers",
   236→          created_at: new Date().toISOString(),
   237→        },
   238→      });
   239→
   240→      if (workerResult.error) {
   241→        console.error("Worker profile insert failed:", workerResult.error);
   242→        toast.error(`Worker profile creation failed: ${workerResult.error.message}`);
   243→        setSaving(false);
   244→        return;
   245→      }
   246→
   247→      console.log("Profile created successfully!");
   248→      router.push("/dashboard");
   249→      router.refresh();
   250→    } catch (error: any) {
   251→      console.error("Signup error:", error);
   252→      toast.error(`Something went wrong: ${error.message || "Unknown error"}`);
   253→      setSaving(false);
   254→    }
   255→  }
   256→
   257→  const workerData: WorkerData | null =
   258→    selectedTrades.length > 0 && experience !== null && suburb && availability
   259→      ? { trades: selectedTrades, experience, suburb, availability, certifications }
   260→      : null;
   261→
   262→  const stepOrder: Step[] = ["auth", "method", "trade", "experience", "location", "availability", "certs", "review"];
   263→  const activeSteps = isAuthenticated ? stepOrder.slice(1) : stepOrder;
   264→  const currentIndex = activeSteps.indexOf(step);
   265→  const totalSteps = activeSteps.length;
   266→
   267→  if (loading) {
   268→    return (
   269→

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

   272→    );
   273→  }
   274→
   275→  return (
   276→

   277→      {/* Header */}
   278→
   279→

   280→          <Link href="/" className="flex items-center gap-2">
   281→

   282→              <HardHat className="h-5 w-5 text-primary-foreground" />
   283→

   284→            RateRight
   285→          </Link>
   286→

   287→
   288→
   289→
   290→        {/* Progress bar */}
   291→

   292→          {Array.from({ length: totalSteps }, (_, i) => (
   293→            <div
   294→              key={i}
   295→              className={`h-1.5 flex-1 rounded-full transition-colors ${
   296→                i <= currentIndex ? "bg-primary" : "bg-muted"
   297→              }`}
   298→            />
   299→          ))}
   300→

   301→
   302→        {/* Step: Auth */}
   303→        {step === "auth" && (
   304→

   305→

   306→

   307→                <HardHat className="h-5 w-5" />
   308→

   309→

   310→
Create your account

   311→

   312→                  Always free for workers
   313→

   314→

   315→

   316→
   317→            <form onSubmit={handleAuth} className="mt-6 space-y-4">
   318→

   319→                Email
   320→                <Input
   321→                  id="email"
   322→                  type="email"
   323→                  placeholder="you@email.com"
   324→                  value={email}
   325→                  onChange={(e) => setEmail(e.target.value)}
   326→                  required
   327→                  autoComplete="email"
   328→                  className="h-12 text-base"
   329→                />
   330→

   331→

   332→                Password
   333→                <Input
   334→                  id="password"
   335→                  type="password"
   336→                  placeholder="Create a password"
   337→                  value={password}
   338→                  onChange={(e) => setPassword(e.target.value)}
   339→                  required
   340→                  minLength={6}
   341→                  autoComplete="new-password"
   342→                  className="h-12 text-base"
   343→                />
   344→

   345→
   346→              {authError && (
   347→
{authError}

   348→              )}
   349→
   350→              <Button
   351→                type="submit"
   352→                className="h-12 w-full gap-2 text-base font-semibold"
   353→                disabled={authLoading}
   354→              >
   355→                {authLoading ? (
   356→                  <>
   357→                    <Loader2 className="h-5 w-5 animate-spin" />
   358→                    Creating account...
   359→                  </>
   360→                ) : (
   361→                  <>
   362→                    Continue
   363→                    <ArrowRight className="h-5 w-5" />
   364→                  </>
   365→                )}
   366→              </Button>
   367→            </form>
   368→
   369→

   370→              Already have an account?{" "}
   371→              <Link
   372→                href="/auth/login"
   373→                className="text-primary underline hover:no-underline"
   374→              >
   375→                Sign in
   376→              </Link>
   377→

   378→

   379→        )}
   380→
   381→        {/* Step: Choose method */}
   382→        {step === "method" && (
   383→

   384→

   385→

   386→                <HardHat className="h-5 w-5" />
   387→

   388→

   389→
Build your profile

   390→

   391→                  How do you want to get started?
   392→

   393→

   394→

   395→
   396→

   397→              <button
   398→                onClick={() => setStep("trade")}
   399→                className="flex w-full items-center gap-4 rounded-xl border-2 border-primary bg-primary/5 p-5 text-left transition-colors hover:bg-primary/10"
   400→              >
   401→

   402→                  <Keyboard className="h-6 w-6" />
   403→

   404→

   405→
Type my answers

   406→

   407→                    5 quick questions, under 2 minutes
   408→

   409→

   410→                <ArrowRight className="ml-auto h-5 w-5 text-primary" />
   411→              </button>
   412→
   413→              <button
   414→                disabled
   415→                className="flex w-full items-center gap-4 rounded-xl border-2 border-muted p-5 text-left opacity-50"
   416→              >
   417→

   418→                  <Mic className="h-6 w-6" />
   419→

   420→

   421→
Send a voice note

   422→

   423→                    Coming soon — speak and AI builds your profile
   424→

   425→

   426→              </button>
   427→

   428→

   429→        )}
   430→
   431→        {/* Step: Select trade */}
   432→        {step === "trade" && (
   433→

   434→            <StepHeader
   435→              icon={<Wrench className="h-5 w-5" />}
   436→              title="What's your trade?"
   437→              subtitle="Select one or more"
   438→            />
   439→
   440→

   441→              {TRADES.map((trade) => (
   442→                <button
   443→                  key={trade}
   444→                  onClick={() => toggleTrade(trade)}
   445→                  className={`rounded-xl border-2 px-4 py-3 text-left text-sm font-medium transition-colors ${
   446→                    selectedTrades.includes(trade)
   447→                      ? "border-primary bg-primary/10 text-primary"
   448→                      : "border-border hover:border-primary/50"
   449→                  }`}
   450→                >
   451→                  {trade}
   452→                </button>
   453→              ))}
   454→

   455→
   456→

   457→              <Button
   458→                variant="outline"
   459→                onClick={() => setStep("method")}
   460→                className="h-12"
   461→              >
   462→                <ArrowLeft className="h-5 w-5" />
   463→              </Button>
   464→              <Button
   465→                onClick={() => setStep("experience")}
   466→                disabled={selectedTrades.length === 0}
   467→                className="h-12 flex-1 gap-2 text-base font-semibold"
   468→              >
   469→                Continue
   470→                <ArrowRight className="h-5 w-5" />
   471→              </Button>
   472→

   473→

   474→        )}
   475→
   476→        {/* Step: Experience */}
   477→        {step === "experience" && (
   478→

   479→            <StepHeader
   480→              icon={<Clock className="h-5 w-5" />}
   481→              title="How much experience?"
   482→              subtitle="In construction overall"
   483→            />
   484→
   485→

   486→              {EXPERIENCE_OPTIONS.map((opt) => (
   487→                <button
   488→                  key={opt.value}
   489→                  onClick={() => {
   490→                    setExperience(opt.value);
   491→                    setStep("location");
   492→                  }}
   493→                  className={`flex w-full items-center justify-between rounded-xl border-2 px-5 py-4 text-left text-base font-medium transition-colors ${
   494→                    experience === opt.value
   495→                      ? "border-primary bg-primary/10 text-primary"
   496→                      : "border-border hover:border-primary/50"
   497→                  }`}
   498→                >
   499→                  {opt.label}
   500→                  <ArrowRight className="h-4 w-4 text-muted-foreground" />
   501→                </button>
   502→              ))}
   503→

   504→
   505→            <button
   506→              onClick={() => setStep("trade")}
   507→              className="mt-4 flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground"
   508→            >
   509→              <ArrowLeft className="h-4 w-4" />
   510→              Back
   511→            </button>
   512→

   513→        )}
   514→
   515→        {/* Step: Location */}
   516→        {step === "location" && (
   517→

   518→            <StepHeader
   519→              icon={<MapPin className="h-5 w-5" />}
   520→              title="Where are you based?"
   521→              subtitle="Your suburb or area"
   522→            />
   523→
   524→            <form
   525→              onSubmit={(e) => {
   526→                e.preventDefault();
   527→                if (suburb.trim()) setStep("availability");
   528→              }}
   529→              className="mt-6 space-y-4"
   530→            >
   531→              <Input
   532→                placeholder="e.g. Parramatta, Blacktown, Liverpool"
   533→                value={suburb}
   534→                onChange={(e) => setSuburb(e.target.value)}
   535→                required
   536→                autoFocus
   537→                className="h-12 text-base"
   538→              />
   539→

   540→                We'll match you to jobs near this location.
   541→

   542→
   543→

   544→                <Button
   545→                  type="button"
   546→                  variant="outline"
   547→                  onClick={() => setStep("experience")}
   548→                  className="h-12"
   549→                >
   550→                  <ArrowLeft className="h-5 w-5" />
   551→                </Button>
   552→                <Button
   553→                  type="submit"
   554→                  disabled={!suburb.trim()}
   555→                  className="h-12 flex-1 gap-2 text-base font-semibold"
   556→                >
   557→                  Continue
   558→                  <ArrowRight className="h-5 w-5" />
   559→                </Button>
   560→

   561→            </form>
   562→

   563→        )}
   564→
   565→        {/* Step: Availability */}
   566→        {step === "availability" && (
   567→

   568→            <StepHeader
   569→              icon={<Calendar className="h-5 w-5" />}
   570→              title="Your availability?"
   571→              subtitle="When can you work?"
   572→            />
   573→
   574→

   575→              {AVAILABILITY_OPTIONS.map((opt) => (
   576→                <button
   577→                  key={opt.value}
   578→                  onClick={() => {
   579→                    setAvailability(opt.value);
   580→                    setStep("certs");
   581→                  }}
   582→                  className={`flex w-full items-center justify-between rounded-xl border-2 px-5 py-4 text-left transition-colors ${
   583→                    availability === opt.value
   584→                      ? "border-primary bg-primary/10"
   585→                      : "border-border hover:border-primary/50"
   586→                  }`}
   587→                >
   588→

   589→                    <p className={`text-base font-medium ${availability === opt.value ? "text-primary" : ""}`}>
   590→                      {opt.label}
   591→

   592→
{opt.desc}

   593→

   594→                  <ArrowRight className="h-4 w-4 shrink-0 text-muted-foreground" />
   595→                </button>
   596→              ))}
   597→

   598→
   599→            <button
   600→              onClick={() => setStep("location")}
   601→              className="mt-4 flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground"
   602→            >
   603→              <ArrowLeft className="h-4 w-4" />
   604→              Back
   605→            </button>
   606→

   607→        )}
   608→
   609→        {/* Step: Certifications */}
   610→        {step === "certs" && (
   611→

   612→            <StepHeader
   613→              icon={<Shield className="h-5 w-5" />}
   614→              title="Your certifications"
   615→              subtitle="Select all that apply (optional)"
   616→            />
   617→
   618→

   619→              {CERT_OPTIONS.map((cert) => (
   620→                <button
   621→                  key={cert}
   622→                  onClick={() => toggleCert(cert)}
   623→                  className={`flex w-full items-center gap-3 rounded-xl border-2 px-5 py-3.5 text-left text-sm font-medium transition-colors ${
   624→                    certifications.includes(cert)
   625→                      ? "border-primary bg-primary/10 text-primary"
   626→                      : "border-border hover:border-primary/50"
   627→                  }`}
   628→                >
   629→

   636→                    {certifications.includes(cert) && (
   637→                      <CheckCircle2 className="h-3.5 w-3.5" />
   638→                    )}
   639→

   640→                  {cert}
   641→                </button>
   642→              ))}
   643→

   644→
   645→

   646→              <Button
   647→                variant="outline"
   648→                onClick={() => setStep("availability")}
   649→                className="h-12"
   650→              >
   651→                <ArrowLeft className="h-5 w-5" />
   652→              </Button>
   653→              <Button
   654→                onClick={() => setStep("review")}
   655→                className="h-12 flex-1 gap-2 text-base font-semibold"
   656→              >
   657→                Review my profile
   658→                <ArrowRight className="h-5 w-5" />
   659→              </Button>
   660→

   661→

   662→        )}
   663→
   664→        {/* Step: Review */}
   665→        {step === "review" && workerData && (
   666→

   667→

   668→

   669→                <CheckCircle2 className="h-5 w-5" />
   670→

   671→

   672→
Your profile

   673→

   674→                  Review and confirm
   675→

   676→

   677→

   678→
   679→            <Card className="mt-6">
   680→              <CardContent className="space-y-4 pt-6">
   681→                <ReviewField
   682→                  icon={<Wrench className="h-4 w-4" />}
   683→                  label="Trade"
   684→                  value={workerData.trades.join(", ")}
   685→                  onEdit={() => setStep("trade")}
   686→                />
   687→                <ReviewField
   688→                  icon={<Clock className="h-4 w-4" />}
   689→                  label="Experience"
   690→                  value={
   691→                    workerData.experience === 0
   692→                      ? "Less than 1 year"
   693→                      : workerData.experience >= 12
   694→                        ? "10+ years"
   695→                        : `${workerData.experience} years`
   696→                  }
   697→                  onEdit={() => setStep("experience")}
   698→                />
   699→                <ReviewField
   700→                  icon={<MapPin className="h-4 w-4" />}
   701→                  label="Location"
   702→                  value={workerData.suburb}
   703→                  onEdit={() => setStep("location")}
   704→                />
   705→                <ReviewField
   706→                  icon={<Calendar className="h-4 w-4" />}
   707→                  label="Availability"
   708→                  value={
   709→                    AVAILABILITY_OPTIONS.find(
   710→                      (o) => o.value === workerData.availability
   711→                    )?.label ?? workerData.availability
   712→                  }
   713→                  onEdit={() => setStep("availability")}
   714→                />
   715→                <ReviewField
   716→                  icon={<Shield className="h-4 w-4" />}
   717→                  label="Certifications"
   718→                  value={
   719→                    workerData.certifications.length > 0
   720→                      ? workerData.certifications.join(", ")
   721→                      : "None added"
   722→                  }
   723→                  onEdit={() => setStep("certs")}
   724→                />
   725→              </CardContent>
   726→            </Card>
   727→
   728→            {/* AI-generated bio */}
   729→

   730→

   731→                <Sparkles className="mt-0.5 h-4 w-4 shrink-0 text-primary" />
   732→

   733→

   734→                    AI-generated bio
   735→

   736→

   737→                    {generateBio(workerData)}
   738→

   739→

   740→

   741→

   742→
   743→            <Button
   744→              onClick={handleConfirm}
   745→              className="mt-6 h-14 w-full gap-2 text-base font-semibold"
   746→              disabled={saving}
   747→            >
   748→              {saving ? (
   749→                <>
   750→                  <Loader2 className="h-5 w-5 animate-spin" />
   751→                  Creating profile...
   752→                </>
   753→              ) : (
   754→                <>
   755→                  <CheckCircle2 className="h-5 w-5" />
   756→                  Looks good — create my profile
   757→                </>
   758→              )}
   759→            </Button>
   760→
   761→            <button
   762→              onClick={() => setStep("certs")}
   763→              className="mt-4 flex w-full items-center justify-center gap-1 text-sm text-muted-foreground hover:text-foreground"
   764→            >
   765→              <ArrowLeft className="h-4 w-4" />
   766→              Go back
   767→            </button>
   768→

   769→        )}
   770→
   771→

   772→  );
   773→}
   774→
   775→function StepHeader({
   776→  icon,
   777→  title,
   778→  subtitle,
   779→}: {
   780→  icon: React.ReactNode;
   781→  title: string;
   782→  subtitle: string;
   783→}) {
   784→  return (
   785→

   786→

   787→        {icon}
   788→

   789→

   790→
{title}

   791→
{subtitle}

   792→

   793→

   794→  );
   795→}
   796→
   797→function ReviewField({
   798→  icon,
   799→  label,
   800→  value,
   801→  onEdit,
   802→}: {
   803→  icon: React.ReactNode;
   804→  label: string;
   805→  value: string;
   806→  onEdit: () => void;
   807→}) {
   808→  return (
   809→    <button
   810→      onClick={onEdit}
   811→      className="flex w-full items-start gap-3 rounded-lg p-2 text-left transition-colors hover:bg-muted"
   812→    >
   813→
{icon}

   814→

   815→
{label}

   816→
{value}

   817→

   818→      <Edit3 className="mt-1 h-3.5 w-3.5 shrink-0 text-muted-foreground" />
   819→    </button>
   820→  );
   821→}
   822→

<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>