Find the worker profile build page that calls the AI endpoints. Search for files matching patterns like "build/page.tsx" or "profile" under the worker directory in /home/ccuser/the-50-dollar-app/src/app/. Also search for any file that calls "/api/ai/generate-profile" or "/api/ai/parse-profile-voice". Return the full file paths found.


I'll search for the worker profile build page and files that call the AI endpoints.


No files found


Great! I found the build page. Now let me search more thoroughly for files calling the AI endpoints and read the main build page:


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

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

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

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

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

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

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

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

   397→
   398→                  {voiceProcessing ? (
   399→

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

   402→

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

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

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

   443→

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

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

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

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

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

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

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

   514→

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

   517→
   518→

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

   536→            </form>
   537→

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

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

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

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

   568→
{opt.desc}

   569→

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

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

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

   588→

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

   591→
Building your profile...

   592→

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

   595→

   596→

   597→

   598→

   599→

   600→

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

   606→

   607→

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

   610→

   611→
Review & Edit

   612→

   613→                  AI generated this — customise as needed
   614→

   615→

   616→

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

   622→

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

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

   633→

   634→

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

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

   647→

   648→

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

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

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

   664→

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

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

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

   681→

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

   688→

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

   699→

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

   703→

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

   710→

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

   722→

   723→

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

   730→

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

   749→

   750→      )}
   751→

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

   766→

   767→        {icon}
   768→

   769→

   770→
{title}

   771→
{subtitle}

   772→

   773→

   774→  );
   775→}
   776→

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


Found 2 files
/home/ccuser/the-50-dollar-app/src/app/(authenticated)/contractor/post-job/page.tsx
/home/ccuser/the-50-dollar-app/src/app/(authenticated)/worker/profile/build/page.tsx


Found 6 files
/home/ccuser/the-50-dollar-app/src/app/(authenticated)/worker/profile/build/page.tsx
/home/ccuser/the-50-dollar-app/AUDIT-H1-H5-H11-H14-M16-M17-COMPLETION.md
/home/ccuser/the-50-dollar-app/CSRF_IMPLEMENTATION.md
/home/ccuser/the-50-dollar-app/AI-PROFILE-BUILDER.md
/home/ccuser/the-50-dollar-app/AUDIT-C3-COMPLETION.md
/home/ccuser/the-50-dollar-app/AUDIT-C4-COMPLETION.md


Perfect! Now let me search for the actual API endpoints to confirm all the files that call these endpoints:


Found 1 file
/home/ccuser/the-50-dollar-app/src/app/(authenticated)/worker/profile/build/page.tsx


Excellent! Let me verify the contractor post-job page as well since it showed up in the grep results:


     1→"use client";
     2→
     3→import { useState } from "react";
     4→import { useRouter } from "next/navigation";
     5→import Link from "next/link";
     6→import { createClient } from "@/lib/supabase/client";
     7→import { Button } from "@/components/ui/button";
     8→import { Card, CardContent } from "@/components/ui/card";
     9→import { Input } from "@/components/ui/input";
    10→import { Label } from "@/components/ui/label";
    11→import { Badge } from "@/components/ui/badge";
    12→import {
    13→  ArrowLeft,
    14→  ArrowRight,
    15→  Loader2,
    16→  Sparkles,
    17→  Check,
    18→  MapPin,
    19→  Calendar,
    20→  Users,
    21→  DollarSign,
    22→  HardHat,
    23→  Wrench,
    24→  Hammer,
    25→  Blocks,
    26→  Fence,
    27→  CircleDot,
    28→  Shovel,
    29→} from "lucide-react";
    30→import { jsonToPostgresPoint } from "@/lib/location-utils";
    31→import { csrfFetch } from "@/lib/csrf-client";
    32→
    33→const TRADE_CATEGORIES = [
    34→  { id: "steel-fixer", label: "Steel Fixer", icon: Wrench, color: "bg-blue-500/10 border-blue-500/30 text-blue-700" },
    35→  { id: "formworker", label: "Formworker", icon: Blocks, color: "bg-amber-500/10 border-amber-500/30 text-amber-700" },
    36→  { id: "labourer", label: "Labourer", icon: Shovel, color: "bg-green-500/10 border-green-500/30 text-green-700" },
    37→  { id: "concreter", label: "Concreter", icon: CircleDot, color: "bg-gray-500/10 border-gray-500/30 text-gray-700" },
    38→  { id: "carpenter", label: "Carpenter", icon: Hammer, color: "bg-orange-500/10 border-orange-500/30 text-orange-700" },
    39→  { id: "scaffolder", label: "Scaffolder", icon: Fence, color: "bg-purple-500/10 border-purple-500/30 text-purple-700" },
    40→  { id: "other", label: "Other", icon: HardHat, color: "bg-muted border-border text-foreground" },
    41→];
    42→
    43→const AI_SUGGESTIONS: Record<string, { title: string; description: string; certs: string[]; rateMin: number; rateMax: number; rateType: "hourly" | "daily" }> = {
    44→  "steel-fixer": {
    45→    title: "Steel Fixer Needed",
    46→    description: "Experienced steel fixer required for commercial construction project. Must be able to read drawings, tie rebar, and work with mesh. PPE required on site at all times. Early starts — 6:30am.",
    47→    certs: ["White Card", "Working at Heights"],
    48→    rateMin: 45,
    49→    rateMax: 65,
    50→    rateType: "hourly",

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


Let me check if this file calls the AI endpoints by searching for the relevant API calls:


134:      const res = await csrfFetch("/api/ai/generate-job", {


Perfect! Now I have all the information. Let me compile the complete findings:

## Summary

I found the worker profile build page and identified all files calling AI endpoints. Here are the results:

### Main Build Page (Primary Target)
- **Full Path:** `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/worker/profile/build/page.tsx`
- This is the worker profile build page that calls the AI endpoints
- Calls made:
  - `/api/ai/generate-profile` (line 141)
  - `/api/ai/parse-profile-voice` (line 219)
  - `/api/ai/transcribe-voice` (line 200)

### Related Files Calling AI Endpoints
1. **Contractor Job Post Page:** `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/contractor/post-job/page.tsx`
   - Calls `/api/ai/generate-job`

### API Endpoint Routes
The corresponding backend API endpoints are located at:
- `/home/ccuser/the-50-dollar-app/src/app/api/ai/generate-profile/route.ts`
- `/home/ccuser/the-50-dollar-app/src/app/api/ai/parse-profile-voice/route.ts`
- `/home/ccuser/the-50-dollar-app/src/app/api/ai/transcribe-voice/route.ts`
- `/home/ccuser/the-50-dollar-app/src/app/api/ai/generate-job/route.ts`

### Profile Page Related Files
Additional profile-related pages in the worker directory:
- `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/worker/profile/page.tsx`
- `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/worker/profile/error.tsx`
- `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/worker/profile/build/loading.tsx`
- `/home/ccuser/the-50-dollar-app/src/app/(authenticated)/worker/profile/build/not-found.tsx`