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 { 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 { useEffect, useState } from 'react';
     4→import { useSearchParams, useRouter } from 'next/navigation';
     5→import Link from 'next/link';
     6→import { CheckCircle2, Loader2, AlertCircle } from 'lucide-react';
     7→import { Button } from '@/components/ui/button';
     8→import { createClient } from '@/lib/supabase/client';
     9→
    10→export default function PaymentSuccessPage() {
    11→  const searchParams = useSearchParams();
    12→  const router = useRouter();
    13→  const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
    14→  const [message, setMessage] = useState('Confirming your payment...');
    15→  const supabase = createClient();
    16→
    17→  useEffect(() => {
    18→    const paymentIntent = searchParams.get('payment_intent');
    19→    const paymentIntentClientSecret = searchParams.get('payment_intent_client_secret');
    20→
    21→    if (!paymentIntent || !paymentIntentClientSecret) {
    22→      setStatus('error');
    23→      setMessage('Invalid payment confirmation');
    24→      return;
    25→    }
    26→
    27→    // Check payment status
    28→    async function checkPayment() {
    29→      try {
    30→        // Wait a moment for webhook to process
    31→        await new Promise(resolve => setTimeout(resolve, 2000));
    32→
    33→        // Check if payment was recorded
    34→        const { data: payment } = await supabase
    35→          .from('payments')
    36→          .select('status, match_id')
    37→          .eq('stripe_payment_id', paymentIntent)
    38→          .single();
    39→
    40→        if (payment?.status === 'charged') {
    41→          setStatus('success');
    42→          setMessage('Your payment was successful and the worker has been notified!');
    43→        } else {
    44→          // Payment might still be processing via webhook
    45→          setStatus('success');
    46→          setMessage('Payment received! Processing your hire...');
    47→        }
    48→      } catch (error) {
    49→        console.error('Error checking payment:', error);
    50→        setStatus('success'); // Assume success since Stripe redirected here
    51→        setMessage('Payment received! Processing your hire...');
    52→      }
    53→    }
    54→
    55→    checkPayment();
    56→  }, [searchParams, supabase]);
    57→
    58→  return (
    59→

    60→

    61→        {status === 'loading' && (
    62→          <>
    63→            <Loader2 className="mx-auto h-16 w-16 animate-spin text-primary" />
    64→
Confirming Payment

    65→
{message}

    66→          </>
    67→        )}
    68→
    69→        {status === 'success' && (
    70→          <>
    71→

    72→              <CheckCircle2 className="h-10 w-10 text-green-600" />
    73→

    74→
Payment Successful!

    75→
{message}

    76→

    77→              <Button asChild className="w-full">
    78→                <Link href="/contractor/matches">Back to Matches</Link>
    79→              </Button>
    80→              <Button asChild variant="outline" className="w-full">
    81→                <Link href="/dashboard">Go to Dashboard</Link>
    82→              </Button>
    83→

    84→          </>
    85→        )}
    86→
    87→        {status === 'error' && (
    88→          <>
    89→

    90→              <AlertCircle className="h-10 w-10 text-destructive" />
    91→

    92→
Payment Error

    93→
{message}

    94→

    95→              <Button asChild variant="outline" className="w-full">
    96→                <Link href="/contractor/matches">Back to Matches</Link>
    97→              </Button>
    98→

    99→          </>
   100→        )}
   101→

   102→

   103→  );
   104→}
   105→

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


Now let me read the remaining worker and message pages, and some UI components:


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 {
    11→  MapPin,
    12→  Calendar,
    13→  DollarSign,
    14→  Loader2,
    15→  Search,
    16→  Briefcase,
    17→  Check,
    18→  X,
    19→} from "lucide-react";
    20→import type { Job } from "@/lib/types";
    21→import { postgresPointToJson } from "@/lib/location-utils";
    22→
    23→const TRADE_FILTERS = [
    24→  "All",
    25→  "Steel Fixer",
    26→  "Formworker",
    27→  "Labourer",
    28→  "Concreter",
    29→  "Carpenter",
    30→  "Scaffolder",
    31→  "Other",
    32→];
    33→
    34→const TRADE_ID_TO_LABEL: Record<string, string> = {
    35→  "steel-fixer": "Steel Fixer",
    36→  "formworker": "Formworker",
    37→  "labourer": "Labourer",
    38→  "concreter": "Concreter",
    39→  "carpenter": "Carpenter",
    40→  "scaffolder": "Scaffolder",
    41→  "other": "Other",
    42→};
    43→
    44→function tradeLabelFromId(id: string): string {
    45→  return TRADE_ID_TO_LABEL[id] || id;
    46→}
    47→
    48→interface SwipeableJobCardProps {
    49→  job: Job;
    50→  hasApplied: boolean;
    51→  onApply: (job: Job) => void;
    52→  onClick: (job: Job) => void;
    53→}
    54→
    55→function SwipeableJobCard({ job, hasApplied, onApply, onClick }: SwipeableJobCardProps) {
    56→  const [swipeX, setSwipeX] = useState(0);
    57→  const [swiping, setSwiping] = useState(false);
    58→  const startX = useRef(0);
    59→  const currentX = useRef(0);
    60→
    61→  const handleTouchStart = (e: React.TouchEvent) => {
    62→    if (hasApplied) return;
    63→    startX.current = e.touches[0].clientX;
    64→    setSwiping(true);
    65→  };
    66→
    67→  const handleTouchMove = (e: React.TouchEvent) => {
    68→    if (!swiping || hasApplied) return;
    69→    currentX.current = e.touches[0].clientX;
    70→    const diff = currentX.current - startX.current;
    71→    // Only allow right swipe (positive diff)
    72→    if (diff > 0) {
    73→      setSwipeX(Math.min(diff, 150));
    74→    }
    75→  };
    76→
    77→  const handleTouchEnd = () => {
    78→    if (!swiping || hasApplied) return;
    79→    setSwiping(false);
    80→
    81→    // If swiped more than 100px, apply
    82→    if (swipeX > 100) {
    83→      onApply(job);
    84→    }
    85→
    86→    // Reset
    87→    setSwipeX(0);
    88→  };
    89→
    90→  const swipeProgress = Math.min(swipeX / 100, 1);
    91→  const backgroundColor = hasApplied
    92→    ? "transparent"
    93→    : `rgba(34, 197, 94, ${swipeProgress * 0.1})`;
    94→
    95→  return (
    96→

    97→      {/* Swipe indicator background */}
    98→      {!hasApplied && swipeX > 0 && (
    99→

   106→          <Check className="h-8 w-8 text-green-600" />
   107→
   108→            {swipeProgress >= 1 ? "Release to apply" : "Swipe to apply"}
   109→
   110→

   111→      )}
   112→
   113→      <Card
   114→        className="cursor-pointer active:scale-[0.98] transition-transform touch-pan-y"
   115→        style={{
   116→          transform: `translateX(${swipeX}px)`,
   117→          transition: swiping ? 'none' : 'transform 0.2s ease-out',
   118→          backgroundColor,
   119→        }}
   120→        onTouchStart={handleTouchStart}
   121→        onTouchMove={handleTouchMove}
   122→        onTouchEnd={handleTouchEnd}
   123→        onClick={() => !swiping && onClick(job)}
   124→      >
   125→        <CardContent className="p-5">
   126→

   127→

   128→

   129→                <Badge variant="secondary" className="text-sm shrink-0 py-1 px-3">
   130→                  {tradeLabelFromId(job.trade)}
   131→                </Badge>
   132→                {hasApplied && (
   133→                  <Badge className="text-sm bg-green-600 shrink-0 py-1 px-3">
   134→                    <Check className="h-3.5 w-3.5 mr-1" />
   135→                    Applied
   136→                  </Badge>
   137→                )}
   138→

   139→
{job.title}

   140→

   141→

   142→
   143→

   144→            {job.suburb && (
   145→
   146→                <MapPin className="h-4 w-4" />
   147→                {job.suburb}
   148→
   149→            )}
   150→            {(job.rate_min || job.rate_max) && (
   151→
   152→                <DollarSign className="h-4 w-4" />
   153→                {job.rate_min && job.rate_max
   154→                  ? `$${job.rate_min}–$${job.rate_max}`
   155→                  : job.rate_min
   156→                  ? `From $${job.rate_min}`
   157→                  : `Up to $${job.rate_max}`}
   158→                {job.rate_type === "hourly" ? "/hr" : "/day"}
   159→
   160→            )}
   161→            {job.start_date && (
   162→
   163→                <Calendar className="h-4 w-4" />
   164→                {new Date(job.start_date).toLocaleDateString("en-AU", {
   165→                  day: "numeric",
   166→                  month: "short",
   167→                })}
   168→
   169→            )}
   170→            {job.workers_needed > 1 && (
   171→
   172→                <Briefcase className="h-4 w-4" />
   173→                {job.workers_needed} workers
   174→
   175→            )}
   176→

   177→        </CardContent>
   178→      </Card>
   179→

   180→  );
   181→}
   182→
   183→export default function WorkerJobsPage() {
   184→  const supabase = createClient();
   185→  const [jobs, setJobs] = useState<Job[]>([]);
   186→  const [loading, setLoading] = useState(true);
   187→  const [activeTrade, setActiveTrade] = useState("All");
   188→  const [selectedJob, setSelectedJob] = useState<Job | null>(null);
   189→  const [applying, setApplying] = useState(false);
   190→  const [appliedJobs, setAppliedJobs] = useState<Set<string>>(new Set());
   191→  const [userId, setUserId] = useState<string | null>(null);
   192→
   193→  useEffect(() => {
   194→    async function load() {
   195→      const { data: { user } } = await supabase.auth.getUser();
   196→      if (user) setUserId(user.id);
   197→
   198→      // Fetch active jobs
   199→      const { data } = await supabase
   200→        .from("jobs")
   201→        .select("*")
   202→        .eq("status", "active")
   203→        .order("created_at", { ascending: false });
   204→
   205→      if (data) {
   206→        // Convert PostgreSQL point to JSON format
   207→        const convertedJobs = data.map((job: any) => ({
   208→          ...job,
   209→          location: postgresPointToJson(job.location)
   210→        }));
   211→        setJobs(convertedJobs);
   212→      }
   213→
   214→      // Fetch already-applied jobs
   215→      if (user) {
   216→        const { data: matches } = await supabase
   217→          .from("matches")
   218→          .select("job_id")
   219→          .eq("worker_id", user.id);
   220→
   221→        if (matches) {
   222→          setAppliedJobs(new Set(matches.map((m: { job_id: string }) => m.job_id)));
   223→        }
   224→      }
   225→
   226→      setLoading(false);
   227→    }
   228→    load();
   229→  }, [supabase]);
   230→
   231→  const filteredJobs =
   232→    activeTrade === "All"
   233→      ? jobs
   234→      : jobs.filter(
   235→          (j) => tradeLabelFromId(j.trade).toLowerCase() === activeTrade.toLowerCase()
   236→        );
   237→
   238→  async function handleApply(job: Job) {
   239→    if (!userId || appliedJobs.has(job.id)) return;
   240→    setApplying(true);
   241→
   242→    // Haptic feedback (if supported)
   243→    if (navigator.vibrate) {
   244→      navigator.vibrate(50);
   245→    }
   246→
   247→    const { error } = await supabase.from("matches").insert({
   248→      job_id: job.id,
   249→      worker_id: userId,
   250→      status: "applied",
   251→      contractor_action: "pending",
   252→      worker_action: "accepted",
   253→    });
   254→
   255→    if (!error) {
   256→      setAppliedJobs((prev) => new Set(prev).add(job.id));
   257→    }
   258→
   259→    setApplying(false);
   260→    setSelectedJob(null);
   261→  }
   262→
   263→  if (loading) {
   264→    return (
   265→

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

   268→    );
   269→  }
   270→
   271→  return (
   272→

   273→
   274→        {/* Trade filters — horizontal scroll */}
   275→

   276→          {TRADE_FILTERS.map((trade) => (
   277→            <button
   278→              key={trade}
   279→              onClick={() => setActiveTrade(trade)}
   280→              className={`shrink-0 rounded-full px-5 py-3 text-base font-semibold transition-all active:scale-95 min-h-[48px] ${
   281→                activeTrade === trade
   282→                  ? "bg-primary text-primary-foreground"
   283→                  : "bg-muted text-muted-foreground"
   284→              }`}
   285→            >
   286→              {trade}
   287→            </button>
   288→          ))}
   289→

   290→
   291→        {/* Swipe hint */}
   292→        {filteredJobs.length > 0 && !loading && (
   293→

   294→

   295→              👉 Swipe right to quick apply • Tap to view details
   296→

   297→

   298→        )}
   299→
   300→        {/* Job list */}
   301→        {filteredJobs.length === 0 ? (
   302→

   303→            <Search className="h-16 w-16 text-muted-foreground/50" />
   304→
No jobs found

   305→

   306→              {activeTrade === "All"
   307→                ? "No jobs posted yet. Check back soon."
   308→                : `No ${activeTrade} jobs right now. Try another trade.`}
   309→

   310→

   311→        ) : (
   312→

   313→            {filteredJobs.map((job) => (
   314→              <SwipeableJobCard
   315→                key={job.id}
   316→                job={job}
   317→                hasApplied={appliedJobs.has(job.id)}
   318→                onApply={handleApply}
   319→                onClick={setSelectedJob}
   320→              />
   321→            ))}
   322→

   323→        )}
   324→
   325→
   326→      {/* Job Detail Sheet (bottom sheet style) */}
   327→      {selectedJob && (
   328→

   329→
 setSelectedJob(null)}
   332→          />
   333→

   334→            {/* Handle */}
   335→

   336→

   337→

   338→
   339→

   340→              <Badge variant="secondary" className="mb-3 text-sm py-1.5 px-3">
   341→                {tradeLabelFromId(selectedJob.trade)}
   342→              </Badge>
   343→
{selectedJob.title}

   344→
   345→

   346→                {selectedJob.suburb && (
   347→
   348→                    <MapPin className="h-5 w-5" />
   349→                    {selectedJob.site_address || selectedJob.suburb}
   350→
   351→                )}
   352→                {(selectedJob.rate_min || selectedJob.rate_max) && (
   353→
   354→                    <DollarSign className="h-5 w-5" />
   355→                    {selectedJob.rate_min && selectedJob.rate_max
   356→                      ? `$${selectedJob.rate_min}–$${selectedJob.rate_max}`
   357→                      : selectedJob.rate_min
   358→                      ? `From $${selectedJob.rate_min}`
   359→                      : `Up to $${selectedJob.rate_max}`}
   360→                    {selectedJob.rate_type === "hourly" ? "/hr" : "/day"}
   361→
   362→                )}
   363→

   364→
   365→

   366→                {selectedJob.start_date && (
   367→
   368→                    <Calendar className="h-5 w-5" />
   369→                    Starts{" "}
   370→                    {new Date(selectedJob.start_date).toLocaleDateString("en-AU", {
   371→                      weekday: "short",
   372→                      day: "numeric",
   373→                      month: "short",
   374→                    })}
   375→
   376→                )}
   377→                {selectedJob.duration && (
   378→                  Duration: {selectedJob.duration}
   379→                )}
   380→                {selectedJob.workers_needed > 1 && (
   381→
   382→                    <Briefcase className="h-5 w-5" />
   383→                    {selectedJob.workers_needed} workers needed
   384→
   385→                )}
   386→

   387→
   388→              {selectedJob.description && (
   389→

   390→
About the Job

   391→

   392→                    {selectedJob.description}
   393→

   394→

   395→              )}
   396→
   397→              {selectedJob.certifications_required?.length > 0 && (
   398→

   399→
Required Certs

   400→

   401→                    {selectedJob.certifications_required.map((cert) => (
   402→                      <Badge key={cert} variant="outline" className="text-sm py-1.5 px-3">
   403→                        {cert}
   404→                      </Badge>
   405→                    ))}
   406→

   407→

   408→              )}
   409→

   410→
   411→            {/* Sticky Bottom CTA */}
   412→

   413→

   414→                {appliedJobs.has(selectedJob.id) ? (
   415→                  <Button disabled className="w-full h-16 text-lg font-semibold bg-green-600">
   416→                    <Check className="mr-2 h-6 w-6" />
   417→                    Already Applied
   418→                  </Button>
   419→                ) : (
   420→                  <Button
   421→                    onClick={() => handleApply(selectedJob)}
   422→                    className="w-full h-16 text-lg font-semibold"
   423→                    disabled={applying}
   424→                  >
   425→                    {applying ? (
   426→                      <>
   427→                        <Loader2 className="mr-2 h-6 w-6 animate-spin" />
   428→                        Applying...
   429→                      </>
   430→                    ) : (
   431→                      <>
   432→                        Apply with One Tap
   433→                        <Check className="ml-2 h-6 w-6" />
   434→                      </>
   435→                    )}
   436→                  </Button>
   437→                )}
   438→

   439→                  Always free for workers. No fees ever.
   440→

   441→

   442→

   443→

   444→

   445→      )}
   446→
   447→      <BottomNav userType="worker" />
   448→

   449→  );
   450→}
   451→

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


