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

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

   259→    );
   260→  }
   261→
   262→  return (
   263→

   264→      {/* Header */}
   265→
   266→

   267→          <Link href="/" className="flex items-center gap-2">
   268→

   269→              <HardHat className="h-5 w-5 text-primary-foreground" />
   270→

   271→            RateRight
   272→          </Link>
   273→

   274→
   275→
   276→
   277→        {/* Progress bar */}
   278→

   279→          {Array.from({ length: totalSteps }, (_, i) => (
   280→            <div
   281→              key={i}
   282→              className={`h-1.5 flex-1 rounded-full transition-colors ${
   283→                i <= currentIndex ? "bg-primary" : "bg-muted"
   284→              }`}
   285→            />
   286→          ))}
   287→

   288→
   289→        {/* Step: Auth */}
   290→        {step === "auth" && (
   291→

   292→

   293→

   294→                <HardHat className="h-5 w-5" />
   295→

   296→

   297→
Create your account

   298→

   299→                  Always free for workers
   300→

   301→

   302→

   303→
   304→            <form onSubmit={handleAuth} className="mt-6 space-y-4">
   305→

   306→                Email
   307→                <Input
   308→                  id="email"
   309→                  type="email"
   310→                  placeholder="you@email.com"
   311→                  value={email}
   312→                  onChange={(e) => setEmail(e.target.value)}
   313→                  required
   314→                  autoComplete="email"
   315→                  className="h-12 text-base"
   316→                />
   317→

   318→

   319→                Password
   320→                <Input
   321→                  id="password"
   322→                  type="password"
   323→                  placeholder="Create a password"
   324→                  value={password}
   325→                  onChange={(e) => setPassword(e.target.value)}
   326→                  required
   327→                  minLength={6}
   328→                  autoComplete="new-password"
   329→                  className="h-12 text-base"
   330→                />
   331→

   332→
   333→              {authError && (
   334→
{authError}

   335→              )}
   336→
   337→              <Button
   338→                type="submit"
   339→                className="h-12 w-full gap-2 text-base font-semibold"
   340→                disabled={authLoading}
   341→              >
   342→                {authLoading ? (
   343→                  <>
   344→                    <Loader2 className="h-5 w-5 animate-spin" />
   345→                    Creating account...
   346→                  </>
   347→                ) : (
   348→                  <>
   349→                    Continue
   350→                    <ArrowRight className="h-5 w-5" />
   351→                  </>
   352→                )}
   353→              </Button>
   354→            </form>
   355→
   356→

   357→              Already have an account?{" "}
   358→              <Link
   359→                href="/auth/login"
   360→                className="text-primary underline hover:no-underline"
   361→              >
   362→                Sign in
   363→              </Link>
   364→

   365→

   366→        )}
   367→
   368→        {/* Step: Choose method */}
   369→        {step === "method" && (
   370→

   371→

   372→

   373→                <HardHat className="h-5 w-5" />
   374→

   375→

   376→
Build your profile

   377→

   378→                  How do you want to get started?
   379→

   380→

   381→

   382→
   383→

   384→              <button
   385→                onClick={() => setStep("trade")}
   386→                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"
   387→              >
   388→

   389→                  <Keyboard className="h-6 w-6" />
   390→

   391→

   392→
Type my answers

   393→

   394→                    5 quick questions, under 2 minutes
   395→

   396→

   397→                <ArrowRight className="ml-auto h-5 w-5 text-primary" />
   398→              </button>
   399→
   400→              <button
   401→                disabled
   402→                className="flex w-full items-center gap-4 rounded-xl border-2 border-muted p-5 text-left opacity-50"
   403→              >
   404→

   405→                  <Mic className="h-6 w-6" />
   406→

   407→

   408→
Send a voice note

   409→

   410→                    Coming soon — speak and AI builds your profile
   411→

   412→

   413→              </button>
   414→

   415→

   416→        )}
   417→
   418→        {/* Step: Select trade */}
   419→        {step === "trade" && (
   420→

   421→            <StepHeader
   422→              icon={<Wrench className="h-5 w-5" />}
   423→              title="What's your trade?"
   424→              subtitle="Select one or more"
   425→            />
   426→
   427→

   428→              {TRADES.map((trade) => (
   429→                <button
   430→                  key={trade}
   431→                  onClick={() => toggleTrade(trade)}
   432→                  className={`rounded-xl border-2 px-4 py-3 text-left text-sm font-medium transition-colors ${
   433→                    selectedTrades.includes(trade)
   434→                      ? "border-primary bg-primary/10 text-primary"
   435→                      : "border-border hover:border-primary/50"
   436→                  }`}
   437→                >
   438→                  {trade}
   439→                </button>
   440→              ))}
   441→

   442→
   443→

   444→              <Button
   445→                variant="outline"
   446→                onClick={() => setStep("method")}
   447→                className="h-12"
   448→              >
   449→                <ArrowLeft className="h-5 w-5" />
   450→              </Button>
   451→              <Button
   452→                onClick={() => setStep("experience")}
   453→                disabled={selectedTrades.length === 0}
   454→                className="h-12 flex-1 gap-2 text-base font-semibold"
   455→              >
   456→                Continue
   457→                <ArrowRight className="h-5 w-5" />
   458→              </Button>
   459→

   460→

   461→        )}
   462→
   463→        {/* Step: Experience */}
   464→        {step === "experience" && (
   465→

   466→            <StepHeader
   467→              icon={<Clock className="h-5 w-5" />}
   468→              title="How much experience?"
   469→              subtitle="In construction overall"
   470→            />
   471→
   472→

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

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

   500→        )}
   501→
   502→        {/* Step: Location */}
   503→        {step === "location" && (
   504→

   505→            <StepHeader
   506→              icon={<MapPin className="h-5 w-5" />}
   507→              title="Where are you based?"
   508→              subtitle="Your suburb or area"
   509→            />
   510→
   511→            <form
   512→              onSubmit={(e) => {
   513→                e.preventDefault();
   514→                if (suburb.trim()) setStep("availability");
   515→              }}
   516→              className="mt-6 space-y-4"
   517→            >
   518→              <Input
   519→                placeholder="e.g. Parramatta, Blacktown, Liverpool"
   520→                value={suburb}
   521→                onChange={(e) => setSuburb(e.target.value)}
   522→                required
   523→                autoFocus
   524→                className="h-12 text-base"
   525→              />
   526→

   527→                We'll match you to jobs near this location.
   528→

   529→
   530→

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

   548→            </form>
   549→

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

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

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

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

   579→
{opt.desc}

   580→

   581→                  <ArrowRight className="h-4 w-4 shrink-0 text-muted-foreground" />
   582→                </button>
   583→              ))}
   584→

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

   594→        )}
   595→
   596→        {/* Step: Certifications */}
   597→        {step === "certs" && (
   598→

   599→            <StepHeader
   600→              icon={<Shield className="h-5 w-5" />}
   601→              title="Your certifications"
   602→              subtitle="Select all that apply (optional)"
   603→            />
   604→
   605→

   606→              {CERT_OPTIONS.map((cert) => (
   607→                <button
   608→                  key={cert}
   609→                  onClick={() => toggleCert(cert)}
   610→                  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 ${
   611→                    certifications.includes(cert)
   612→                      ? "border-primary bg-primary/10 text-primary"
   613→                      : "border-border hover:border-primary/50"
   614→                  }`}
   615→                >
   616→

   623→                    {certifications.includes(cert) && (
   624→                      <CheckCircle2 className="h-3.5 w-3.5" />
   625→                    )}
   626→

   627→                  {cert}
   628→                </button>
   629→              ))}
   630→

   631→
   632→

   633→              <Button
   634→                variant="outline"
   635→                onClick={() => setStep("availability")}
   636→                className="h-12"
   637→              >
   638→                <ArrowLeft className="h-5 w-5" />
   639→              </Button>
   640→              <Button
   641→                onClick={() => setStep("review")}
   642→                className="h-12 flex-1 gap-2 text-base font-semibold"
   643→              >
   644→                Review my profile
   645→                <ArrowRight className="h-5 w-5" />
   646→              </Button>
   647→

   648→

   649→        )}
   650→
   651→        {/* Step: Review */}
   652→        {step === "review" && workerData && (
   653→

   654→

   655→

   656→                <CheckCircle2 className="h-5 w-5" />
   657→

   658→

   659→
Your profile

   660→

   661→                  Review and confirm
   662→

   663→

   664→

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

   717→

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

   720→

   721→                    AI-generated bio
   722→

   723→

   724→                    {generateBio(workerData)}
   725→

   726→

   727→

   728→

   729→
   730→            <Button
   731→              onClick={handleConfirm}
   732→              className="mt-6 h-14 w-full gap-2 text-base font-semibold"
   733→              disabled={saving}
   734→            >
   735→              {saving ? (
   736→                <>
   737→                  <Loader2 className="h-5 w-5 animate-spin" />
   738→                  Creating profile...
   739→                </>
   740→              ) : (
   741→                <>
   742→                  <CheckCircle2 className="h-5 w-5" />
   743→                  Looks good — create my profile
   744→                </>
   745→              )}
   746→            </Button>
   747→
   748→            <button
   749→              onClick={() => setStep("certs")}
   750→              className="mt-4 flex w-full items-center justify-center gap-1 text-sm text-muted-foreground hover:text-foreground"
   751→            >
   752→              <ArrowLeft className="h-4 w-4" />
   753→              Go back
   754→            </button>
   755→

   756→        )}
   757→
   758→

   759→  );
   760→}
   761→
   762→function StepHeader({
   763→  icon,
   764→  title,
   765→  subtitle,
   766→}: {
   767→  icon: React.ReactNode;
   768→  title: string;
   769→  subtitle: string;
   770→}) {
   771→  return (
   772→

   773→

   774→        {icon}
   775→

   776→

   777→
{title}

   778→
{subtitle}

   779→

   780→

   781→  );
   782→}
   783→
   784→function ReviewField({
   785→  icon,
   786→  label,
   787→  value,
   788→  onEdit,
   789→}: {
   790→  icon: React.ReactNode;
   791→  label: string;
   792→  value: string;
   793→  onEdit: () => void;
   794→}) {
   795→  return (
   796→    <button
   797→      onClick={onEdit}
   798→      className="flex w-full items-start gap-3 rounded-lg p-2 text-left transition-colors hover:bg-muted"
   799→    >
   800→
{icon}

   801→

   802→
{label}

   803→
{value}

   804→

   805→      <Edit3 className="mt-1 h-3.5 w-3.5 shrink-0 text-muted-foreground" />
   806→    </button>
   807→  );
   808→}
   809→

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


Perfect! Now I have reviewed all the page components. Let me compile my comprehensive audit report.

## Comprehensive Audit Report: RateRight $50 App - Page Components

### Executive Summary
I've thoroughly audited ALL page components in `/home/ccuser/the-50-dollar-app/src/app/`. The codebase is **generally well-structured with good error handling**, but I've identified several bugs, issues, and missing error handling that need attention.

---

### CRITICAL & HIGH-SEVERITY BUGS

#### 1. **Missing Error Handling in Worker Signup (page.tsx)**
**File:** `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/worker/signup/page.tsx`
**Lines:** 213-225, 227-246

**Issue:** The profile and worker_profiles database operations lack error handling:
```typescript
// Lines 214-218
const profileResult = await supabase.from("profiles").update({...})
if (profileResult.error) {
  console.error("Profile update failed:", profileResult.error);
  toast.error(`Profile update failed: ${profileResult.error.message}`);
  setSaving(false);
  return;
}
// No error handling after this for insert
```

**Bug:** If `worker_profiles.insert()` fails (line 228-239), the user is NOT informed. The code doesn't check for errors, so the page silently redirects on line 249 even if the database insert failed.

**Fix:** Add error handling after line 240:
```typescript
if (workerResult.error) {
  console.error("Worker profile insert failed:", workerResult.error);
  toast.error(`Worker profile creation failed: ${workerResult.error.message}`);
  setSaving(false);
  return;
}
```

---

#### 2. **Race Condition in Dashboard - Pending Ratings Loop**
**File:** `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/dashboard/page.tsx`
**Lines:** 60-84

**Issue:** The code fetches hired matches and then makes N+1 database queries in a loop:
```typescript
for (const match of hiredMatches) {
  const { data: existingRating } = await supabase
    .from("ratings")
    .select("id")
    .eq("match_id", match.id)
    .eq("from_id", user.id)
    .maybeSingle();

  if (!existingRating) {
    // Gets profile data in loop
    const { data: p } = await supabase.from("profiles").select("name").eq("id", otherId).single();
  }
}
```

**Bug:** This creates N+1 queries for every hired match. For a user with 50 hired matches, this makes ~100 database queries. This will slow down the dashboard significantly.

**Fix:** Use a single `.in()` query to fetch all profiles at once.

---

#### 3. **No Validation of URL Parameters in Rate Page**
**File:** `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/rate/[matchId]/page.tsx`
**Lines:** 23-24, 27-42

**Issue:** The `matchId` is extracted from params without validation:
```typescript
const matchId = params.matchId as string;
```

If `matchId` is empty or invalid, the query on line 34 fails silently and returns null. The error message says "This match is not available" which is misleading - it could be a bad URL.

**Fix:** Validate matchId before querying:
```typescript
if (!matchId || typeof matchId !== 'string' || matchId.trim() === '') {
  setError("Invalid match ID");
  setLoading(false);
  return;
}
```

---

#### 4. **Missing Error Handling in Contractor Matches Page**
**File:** `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/contractor/matches/page.tsx`
**Lines:** 413-429

**Issue:** The `handlePaymentSuccess()` function doesn't actually update the database:
```typescript
async function handlePaymentSuccess() {
  if (!selectedMatch) return;

  // Only updates local state!
  setMatches((prev) =>
    prev.map((m) =>
      m.id === selectedMatch.id
        ? { ...m, status: "hired", contractor_action: "accepted", hired_at: new Date().toISOString(), fee_charged: true }
        : m
    )
  );

  setHireSuccess(selectedMatch.id);
  setIsPaymentModalOpen(false);
  setSelectedMatch(null);
  setTimeout(() => setHireSuccess(null), 3000);
}
```

**Bug:** The function updates local React state but never calls the API or database to persist the "hired" status. If the user refreshes the page, the hire will be lost because it was never saved to the database.

**Fix:** Add database update after payment success:
```typescript
async function handlePaymentSuccess() {
  if (!selectedMatch) return;

  // Update database FIRST
  const { error } = await supabase
    .from("matches")
    .update({ status: "hired", contractor_action: "accepted", hired_at: new Date().toISOString(), fee_charged: true })
    .eq("id", selectedMatch.id);

  if (error) {
    setError("Failed to update hire status. Please refresh.");
    return;
  }

  // Then update local state
  setMatches((prev) => ...);
}
```

---

#### 5. **Message Sending Without CSRF Protection**
**File:** `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/messages/[conversationId]/page.tsx`
**Lines:** 131-147

**Issue:** The message sending endpoint doesn't use CSRF protection:
```typescript
const response = await fetch(`/api/messages/${conversationId}/send`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ content: messageContent })
});
```

**Bug:** Unlike other API calls in the codebase (e.g., profile building uses `csrfFetch`), this endpoint is missing CSRF token. This could allow CSRF attacks where an attacker tricks users into sending messages.

**Fix:** Use `csrfFetch` instead:
```typescript
import { csrfFetch } from '@/lib/csrf-client';

const response = await csrfFetch(`/api/messages/${conversationId}/send`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ content: messageContent })
});
```

---

### MEDIUM-SEVERITY ISSUES

#### 6. **Inconsistent Error Handling in Payment Success Page**
**File:** `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/contractor/payment-success/page.tsx`
**Lines:** 28-57

**Issue:** The page assumes success even if the database query fails:
```typescript
const paymentIntent = searchParams.get('payment_intent');
if (!paymentIntent || !paymentIntentClientSecret) {
  setStatus('error');
  setMessage('Invalid payment confirmation');
  return;
}

