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

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

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

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

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

   285→
   286→
   287→
   288→        {/* Progress bar */}
   289→

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

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

   303→

   304→

   305→                <HardHat className="h-5 w-5" />
   306→

   307→

   308→
Create your account

   309→

   310→                  Always free for workers
   311→

   312→

   313→

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

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

   329→

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

   343→
   344→              {authError && (
   345→
{authError}

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

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

   376→

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

   382→

   383→

   384→                <HardHat className="h-5 w-5" />
   385→

   386→

   387→
Build your profile

   388→

   389→                  How do you want to get started?
   390→

   391→

   392→

   393→
   394→

   395→              <button
   396→                onClick={() => setStep("trade")}
   397→                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"
   398→              >
   399→

   400→                  <Keyboard className="h-6 w-6" />
   401→

   402→

   403→
Type my answers

   404→

   405→                    5 quick questions, under 2 minutes
   406→

   407→

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

   416→                  <Mic className="h-6 w-6" />
   417→

   418→

   419→
Send a voice note

   420→

   421→                    Coming soon — speak and AI builds your profile
   422→

   423→

   424→              </button>
   425→

   426→

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

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

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

   453→
   454→

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

   471→

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

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

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

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

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

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

   538→                We'll match you to jobs near this location.
   539→

   540→
   541→

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

   559→            </form>
   560→

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

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

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

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

   590→
{opt.desc}

   591→

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

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

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

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

   617→              {CERT_OPTIONS.map((cert) => (
   618→                <button
   619→                  key={cert}
   620→                  onClick={() => toggleCert(cert)}
   621→                  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 ${
   622→                    certifications.includes(cert)
   623→                      ? "border-primary bg-primary/10 text-primary"
   624→                      : "border-border hover:border-primary/50"
   625→                  }`}
   626→                >
   627→

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

   638→                  {cert}
   639→                </button>
   640→              ))}
   641→

   642→
   643→

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

   659→

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

   665→

   666→

   667→                <CheckCircle2 className="h-5 w-5" />
   668→

   669→

   670→
Your profile

   671→

   672→                  Review and confirm
   673→

   674→

   675→

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

   728→

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

   731→

   732→                    AI-generated bio
   733→

   734→

   735→                    {generateBio(workerData)}
   736→

   737→

   738→

   739→

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

   767→        )}
   768→
   769→

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

   784→

   785→        {icon}
   786→

   787→

   788→
{title}

   789→
{subtitle}

   790→

   791→

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

   812→

   813→
{label}

   814→
{value}

   815→

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

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

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

   362→    );
   363→  }
   364→
   365→  return (
   366→

   367→
   368→        {/* Progress bar */}
   369→        {step !== "generating" && step !== "review" && (
   370→

   371→            {Array.from({ length: totalSteps }, (_, i) => (
   372→              <div
   373→                key={i}
   374→                className={`h-1.5 flex-1 rounded-full transition-colors ${
   375→                  i <= currentIndex ? "bg-primary" : "bg-muted"
   376→                }`}
   377→              />
   378→            ))}
   379→

   380→        )}
   381→
   382→        {/* Step: Trade */}
   383→        {step === "trade" && (
   384→

   385→            <StepHeader
   386→              icon={<Wrench className="h-5 w-5" />}
   387→              title="What's your trade?"
   388→              subtitle="Pick your primary trade"
   389→            />
   390→
   391→            {/* Voice option */}
   392→            {!showVoice && (
   393→              <button
   394→                onClick={() => setShowVoice(true)}
   395→                className="mt-6 flex w-full items-center justify-center gap-3 rounded-xl border-2 border-dashed border-primary/40 bg-primary/5 px-5 py-4 text-base font-medium text-primary transition-colors hover:border-primary hover:bg-primary/10 active:scale-95"
   396→                style={{ minHeight: "64px" }}
   397→              >
   398→                <Mic className="h-6 w-6" />
   399→                Or tell us with your voice
   400→              </button>
   401→            )}
   402→
   403→            {showVoice && (
   404→              <Card className="mt-6">
   405→                <CardContent className="flex flex-col items-center gap-4 pt-6">
   406→

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

   409→
   410→                  {voiceProcessing ? (
   411→

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

   414→

   415→                  ) : (
   416→                    <VoiceRecorder onRecordingComplete={handleVoiceComplete} />
   417→                  )}
   418→
   419→                  {voiceError && (
   420→
{voiceError}

   421→                  )}
   422→
   423→                  <button
   424→                    onClick={() => {
   425→                      setShowVoice(false);
   426→                      setVoiceError(null);
   427→                    }}
   428→                    className="text-sm text-muted-foreground hover:text-foreground"
   429→                  >
   430→                    Cancel — I'll tap instead
   431→                  </button>
   432→                </CardContent>
   433→              </Card>
   434→            )}
   435→
   436→

   437→              {TRADES.map((t) => (
   438→                <button
   439→                  key={t}
   440→                  onClick={() => {
   441→                    setTrade(t);
   442→                    setAllTrades([t]);
   443→                    setStep("experience");
   444→                  }}
   445→                  className={`rounded-xl border-2 px-4 py-4 text-left text-base font-medium transition-colors active:scale-95 ${
   446→                    trade === t
   447→                      ? "border-primary bg-primary/10 text-primary"
   448→                      : "border-border hover:border-primary/50"
   449→                  }`}
   450→                  style={{ minHeight: "64px" }}
   451→                >
   452→                  {t}
   453→                </button>
   454→              ))}
   455→

   456→

   457→        )}
   458→
   459→        {/* Step: Experience */}
   460→        {step === "experience" && (
   461→

   462→            <StepHeader
   463→              icon={<Clock className="h-5 w-5" />}
   464→              title="Your experience?"
   465→              subtitle="Years in construction"
   466→            />
   467→
   468→

   469→              {EXPERIENCE_OPTIONS.map((opt) => (
   470→                <button
   471→                  key={opt.value}
   472→                  onClick={() => {
   473→                    setExperience(opt.value);
   474→                    setStep("location");
   475→                  }}
   476→                  className={`flex w-full items-center justify-between rounded-xl border-2 px-5 text-left text-base font-medium transition-colors active:scale-95 ${
   477→                    experience === opt.value
   478→                      ? "border-primary bg-primary/10 text-primary"
   479→                      : "border-border hover:border-primary/50"
   480→                  }`}
   481→                  style={{ minHeight: "64px" }}
   482→                >
   483→                  {opt.label}
   484→                  <ArrowRight className="h-5 w-5 text-muted-foreground" />
   485→                </button>
   486→              ))}
   487→

   488→
   489→            <button
   490→              onClick={() => setStep("trade")}
   491→              className="mt-4 flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground"
   492→            >
   493→              <ArrowLeft className="h-4 w-4" />
   494→              Back
   495→            </button>
   496→

   497→        )}
   498→
   499→        {/* Step: Location */}
   500→        {step === "location" && (
   501→

   502→            <StepHeader
   503→              icon={<MapPin className="h-5 w-5" />}
   504→              title="Where are you based?"
   505→              subtitle="Your suburb or area"
   506→            />
   507→
   508→            <form
   509→              onSubmit={(e) => {
   510→                e.preventDefault();
   511→                if (location.trim()) setStep("availability");
   512→              }}
   513→              className="mt-6 space-y-4"
   514→            >
   515→

   516→                Suburb or Area
   517→                <Input
   518→                  id="location"
   519→                  placeholder="e.g. Parramatta, Blacktown"
   520→                  value={location}
   521→                  onChange={(e) => setLocation(e.target.value)}
   522→                  required
   523→                  autoFocus
   524→                  className="h-14 text-base"
   525→                />
   526→

   527→

   528→                We'll match you to nearby jobs
   529→

   530→
   531→

   532→                <Button
   533→                  type="button"
   534→                  variant="outline"
   535→                  onClick={() => setStep("experience")}
   536→                  className="h-14 w-14 p-0"
   537→                >
   538→                  <ArrowLeft className="h-5 w-5" />
   539→                </Button>
   540→                <Button
   541→                  type="submit"
   542→                  disabled={!location.trim()}
   543→                  className="h-14 flex-1 gap-2 text-base font-semibold"
   544→                >
   545→                  Continue
   546→                  <ArrowRight className="h-5 w-5" />
   547→                </Button>
   548→

   549→            </form>
   550→

   551→        )}
   552→
   553→        {/* Step: Availability */}
   554→        {step === "availability" && (
   555→

   556→            <StepHeader
   557→              icon={<Calendar className="h-5 w-5" />}
   558→              title="Your availability?"
   559→              subtitle="When can you work?"
   560→            />
   561→
   562→

   563→              {AVAILABILITY_OPTIONS.map((opt) => (
   564→                <button
   565→                  key={opt.value}
   566→                  onClick={() => {
   567→                    setAvailability(opt.value);
   568→                    generateProfile();
   569→                  }}
   570→                  className={`flex w-full items-start justify-between rounded-xl border-2 px-5 py-4 text-left transition-colors active:scale-95 ${
   571→                    availability === opt.value
   572→                      ? "border-primary bg-primary/10"
   573→                      : "border-border hover:border-primary/50"
   574→                  }`}
   575→                  style={{ minHeight: "64px" }}
   576→                >
   577→

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

   581→
{opt.desc}

   582→

   583→                  <Zap className="h-5 w-5 shrink-0 text-primary" />
   584→                </button>
   585→              ))}
   586→

   587→
   588→            <button
   589→              onClick={() => setStep("location")}
   590→              className="mt-4 flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground"
   591→            >
   592→              <ArrowLeft className="h-4 w-4" />
   593→              Back
   594→            </button>
   595→

   596→        )}
   597→
   598→        {/* Step: Generating */}
   599→        {step === "generating" && (
   600→

   601→

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

   604→
Building your profile...

   605→

   606→              AI is generating your bio, skills, and certifications
   607→

   608→

   609→

   610→

   611→

   612→

   613→

   614→        )}
   615→
   616→        {/* Step: Review */}
   617→        {step === "review" && (
   618→

   619→

   620→

   621→                <Sparkles className="h-5 w-5" />
   622→

   623→

   624→
Review & Edit

   625→

   626→                  AI generated this — customise as needed
   627→

   628→

   629→

   630→
   631→            {/* Your Answers Summary */}
   632→            <Card className="mt-6">
   633→              <CardContent className="space-y-3 pt-6">
   634→

   635→

   636→                    <Wrench className="h-4 w-4 text-muted-foreground" />
   637→
   638→                      {allTrades.length > 1 ? allTrades.join(", ") : trade}
   639→
   640→

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

   648→

   649→

   650→                    <Clock className="h-4 w-4 text-muted-foreground" />
   651→
   652→                      {EXPERIENCE_OPTIONS.find((o) => o.value === experience)?.label}
   653→
   654→

   655→                  <button
   656→                    onClick={() => setStep("experience")}
   657→                    className="text-xs text-primary hover:underline"
   658→                  >
   659→                    Change
   660→                  </button>
   661→

   662→

   663→

   664→                    <MapPin className="h-4 w-4 text-muted-foreground" />
   665→                    {location}
   666→

   667→                  <button
   668→                    onClick={() => setStep("location")}
   669→                    className="text-xs text-primary hover:underline"
   670→                  >
   671→                    Change
   672→                  </button>
   673→

   674→              </CardContent>
   675→            </Card>
   676→
   677→            {/* Bio */}
   678→

   679→

   680→
   681→                  <Sparkles className="h-4 w-4 text-primary" />
   682→                  Your Bio
   683→
   684→                AI-generated
   685→

   686→              <textarea
   687→                value={editedBio}
   688→                onChange={(e) => setEditedBio(e.target.value)}
   689→                className="min-h-[120px] w-full rounded-xl border-2 border-border bg-background px-4 py-3 text-base focus:border-primary focus:outline-none"
   690→                placeholder="Your professional bio..."
   691→              />
   692→

   693→
   694→            {/* Skills */}
   695→

   696→

   697→
   698→                  <Zap className="h-4 w-4 text-primary" />
   699→                  Skills
   700→
   701→                Tap to remove
   702→

   703→

   704→                {editedSkills.map((skill, idx) => (
   705→                  <button
   706→                    key={idx}
   707→                    onClick={() => setEditedSkills(editedSkills.filter((_, i) => i !== idx))}
   708→                    className="rounded-full border-2 border-primary bg-primary/10 px-4 py-2 text-sm font-medium text-primary transition-colors hover:bg-primary/20 active:scale-95"
   709→                  >
   710→                    {skill} ×
   711→                  </button>
   712→                ))}
   713→

   714→

   715→
   716→            {/* Certifications */}
   717→

   718→

   719→
   720→                  <Shield className="h-4 w-4 text-primary" />
   721→                  Certifications
   722→
   723→                Tap to remove
   724→

   725→

   726→                {editedCerts.map((cert, idx) => (
   727→                  <button
   728→                    key={idx}
   729→                    onClick={() => setEditedCerts(editedCerts.filter((_, i) => i !== idx))}
   730→                    className="flex w-full items-center justify-between rounded-xl border-2 border-border bg-background px-4 py-3 text-left text-sm font-medium transition-colors hover:border-destructive hover:bg-destructive/5 active:scale-95"
   731→                  >
   732→                    {cert}
   733→                    Remove
   734→                  </button>
   735→                ))}
   736→

   737→

   738→

   739→        )}
   740→
   741→
   742→      {/* Bottom sticky CTA - only show on review step */}
   743→      {step === "review" && (
   744→

   745→

   746→            <Button
   747→              onClick={handleSave}
   748→              className="h-16 w-full gap-2 text-lg font-semibold"
   749→              disabled={saving}
   750→            >
   751→              {saving ? (
   752→                <>
   753→                  <Loader2 className="h-6 w-6 animate-spin" />
   754→                  Saving profile...
   755→                </>
   756→              ) : (
   757→                <>
   758→                  <CheckCircle2 className="h-6 w-6" />
   759→                  Save My Profile
   760→                </>
   761→              )}
   762→            </Button>
   763→

   764→

   765→      )}
   766→

   767→  );
   768→}
   769→
   770→function StepHeader({
   771→  icon,
   772→  title,
   773→  subtitle,
   774→}: {
   775→  icon: React.ReactNode;
   776→  title: string;
   777→  subtitle: string;
   778→}) {
   779→  return (
   780→

   781→

   782→        {icon}
   783→

   784→

   785→
{title}

   786→
{subtitle}

   787→

   788→

   789→  );
   790→}
   791→

<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 pages (contractor and notifications/messages pages):


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

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

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

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

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

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

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

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

   327→

   328→

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

   331→

   332→
Create your account

   333→

   334→                  Quick signup to get started
   335→

   336→

   337→

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

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

   353→

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

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

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

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

   400→

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

   406→

   407→

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

   410→

   411→
Your company

   412→

   413→                  We'll verify it via ABR
   414→

   415→

   416→

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

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

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

   434→

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

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

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

   464→

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

   467→
Verifying your business

   468→

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

   471→
   472→

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

   477→

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

   483→

   484→

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

   487→

   488→
Confirm your profile

   489→

   490→                  Tap any field to edit
   491→

   492→

   493→

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

   556→

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

   559→

   560→                    Verified via Australian Business Register
   561→

   562→

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

   565→

   566→

   567→

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

   595→        )}
   596→
   597→

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

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

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

   635→        {label}
   636→

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

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

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

   670→

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

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

   694→      ) : (
   695→
{icon}

   696→      )}
   697→

   698→
{label}

   699→
{value}

   700→

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

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