1→"use client";
     2→
     3→import { useState, useEffect } from "react";
     4→import Link from "next/link";
     5→import { useRouter } from "next/navigation";
     6→import { ConversationPreview } from "@/components/ConversationPreview";
     7→import { Button } from "@/components/ui/button";
     8→import { Input } from "@/components/ui/input";
     9→import { Search, MessageSquare } from "lucide-react";
    10→import { createClient } from "@/lib/supabase/client";
    11→
    12→interface Conversation {
    13→  conversation_id: string;
    14→  other_participant_name: string;
    15→  other_participant_avatar: string | null;
    16→  last_message: string | null;
    17→  last_message_time: string | null;
    18→  unread_count: number;
    19→  other_participant_is_online: boolean;
    20→}
    21→
    22→export default function MessagesPage() {
    23→  const router = useRouter();
    24→  const [searchQuery, setSearchQuery] = useState("");
    25→  const [conversations, setConversations] = useState<Conversation[]>([]);
    26→  const [loading, setLoading] = useState(true);
    27→  const supabase = createClient();
    28→
    29→  useEffect(() => {
    30→    fetchConversations();
    31→
    32→    // Subscribe to new messages for real-time updates
    33→    const channel = supabase
    34→      .channel('messages')
    35→      .on('postgres_changes',
    36→        {
    37→          event: 'INSERT',
    38→          schema: 'public',
    39→          table: 'messages'
    40→        },
    41→        () => {
    42→          fetchConversations();
    43→        }
    44→      )
    45→      .subscribe();
    46→
    47→    return () => {
    48→      supabase.removeChannel(channel);
    49→    };
    50→  }, []);
    51→
    52→  const fetchConversations = async () => {
    53→    try {
    54→      const response = await fetch('/api/messages');
    55→      if (response.ok) {
    56→        const data = await response.json();
    57→        setConversations(data.conversations || []);
    58→      }
    59→    } catch (error) {
    60→      console.error("Error fetching conversations:", error);
    61→    } finally {
    62→      setLoading(false);
    63→    }
    64→  };
    65→
    66→  const filteredConversations = conversations.filter((conversation) =>
    67→    conversation.other_participant_name?.toLowerCase().includes(searchQuery.toLowerCase())
    68→  );
    69→
    70→  const totalUnread = conversations.reduce(
    71→    (sum, conv) => sum + conv.unread_count,
    72→    0
    73→  );
    74→
    75→  if (loading) {
    76→    return (
    77→

    78→

    79→

    80→            <Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
    81→            <Input
    82→              type="search"
    83→              placeholder="Search conversations..."
    84→              value={searchQuery}
    85→              onChange={(e) => setSearchQuery(e.target.value)}
    86→              className="h-12 pl-10 text-base"
    87→              disabled
    88→            />
    89→

    90→

    91→

    92→
Loading conversations...

    93→

    94→

    95→    );
    96→  }
    97→
    98→  return (
    99→

   100→      {/* Search Bar */}
   101→

   102→

   103→          <Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
   104→          <Input
   105→            type="search"
   106→            placeholder="Search conversations..."
   107→            value={searchQuery}
   108→            onChange={(e) => setSearchQuery(e.target.value)}
   109→            className="h-12 pl-10 text-base"
   110→          />
   111→

   112→

   113→
   114→      {/* Conversations List */}
   115→
   116→        {filteredConversations.length === 0 ? (
   117→

   118→            <MessageSquare className="h-16 w-16 text-muted-foreground mb-4" />
   119→
No conversations yet

   120→

   121→              {searchQuery
   122→                ? "No conversations match your search"
   123→                : "Start a conversation by hiring a worker or applying for a job"}
   124→

   125→            {!searchQuery && (
   126→              <Button asChild>
   127→                <Link href="/dashboard">Browse Jobs</Link>
   128→              </Button>
   129→            )}
   130→

   131→        ) : (
   132→

   133→            {filteredConversations.map((conversation) => (
   134→              <ConversationPreview
   135→                key={conversation.conversation_id}
   136→                id={conversation.conversation_id}
   137→                name={conversation.other_participant_name}
   138→                avatar={conversation.other_participant_avatar || undefined}
   139→                lastMessage={conversation.last_message || "No messages yet"}
   140→                lastMessageTime={conversation.last_message_time ? new Date(conversation.last_message_time) : new Date()}
   141→                unreadCount={conversation.unread_count}
   142→                isOnline={conversation.other_participant_is_online}
   143→                onClick={() => router.push(`/messages/${conversation.conversation_id}`)}
   144→              />
   145→            ))}
   146→

   147→        )}
   148→
   149→

   150→  );
   151→}
   152→

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