async function checkPayment() {
  try {
    // Wait for webhook (2 second delay)
    await new Promise(resolve => setTimeout(resolve, 2000));

    const { data: payment } = await supabase
      .from('payments')
      .select('status, match_id')
      .eq('stripe_payment_id', paymentIntent)
      .single();

    if (payment?.status === 'charged') {
      setStatus('success');
    } else {
      // Still shows success even if payment not confirmed!
      setStatus('success');
      setMessage('Payment received! Processing your hire...');
    }
  } catch (error) {
    // Assumes success on error!
    setStatus('success');
  }
}
```

**Bug:** Lines 46 and 51 show success even when:
1. Payment status is not "charged" (line 46)
2. An exception occurs (line 51)

This is dangerous because if the webhook fails, the user thinks they paid but no database record exists.

**Fix:**
```typescript
if (payment?.status === 'charged') {
  setStatus('success');
  setMessage('Your payment was successful and the worker has been notified!');
} else {
  setStatus('error');
  setMessage('Payment status could not be confirmed. Please contact support.');
}
```

---

#### 7. **Unprotected API Call in Worker Jobs Page**
**File:** `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/worker/jobs/page.tsx`
**Lines:** 238-261

**Issue:** The `handleApply` function has no error handling:
```typescript
async function handleApply(job: Job) {
  if (!userId || appliedJobs.has(job.id)) return;
  setApplying(true);

  if (navigator.vibrate) {
    navigator.vibrate(50);
  }

  const { error } = await supabase.from("matches").insert({
    job_id: job.id,
    worker_id: userId,
    status: "applied",
    contractor_action: "pending",
    worker_action: "accepted",
  });

  if (!error) {
    setAppliedJobs((prev) => new Set(prev).add(job.id));
  }

  setApplying(false);
  setSelectedJob(null);
}
```

**Bug:** If the insert fails (line 247), the function:
1. Doesn't show an error toast to the user
2. Still calls `setApplying(false)` (line 259) making it look like it succeeded
3. Still clears `setSelectedJob(null)` (line 260) closing the modal

The user thinks they applied but the database insert failed.

**Fix:**
```typescript
if (error) {
  toast.error("Failed to apply for job. Please try again.");
  setApplying(false);
  return;
}
toast.success("Applied successfully!");
```

---

#### 8. **No Error Handling in Contractor Post Job Page**
**File:** `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/contractor/post-job/page.tsx`
**Lines:** 168-221

**Issue:** The `handlePost()` function doesn't handle AI generation failures:
```typescript
async function handleTradeSelect(tradeId: string) {
  setSelectedTrade(tradeId);
  setAiLoading(true);
  setStep(2);

  try {
    const res = await csrfFetch("/api/ai/generate-job", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ trade: tradeId }),
    });

    if (res.ok) {
      // ... success
    }
  } catch {
    // Silently falls through to hardcoded fallback
  }

  // Fallback: use hardcoded suggestions
  const suggestion = AI_SUGGESTIONS[tradeId];
}
```

**Bug:** If the API returns a non-200 status (but doesn't throw), the code falls through to hardcoded suggestions without logging the error. Also, if `AI_SUGGESTIONS[tradeId]` is undefined, the fields become empty with no error message.

**Fix:**
```typescript
if (!suggestion) {
  setError("Failed to load job template. Please try again.");
  setStep(1); // Go back
  setAiLoading(false);
  return;
}
```

---

#### 9. **Missing Null Safety in Messages Page**
**File:** `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/messages/[conversationId]/page.tsx`
**Lines:** 64-77

**Issue:** The real-time subscription callback has unsafe type casting:
```typescript
.on('postgres_changes',
  {
    event: 'INSERT',
    schema: 'public',
    table: 'messages',
    filter: `conversation_id=eq.${conversationId}`
  },
  (payload) => {
    const newMessage = payload.new as any; // ← Unsafe cast!
    supabase.auth.getUser().then(({ data: { user } }) => {
      if (newMessage.sender_id !== user?.id) {
        setMessages(prev => [...prev, {
          id: newMessage.id,
          content: newMessage.content,
          isOwn: false,
          timestamp: new Date(newMessage.created_at),
          status: newMessage.status
        }]);
      }
    });
  }
)
```

**Bug:** Using `as any` bypasses TypeScript safety. If the database schema changes or Supabase returns unexpected data, the code will silently create invalid message objects.

**Fix:** Create a proper type:
```typescript
interface DatabaseMessage {
  id: string;
  content: string;
  sender_id: string;
  created_at: string;
  status?: string;
}

const newMessage = payload.new as DatabaseMessage;
if (!newMessage.id || !newMessage.content) {
  console.error("Invalid message received:", payload.new);
  return;
}
```

---

### LOW-SEVERITY ISSUES & CODE QUALITY

#### 10. **Backup File Should Be Removed**
**File:** `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/worker/signup/page.tsx.backup`

**Issue:** Backup files should not be in production source code. This file is a duplicate of the current signup page (with minor differences - missing error handling in the original version).

**Fix:** Delete the `.backup` file. Use version control (git) instead.

---

#### 11. **Commented-Out Code in Profile Build Page**
**File:** `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/worker/profile/build/page.tsx`

**Issue:** No dead code found, but the code is very long and complex. The voice feature (which is disabled) adds significant complexity:
```typescript
const [showVoice, setShowVoice] = useState(false);
const [voiceProcessing, setVoiceProcessing] = useState(false);
// ... all voice-related state
```

**Recommendation:** Consider moving voice functionality to a separate component or removing it entirely if it's not being developed.

---

#### 12. **No Loading State in Contractor Matches During Payment**
**File:** `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/contractor/matches/page.tsx`
**Lines:** 397-411

**Issue:** When payment modal opens, there's no visual feedback that something is happening. The button just says "Hiring — $50 Fee" without showing loading state until the modal appears.

**Recommendation:** Show a loading spinner while opening the payment modal.

---

#### 13. **Inconsistent API Error Handling**
Multiple pages have inconsistent error handling patterns:

- **worker/jobs/page.tsx:** No toast on error
- **contractor/matches/page.tsx:** Uses `setError()` to display errors
- **worker/profile/build/page.tsx:** Uses `toast.error()` for errors
- **messages/[conversationId]/page.tsx:** Silently fails without user notification

**Recommendation:** Standardize on using `react-hot-toast` for all error messages across the app.

---

#### 14. **Missing Validations**
**Files:** Multiple

**Issues:**
- `/contractor/post-job/page.tsx` (line 505): No validation that `title` and `suburb` are not just whitespace
- `/contractor/signup/page.tsx` (lines 280-284): Editing company fields doesn't validate input length or format
- `/worker/profile/build/page.tsx`: No validation that location input is a valid suburb

**Fix:** Add `.trim()` checks and length validations.

---

#### 15. **Missing Query Optimization**
**File:** `/contractor/matches/page.tsx` (lines 364-367)

**Issue:** Fetches profiles and worker_profiles separately with two parallel queries. Could be optimized with a single join:
```typescript
const [{ data: profiles }, { data: workerProfiles }] = await Promise.all([
  supabase.from("profiles").select("id, name, suburb, avatar_url").in("id", workerIds),
  supabase.from("worker_profiles").select("profile_id, trades, experience_years, certifications, availability").in("profile_id", workerIds),
]);
```

**Better approach:** Create a view in Supabase that joins these tables, or use a single query with joins.

---

### SECURITY CONCERNS

#### 16. **Message Send Endpoint Missing CSRF** (Already noted above as #5)

#### 17. **Unvalidated User Type in Bottom Nav**
**File:** `/messages/page.tsx` (line 28)

The `userType` state defaults to "worker" without verification:
```typescript
const [userType, setUserType] = useState<"contractor" | "worker">("worker");
```

If the fetch fails, it stays as "worker" even if the user is a contractor. This could show wrong navigation items.

**Fix:** Don't render navigation until userType is verified.

---

### MISSING FUNCTIONALITY

#### 18. **No Confirmation Before Rejecting Match**
**File:** `/contractor/matches/page.tsx` (line 431-447)

The `handleReject()` function permanently removes a match from the UI without asking for confirmation. Users might accidentally reject a good candidate by swiping left.

**Recommendation:** Add a confirmation dialog: "Are you sure you want to pass on this worker?"

---

#### 19. **Phone Button Non-Functional**
**File:** `/messages/[conversationId]/page.tsx` (line 228-230)

The phone button renders but has no `onClick` handler:
```typescript
<Button variant="ghost" size="icon" className="h-10 w-10">
  <Phone className="h-5 w-5" />
</Button>
```

**Fix:** Either implement phone call functionality or remove the button.

---

### BEST PRACTICES & RECOMMENDATIONS

1. **Add Request Timeout Handling:** API calls should have timeouts (e.g., 30s) to prevent hanging requests.

2. **Add Retry Logic:** For critical operations like payment and hire confirmation, add exponential backoff retry.

3. **Database Connection Pooling:** The N+1 query pattern in dashboard (issue #2) suggests need for query optimization.

4. **API Rate Limiting:** No rate limiting visible on client-side. Add client-side delays to prevent rapid-fire requests.

5. **Audit Logging:** Payment failures and hire status changes should be logged for auditing.

---

### SUMMARY TABLE

| Severity | Count | Issues |
|----------|-------|--------|
| Critical | 5 | #1, #2, #3, #4, #5 |
| High | 3 | #6, #7, #8 |
| Medium | 4 | #9, #12, #13, #14 |
| Low | 6 | #10, #11, #15, #16, #17, #18, #19 |

**Total Issues Found: 18**

All page components have been audited. The most critical issues relate to missing error handling on database operations, unprotected API calls, and data persistence bugs in the hiring flow.