Perfect! Now let me read a few more key component files to complete the picture:


     1→import * as React from "react"
     2→import { cva, type VariantProps } from "class-variance-authority"
     3→import { Slot } from "radix-ui"
     4→
     5→import { cn } from "@/lib/utils"
     6→
     7→const buttonVariants = cva(
     8→  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
     9→  {
    10→    variants: {
    11→      variant: {
    12→        default: "bg-primary text-primary-foreground hover:bg-primary/90",
    13→        destructive:
    14→          "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
    15→        outline:
    16→          "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
    17→        secondary:
    18→          "bg-secondary text-secondary-foreground hover:bg-secondary/80",
    19→        ghost:
    20→          "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
    21→        link: "text-primary underline-offset-4 hover:underline",
    22→      },
    23→      size: {
    24→        default: "h-9 px-4 py-2 has-[>svg]:px-3",
    25→        xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
    26→        sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
    27→        lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
    28→        icon: "size-9",
    29→        "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
    30→        "icon-sm": "size-8",
    31→        "icon-lg": "size-10",
    32→      },
    33→    },
    34→    defaultVariants: {
    35→      variant: "default",
    36→      size: "default",
    37→    },
    38→  }
    39→)
    40→
    41→function Button({
    42→  className,
    43→  variant = "default",
    44→  size = "default",
    45→  asChild = false,
    46→  ...props
    47→}: React.ComponentProps<"button"> &
    48→  VariantProps<typeof buttonVariants> & {
    49→    asChild?: boolean
    50→  }) {
    51→  const Comp = asChild ? Slot.Root : "button"
    52→
    53→  return (
    54→    <Comp
    55→      data-slot="button"
    56→      data-variant={variant}
    57→      data-size={size}
    58→      className={cn(buttonVariants({ variant, size, className }))}
    59→      {...props}
    60→    />
    61→  )
    62→}
    63→
    64→export { Button, buttonVariants }
    65→

<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 * as React from "react"
     2→
     3→import { cn } from "@/lib/utils"
     4→
     5→function Card({ className, ...props }: React.ComponentProps<"div">) {
     6→  return (
     7→    <div
     8→      data-slot="card"
     9→      className={cn(
    10→        "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
    11→        className
    12→      )}
    13→      {...props}
    14→    />
    15→  )
    16→}
    17→
    18→function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
    19→  return (
    20→    <div
    21→      data-slot="card-header"
    22→      className={cn(
    23→        "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
    24→        className
    25→      )}
    26→      {...props}
    27→    />
    28→  )
    29→}
    30→
    31→function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
    32→  return (
    33→    <div
    34→      data-slot="card-title"
    35→      className={cn("leading-none font-semibold", className)}
    36→      {...props}
    37→    />
    38→  )
    39→}
    40→
    41→function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
    42→  return (
    43→    <div
    44→      data-slot="card-description"
    45→      className={cn("text-muted-foreground text-sm", className)}
    46→      {...props}
    47→    />
    48→  )
    49→}
    50→
    51→function CardAction({ className, ...props }: React.ComponentProps<"div">) {
    52→  return (
    53→    <div
    54→      data-slot="card-action"
    55→      className={cn(
    56→        "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
    57→        className
    58→      )}
    59→      {...props}
    60→    />
    61→  )
    62→}
    63→
    64→function CardContent({ className, ...props }: React.ComponentProps<"div">) {
    65→  return (
    66→    <div
    67→      data-slot="card-content"
    68→      className={cn("px-6", className)}
    69→      {...props}
    70→    />
    71→  )
    72→}
    73→
    74→function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
    75→  return (
    76→    <div
    77→      data-slot="card-footer"
    78→      className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
    79→      {...props}
    80→    />
    81→  )
    82→}
    83→
    84→export {
    85→  Card,
    86→  CardHeader,
    87→  CardFooter,
    88→  CardTitle,
    89→  CardAction,
    90→  CardDescription,
    91→  CardContent,
    92→}
    93→

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