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

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

   316→    );
   317→  }
   318→
   319→  return (
   320→

   321→      {/* Header */}
   322→
   323→

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

   328→
   329→
   330→
   331→        {/* Progress bar */}
   332→

   333→          {Array.from({ length: totalSteps }, (_, i) => (
   334→            <div
   335→              key={i}
   336→              className={`h-1.5 flex-1 rounded-full transition-colors ${
   337→                i <= currentStep - (isAuthenticated ? 1 : 0)
   338→                  ? "bg-primary"
   339→                  : "bg-muted"
   340→              }`}
   341→            />
   342→          ))}
   343→

   344→
   345→        {/* Step: Auth */}
   346→        {step === "auth" && (
   347→

   348→

   349→

   350→                <Building2 className="h-5 w-5" />
   351→

   352→

   353→
Create your account

   354→

   355→                  Quick signup to get started
   356→

   357→

   358→

   359→
   360→            <form onSubmit={handleAuth} className="mt-6 space-y-4">
   361→

   362→                Email
   363→                <Input
   364→                  id="email"
   365→                  type="email"
   366→                  placeholder="you@company.com.au"
   367→                  value={email}
   368→                  onChange={(e) => setEmail(e.target.value)}
   369→                  required
   370→                  autoComplete="email"
   371→                  className="h-12 text-base"
   372→                />
   373→

   374→

   375→                Password
   376→                <Input
   377→                  id="password"
   378→                  type="password"
   379→                  placeholder="Create a password"
   380→                  value={password}
   381→                  onChange={(e) => setPassword(e.target.value)}
   382→                  required
   383→                  minLength={6}
   384→                  autoComplete="new-password"
   385→                  className="h-12 text-base"
   386→                />
   387→

   388→
   389→              <Button
   390→                type="submit"
   391→                className="h-12 w-full gap-2 text-base font-semibold"
   392→                disabled={authLoading}
   393→              >
   394→                {authLoading ? (
   395→                  <>
   396→                    <Loader2 className="h-5 w-5 animate-spin" />
   397→                    Creating account...
   398→                  </>
   399→                ) : (
   400→                  <>
   401→                    Continue
   402→                    <ArrowRight className="h-5 w-5" />
   403→                  </>
   404→                )}
   405→              </Button>
   406→            </form>
   407→
   408→

   409→              Already have an account?{" "}
   410→              <Link
   411→                href="/auth/login"
   412→                className="text-primary underline hover:no-underline"
   413→              >
   414→                Sign in
   415→              </Link>
   416→

   417→

   418→        )}
   419→
   420→        {/* Step: Enter company name/ABN */}
   421→        {step === "company" && (
   422→

   423→

   424→

   425→                <Search className="h-5 w-5" />
   426→

   427→

   428→
Your company

   429→

   430→                  We'll verify it via ABR
   431→

   432→

   433→

   434→
   435→            <form onSubmit={handleCompanySearch} className="mt-6 space-y-4">
   436→

   437→                ABN
   438→                <Input
   439→                  id="company"
   440→                  type="text"
   441→                  placeholder="Enter your 11-digit ABN"
   442→                  value={companyInput}
   443→                  onChange={(e) => setCompanyInput(e.target.value)}
   444→                  required
   445→                  autoFocus
   446→                  className="h-12 text-base"
   447→                />
   448→

   449→                  Enter your 11-digit ABN for verification via the Australian Business Register.
   450→

   451→

   452→
   453→              <Button
   454→                type="submit"
   455→                className="h-12 w-full gap-2 text-base font-semibold"
   456→              >
   457→                <Search className="h-5 w-5" />
   458→                Verify my ABN
   459→              </Button>
   460→            </form>
   461→
   462→            {!isAuthenticated && (
   463→              <button
   464→                onClick={() => setStep("auth")}
   465→                className="mt-4 flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground"
   466→              >
   467→                <ArrowLeft className="h-4 w-4" />
   468→                Back
   469→              </button>
   470→            )}
   471→

   472→        )}
   473→
   474→        {/* Step: ABR Verification */}
   475→        {step === "researching" && (
   476→

   477→

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

   480→
Verifying your business

   481→

   482→              Checking ABR for your business details...
   483→

   484→
   485→

   486→              <ResearchStep label="Checking ABR..." />
   487→              <ResearchStep label="Verifying company..." />
   488→              <ResearchStep label="Done" />
   489→

   490→

   491→        )}
   492→
   493→        {/* Step: Review profile */}
   494→        {step === "review" && companyData && (
   495→

   496→

   497→

   498→                <CheckCircle2 className="h-5 w-5" />
   499→

   500→

   501→
Confirm your profile

   502→

   503→                  Tap any field to edit
   504→

   505→

   506→

   507→
   508→            <Card className="mt-6">
   509→              <CardContent className="space-y-4 pt-6">
   510→                <EditableField
   511→                  icon={<Building2 className="h-4 w-4" />}
   512→                  label="Company name"
   513→                  value={companyData.name}
   514→                  fieldKey="name"
   515→                  editField={editField}
   516→                  setEditField={setEditField}
   517→                  onSave={(v) => updateCompanyField("name", v)}
   518→                  logoUrl={companyData.logoUrl}
   519→                />
   520→                <EditableField
   521→                  icon={<FileText className="h-4 w-4" />}
   522→                  label="ABN"
   523→                  value={companyData.abn}
   524→                  fieldKey="abn"
   525→                  editField={editField}
   526→                  setEditField={setEditField}
   527→                  onSave={(v) => updateCompanyField("abn", v)}
   528→                />
   529→                <EditableField
   530→                  icon={<MapPin className="h-4 w-4" />}
   531→                  label="Address"
   532→                  value={companyData.address}
   533→                  fieldKey="address"
   534→                  editField={editField}
   535→                  setEditField={setEditField}
   536→                  onSave={(v) => updateCompanyField("address", v)}
   537→                />
   538→                <EditableField
   539→                  icon={<Building2 className="h-4 w-4" />}
   540→                  label="Industry"
   541→                  value={companyData.industry}
   542→                  fieldKey="industry"
   543→                  editField={editField}
   544→                  setEditField={setEditField}
   545→                  onSave={(v) => updateCompanyField("industry", v)}
   546→                />
   547→                <EditableField
   548→                  icon={<Users className="h-4 w-4" />}
   549→                  label="Company size"
   550→                  value={companyData.size}
   551→                  fieldKey="size"
   552→                  editField={editField}
   553→                  setEditField={setEditField}
   554→                  onSave={(v) => updateCompanyField("size", v)}
   555→                />
   556→                <EditableField
   557→                  icon={<Globe className="h-4 w-4" />}
   558→                  label="Website"
   559→                  value={companyData.website}
   560→                  fieldKey="website"
   561→                  editField={editField}
   562→                  setEditField={setEditField}
   563→                  onSave={(v) => updateCompanyField("website", v)}
   564→                />
   565→              </CardContent>
   566→            </Card>
   567→
   568→

   569→

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

   572→

   573→                    Verified via Australian Business Register
   574→

   575→

   576→                    Your ABN has been verified. Tap any field to correct it.
   577→

   578→

   579→

   580→

   581→
   582→            <Button
   583→              onClick={handleConfirm}
   584→              className="mt-6 h-14 w-full gap-2 text-base font-semibold"
   585→              disabled={saving}
   586→            >
   587→              {saving ? (
   588→                <>
   589→                  <Loader2 className="h-5 w-5 animate-spin" />
   590→                  Saving...
   591→                </>
   592→              ) : (
   593→                <>
   594→                  <CheckCircle2 className="h-5 w-5" />
   595→                  Looks good — create my profile
   596→                </>
   597→              )}
   598→            </Button>
   599→
   600→            <button
   601→              onClick={() => setStep("company")}
   602→              className="mt-4 flex w-full items-center justify-center gap-1 text-sm text-muted-foreground hover:text-foreground"
   603→            >
   604→              <ArrowLeft className="h-4 w-4" />
   605→              Search again
   606→            </button>
   607→

   608→        )}
   609→
   610→

   611→  );
   612→}
   613→
   614→function ResearchStep({ label }: { label: string }) {
   615→  return (
   616→

   617→      <Loader2 className="h-4 w-4 animate-spin text-primary" />
   618→      {label}
   619→

   620→  );
   621→}
   622→
   623→function EditableField({
   624→  icon,
   625→  label,
   626→  value,
   627→  fieldKey,
   628→  editField,
   629→  setEditField,
   630→  onSave,
   631→  logoUrl,
   632→}: {
   633→  icon: React.ReactNode;
   634→  label: string;
   635→  value: string;
   636→  fieldKey: string;
   637→  editField: string | null;
   638→  setEditField: (f: string | null) => void;
   639→  onSave: (value: string) => void;
   640→  logoUrl?: string | null;
   641→}) {
   642→  const [editValue, setEditValue] = useState(value);
   643→  const isEditing = editField === fieldKey;
   644→
   645→  if (isEditing) {
   646→    return (
   647→

   648→        {label}
   649→

   650→          {fieldKey === 'name' && logoUrl ? (
   651→

   652→              <img
   653→                src={logoUrl}
   654→                alt={`${value} logo`}
   655→                className="h-8 w-8 rounded-full object-cover"
   656→                onError={(e) => {
   657→                  e.currentTarget.style.display = 'none';
   658→                }}
   659→              />
   660→              <Input
   661→                value={editValue}
   662→                onChange={(e) => setEditValue(e.target.value)}
   663→                className="h-10 text-base"
   664→                autoFocus
   665→              />
   666→

   667→          ) : (
   668→            <Input
   669→              value={editValue}
   670→              onChange={(e) => setEditValue(e.target.value)}
   671→              className="h-10 text-base"
   672→              autoFocus
   673→            />
   674→          )}
   675→          <Button
   676→            size="sm"
   677→            onClick={() => onSave(editValue)}
   678→            className="h-10 shrink-0"
   679→          >
   680→            Save
   681→          </Button>
   682→

   683→

   684→    );
   685→  }
   686→
   687→  return (
   688→    <button
   689→      onClick={() => {
   690→        setEditValue(value);
   691→        setEditField(fieldKey);
   692→      }}
   693→      className="flex w-full items-start gap-3 rounded-lg p-2 text-left transition-colors hover:bg-muted"
   694→    >
   695→      {fieldKey === 'name' && logoUrl ? (
   696→

   697→          <img
   698→            src={logoUrl}
   699→            alt={`${value} logo`}
   700→            className="h-6 w-6 rounded-full object-cover"
   701→            onError={(e) => {
   702→              // If image fails to load, fall back to icon
   703→              e.currentTarget.style.display = 'none';
   704→            }}
   705→          />
   706→

   707→      ) : (
   708→
{icon}

   709→      )}
   710→

   711→
{label}

   712→
{value}

   713→

   714→      <Edit3 className="mt-1 h-3.5 w-3.5 shrink-0 text-muted-foreground" />
   715→    </button>
   716→  );
   717→}
   718→

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


1→import { redirect } from "next/navigation";
     2→import Link from "next/link";
     3→import { createClient } from "@/lib/supabase/server";
     4→import { Button } from "@/components/ui/button";
     5→import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
     6→import { Badge } from "@/components/ui/badge";
     7→import {
     8→  Building2,
     9→  MapPin,
    10→  Globe,
    11→  Users,
    12→  Edit3,
    13→  Star,
    14→  ArrowLeft,
    15→} from "lucide-react";
    16→import { BottomNavWrapper } from "@/components/bottom-nav-wrapper";
    17→import { AvatarUpload } from "@/components/avatar-upload";
    18→import { RatingsSummary } from "@/components/ratings-summary";
    19→import { RatingsList } from "@/components/ratings-list";
    20→
    21→export default async function CompanyProfilePage() {
    22→  const supabase = await createClient();
    23→
    24→  const {
    25→    data: { user },
    26→  } = await supabase.auth.getUser();
    27→
    28→  if (!user) {
    29→    redirect("/auth/login");
    30→  }
    31→
    32→  // Fetch company profile
    33→  const { data: company } = await supabase
    34→    .from("companies")
    35→    .select("*")
    36→    .eq("profile_id", user.id)
    37→    .single();
    38→
    39→  if (!company) {
    40→    redirect("/contractor/signup");
    41→  }
    42→
    43→  // Fetch ratings with profile information
    44→  const { data: ratings } = await supabase
    45→    .from("ratings")
    46→    .select(`
    47→      *,
    48→      from_profile:profiles!ratings_from_id_fkey(
    49→        id,
    50→        name,
    51→        avatar_url
    52→      )
    53→    `)
    54→    .eq("to_id", user.id)
    55→    .order("created_at", { ascending: false })
    56→    .limit(3); // Show latest 3 ratings
    57→
    58→  const avgRating = ratings && ratings.length > 0
    59→    ? ratings.reduce((sum, r) => sum + r.score, 0) / ratings.length
    60→    : 0;
    61→
    62→  const totalRatings = ratings?.length || 0;
    63→
    64→  return (
    65→

    66→
    67→

    68→          <Button asChild variant="ghost" size="icon">
    69→            <Link href="/dashboard">
    70→              <ArrowLeft className="h-5 w-5" />
    71→            </Link>
    72→          </Button>
    73→
Company Profile

    74→

    75→
    76→        {/* Company header */}
    77→

    78→          <AvatarUpload
    79→            currentUrl={company.logo_url}
    80→            userName={company.name}
    81→          />
    82→
    83→
{company.name}

    84→
    85→

    86→            {company.address && (
    87→
    88→                <MapPin className="h-3.5 w-3.5" />
    89→                {company.address}
    90→
    91→            )}
    92→            {company.industry && (
    93→
    94→                <Building2 className="h-3.5 w-3.5" />
    95→                {company.industry}
    96→
    97→            )}
    98→            {avgRating > 0 && (
    99→
   100→                ⭐ {avgRating.toFixed(1)} ({totalRatings})
   101→
   102→            )}
   103→

   104→
   105→          {company.size && (
   106→            <Badge variant="outline" className="mt-2">
   107→              <Users className="mr-1 h-3 w-3" />
   108→              {company.size}
   109→            </Badge>
   110→          )}
   111→

   112→
   113→        {/* Company info */}
   114→

   115→          {company.abn && (
   116→            <Card>
   117→              <CardContent className="pt-5">
   118→

   119→                  <Building2 className="h-4 w-4" />
   120→                  ABN
   121→

   122→
{company.abn}

   123→              </CardContent>
   124→            </Card>
   125→          )}
   126→
   127→          {company.website && (
   128→            <Card>
   129→              <CardContent className="pt-5">
   130→

   131→                  <Globe className="h-4 w-4" />
   132→                  Website
   133→

   134→
   140→                  {company.website}
   141→
   142→              </CardContent>
   143→            </Card>
   144→          )}
   145→
   146→          {company.ai_research && (
   147→            <Card>
   148→              <CardContent className="pt-5">
   149→

   150→                  <Building2 className="h-4 w-4" />
   151→                  About
   152→

   153→

   154→                  {(company.ai_research as { description?: string })?.description ||
   155→                   (company.ai_research as { summary?: string })?.summary ||
   156→                   "Company information available."}
   157→

   158→              </CardContent>
   159→            </Card>
   160→          )}
   161→

   162→
   163→        {/* Ratings */}
   164→        {(ratings && ratings.length > 0) && (
   165→

   166→            <RatingsSummary
   167→              averageRating={avgRating}
   168→              totalRatings={totalRatings}
   169→            />
   170→
   171→

   172→              <RatingsList
   173→                ratings={ratings}
   174→                title="Recent Reviews"
   175→              />
   176→
   177→              {totalRatings > 3 && (
   178→                <Button asChild variant="outline" className="w-full mt-4">
   179→                  <Link href={`/ratings/${user.id}`}>
   180→                    <Star className="h-4 w-4 mr-2" />
   181→                    View All Reviews ({totalRatings})
   182→                  </Link>
   183→                </Button>
   184→              )}
   185→

   186→

   187→        )}
   188→
   189→        {/* Edit button */}
   190→

   191→          <Button asChild variant="outline" className="w-full h-12 text-base gap-2">
   192→            <Link href="/contractor/signup">
   193→              <Edit3 className="h-5 w-5" />
   194→              Edit Profile
   195→            </Link>
   196→          </Button>
   197→

   198→
   199→
   200→      <BottomNavWrapper userType="contractor" />
   201→

   202→  );
   203→}

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


1→'use client';
     2→
     3→import { useEffect, useState } from 'react';
     4→import { useSearchParams, useRouter } from 'next/navigation';
     5→import { logger } from '@/lib/logger';
     6→import Link from 'next/link';
     7→import { CheckCircle2, Loader2, AlertCircle } from 'lucide-react';
     8→import { Button } from '@/components/ui/button';
     9→import { Card, CardContent } from '@/components/ui/card';
    10→import { createClient } from '@/lib/supabase/client';
    11→
    12→export default function PaymentSuccessPage() {
    13→  const searchParams = useSearchParams();
    14→  const router = useRouter();
    15→  const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
    16→  const [message, setMessage] = useState('Confirming your payment...');
    17→  const supabase = createClient();
    18→
    19→  useEffect(() => {
    20→    const paymentIntent = searchParams.get('payment_intent');
    21→    const paymentIntentClientSecret = searchParams.get('payment_intent_client_secret');
    22→
    23→    if (!paymentIntent || !paymentIntentClientSecret) {
    24→      setStatus('error');
    25→      setMessage('Invalid payment confirmation');
    26→      return;
    27→    }
    28→
    29→    // Check payment status
    30→    async function checkPayment() {
    31→      try {
    32→        // Wait a moment for webhook to process
    33→        await new Promise(resolve => setTimeout(resolve, 2000));
    34→
    35→        // Verify payment intent status with Stripe via API
    36→        const response = await fetch(`/api/payments/verify?payment_intent=${paymentIntent}`);
    37→
    38→        if (!response.ok) {
    39→          const error = await response.json();
    40→          setStatus('error');
    41→          setMessage(error.error || 'Failed to verify payment status');
    42→          return;
    43→        }
    44→
    45→        const { status: stripeStatus } = await response.json();
    46→
    47→        // Only show success if payment is actually succeeded
    48→        if (stripeStatus === 'succeeded') {
    49→          // Double-check our database has recorded it
    50→          const { data: payment } = await supabase
    51→            .from('payments')
    52→            .select('status, match_id')
    53→            .eq('stripe_payment_id', paymentIntent)
    54→            .single();
    55→
    56→          if (payment?.status === 'charged') {
    57→            setStatus('success');
    58→            setMessage('Your payment was successful and the worker has been notified!');
    59→          } else {
    60→            // Stripe succeeded but webhook hasn't processed yet
    61→            setStatus('success');
    62→            setMessage('Payment successful! Processing your hire...');
    63→          }
    64→        } else if (stripeStatus === 'processing') {
    65→          setStatus('success');
    66→          setMessage('Payment is being processed. You will be notified once complete.');
    67→        } else if (stripeStatus === 'requires_payment_method') {
    68→          setStatus('error');
    69→          setMessage('Payment failed. Please try again with a different payment method.');
    70→        } else if (stripeStatus === 'canceled') {
    71→          setStatus('error');
    72→          setMessage('Payment was canceled. Please try again.');
    73→        } else {
    74→          setStatus('error');
    75→          setMessage(`Payment status: ${stripeStatus}. Please contact support if you were charged.`);
    76→        }
    77→      } catch (error) {
    78→        logger.error('Error checking payment:', error);
    79→        setStatus('error');
    80→        setMessage('Unable to verify payment status. Please contact support if you were charged.');
    81→      }
    82→    }
    83→
    84→    checkPayment();
    85→  }, [searchParams, supabase]);
    86→
    87→  return (
    88→

    89→      <Card className="w-full max-w-md">
    90→        <CardContent className="pt-8 pb-6 text-center">
    91→          {status === 'loading' && (
    92→            <>
    93→              <Loader2 className="mx-auto h-12 w-12 animate-spin text-primary" />
    94→
Confirming Payment

    95→
{message}

    96→            </>
    97→          )}
    98→
    99→          {status === 'success' && (
   100→            <>
   101→

   102→                <CheckCircle2 className="h-10 w-10 text-green-600" />
   103→

   104→
Payment Successful!

   105→
{message}

   106→

   107→                <Button asChild className="w-full h-12 text-base">
   108→                  <Link href="/contractor/matches">Back to Matches</Link>
   109→                </Button>
   110→                <Button asChild variant="outline" className="w-full h-12 text-base">
   111→                  <Link href="/dashboard">Go to Dashboard</Link>
   112→                </Button>
   113→

   114→            </>
   115→          )}
   116→
   117→          {status === 'error' && (
   118→            <>
   119→

   120→                <AlertCircle className="h-10 w-10 text-destructive" />
   121→

   122→
Payment Error

   123→
{message}

   124→

   125→                <Button asChild variant="outline" className="w-full h-12 text-base">
   126→                  <Link href="/contractor/matches">Back to Matches</Link>
   127→                </Button>
   128→

   129→            </>
   130→          )}
   131→        </CardContent>
   132→      </Card>
   133→

   134→  );
   135→}
   136→

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


1→"use client";
     2→
     3→import { useState, useEffect } from "react";
     4→import { useRouter } from "next/navigation";
     5→import Link from "next/link";
     6→import Image from "next/image";
     7→import { Loader2, HardHat } from "lucide-react";
     8→import { useWorkerAuth } from "./hooks/useWorkerAuth";
     9→import { useWorkerProfile } from "./hooks/useWorkerProfile";
    10→import { useVoiceProcessing } from "./hooks/useVoiceProcessing";
    11→import { AuthStep } from "./components/AuthStep";
    12→import { MethodStep } from "./components/MethodStep";
    13→import { VoiceStep } from "./components/VoiceStep";
    14→import { TradeStep } from "./components/TradeStep";
    15→import { ExperienceStep } from "./components/ExperienceStep";
    16→import { LocationStep } from "./components/LocationStep";
    17→import { AvailabilityStep } from "./components/AvailabilityStep";
    18→import { CertificationsStep } from "./components/CertificationsStep";
    19→import { ReviewStep } from "./components/ReviewStep";
    20→import { ProgressBar } from "./components/ProgressBar";
    21→import type { Step } from "./types";
    22→
    23→export default function WorkerSignupPage() {
    24→  const [step, setStep] = useState<Step>("auth");
    25→
    26→  const {
    27→    isAuthenticated,
    28→    loading: authLoading,
    29→    email,
    30→    setEmail,
    31→    password,
    32→    setPassword,
    33→    authError,
    34→    authLoading: isAuthLoading,
    35→    handleAuth,
    36→  } = useWorkerAuth();
    37→
    38→  const {
    39→    selectedTrades,
    40→    setSelectedTrades,
    41→    experience,
    42→    setExperience,
    43→    suburb,
    44→    setSuburb,
    45→    availability,
    46→    setAvailability,
    47→    certifications,
    48→    setCertifications,
    49→    saving,
    50→    toggleTrade,
    51→    toggleCert,
    52→    saveWorkerProfile,
    53→    workerData,
    54→  } = useWorkerProfile();
    55→
    56→  const {
    57→    voiceState,
    58→    handleVoiceComplete,
    59→  } = useVoiceProcessing(setSelectedTrades, setExperience, setSuburb, setAvailability, setStep);
    60→
    61→  useEffect(() => {
    62→    if (isAuthenticated && step === "auth") {
    63→      setStep("method");
    64→    }
    65→  }, [isAuthenticated, step]);
    66→
    67→  const stepOrder: Step[] = ["auth", "method", "voice", "trade", "experience", "location", "availability", "certs", "review"];
    68→  const progressSteps = ["auth", "method", "trade", "experience", "location", "availability", "certs", "review"]; // Exclude voice from progress bar
    69→  const activeSteps = isAuthenticated ? stepOrder.slice(1) : stepOrder;
    70→  const activeProgressSteps = isAuthenticated ? progressSteps.slice(1) : progressSteps;
    71→  const currentIndex = activeProgressSteps.indexOf(step === "voice" ? "method" : step); // Map voice to method for progress
    72→  const totalSteps = activeProgressSteps.length;
    73→
    74→  if (authLoading) {
    75→    return (
    76→

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

    79→    );
    80→  }
    81→
    82→  return (
    83→

    84→      {/* Header */}
    85→
    86→

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

    91→
    92→
    93→
    94→        {/* Progress bar */}
    95→        <ProgressBar currentStep={currentIndex} totalSteps={totalSteps} />
    96→
    97→        {/* Step: Auth */}
    98→        {step === "auth" && (
    99→          <AuthStep
   100→            email={email}
   101→            setEmail={setEmail}
   102→            password={password}
   103→            setPassword={setPassword}
   104→            authError={authError}
   105→            authLoading={isAuthLoading}
   106→            onSubmit={handleAuth}
   107→          />
   108→        )}
   109→
   110→        {/* Step: Choose method */}
   111→        {step === "method" && (
   112→          <MethodStep
   113→            onTypeSelect={() => setStep("trade")}
   114→            onVoiceSelect={() => setStep("voice")}
   115→          />
   116→        )}
   117→
   118→        {/* Step: Voice recording */}
   119→        {step === "voice" && (
   120→          <VoiceStep
   121→            voiceProcessing={voiceState.processing}
   122→            voiceStatus={voiceState.status}
   123→            voiceError={voiceState.error}
   124→            onRecordingComplete={handleVoiceComplete}
   125→            onBack={() => setStep("method")}
   126→          />
   127→        )}
   128→
   129→        {/* Step: Select trade */}
   130→        {step === "trade" && (
   131→          <TradeStep
   132→            selectedTrades={selectedTrades}
   133→            onTradeToggle={toggleTrade}
   134→            onContinue={() => setStep("experience")}
   135→            onBack={() => setStep(isAuthenticated ? "method" : "auth")}
   136→          />
   137→        )}
   138→
   139→        {/* Step: Experience */}
   140→        {step === "experience" && (
   141→          <ExperienceStep
   142→            experience={experience}
   143→            onExperienceSelect={(exp) => {
   144→              setExperience(exp);
   145→              setStep("location");
   146→            }}
   147→            onBack={() => setStep("trade")}
   148→          />
   149→        )}
   150→
   151→        {/* Step: Location */}
   152→        {step === "location" && (
   153→          <LocationStep
   154→            suburb={suburb}
   155→            setSuburb={setSuburb}
   156→            onContinue={() => setStep("availability")}
   157→            onBack={() => setStep("experience")}
   158→          />
   159→        )}
   160→
   161→        {/* Step: Availability */}
   162→        {step === "availability" && (
   163→          <AvailabilityStep
   164→            availability={availability}
   165→            onAvailabilitySelect={(avail) => {
   166→              setAvailability(avail);
   167→              setStep("certs");
   168→            }}
   169→            onBack={() => setStep("location")}
   170→          />
   171→        )}
   172→
   173→        {/* Step: Certifications */}
   174→        {step === "certs" && (
   175→          <CertificationsStep
   176→            certifications={certifications}
   177→            onCertToggle={toggleCert}
   178→            onContinue={() => setStep("review")}
   179→            onBack={() => setStep("availability")}
   180→          />
   181→        )}
   182→
   183→        {/* Step: Review */}
   184→        {step === "review" && workerData && (
   185→          <ReviewStep
   186→            workerData={workerData}
   187→            onEdit={setStep}
   188→            onConfirm={saveWorkerProfile}
   189→            saving={saving}
   190→            onBack={() => setStep("certs")}
   191→          />
   192→        )}
   193→
   194→

   195→  );
   196→}

<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 { logger } from "@/lib/logger";
     6→import { createClient } from "@/lib/supabase/client";
     7→import { Card, CardContent } from "@/components/ui/card";
     8→import { Badge } from "@/components/ui/badge";
     9→import { Button } from "@/components/ui/button";
    10→import { BottomNav } from "@/components/bottom-nav";
    11→import {
    12→  MapPin,
    13→  Calendar,
    14→  DollarSign,
    15→  Loader2,
    16→  Search,
    17→  HardHat,
    18→  Check,
    19→  X,
    20→} from "lucide-react";
    21→import type { Job } from "@/lib/types";
    22→import { postgresPointToJson } from "@/lib/location-utils";
    23→import toast from "react-hot-toast";
    24→import { TRADE_CATEGORIES } from "@/lib/constants";
    25→
    26→const TRADE_FILTERS = [
    27→  "All",
    28→  ...TRADE_CATEGORIES.map(cat => cat.label),
    29→];
    30→
    31→const TRADE_ID_TO_LABEL: Record<string, string> = TRADE_CATEGORIES.reduce((acc, cat) => {
    32→  acc[cat.id] = cat.label;
    33→  return acc;
    34→}, {} as Record<string, string>);
    35→
    36→function tradeLabelFromId(id: string): string {
    37→  return TRADE_ID_TO_LABEL[id] || id;
    38→}
    39→
    40→interface SwipeableJobCardProps {
    41→  job: Job;
    42→  hasApplied: boolean;
    43→  onApply: (job: Job) => void;
    44→  onClick: (job: Job) => void;
    45→}
    46→
    47→function SwipeableJobCard({ job, hasApplied, onApply, onClick }: SwipeableJobCardProps) {
    48→  const [swipeX, setSwipeX] = useState(0);
    49→  const [swiping, setSwiping] = useState(false);
    50→  const startX = useRef(0);
    51→  const currentX = useRef(0);
    52→
    53→  const handleTouchStart = (e: React.TouchEvent) => {
    54→    if (hasApplied) return;
    55→    startX.current = e.touches[0].clientX;
    56→    setSwiping(true);
    57→  };
    58→
    59→  const handleTouchMove = (e: React.TouchEvent) => {
    60→    if (!swiping || hasApplied) return;
    61→    currentX.current = e.touches[0].clientX;
    62→    const diff = currentX.current - startX.current;
    63→    // Only allow right swipe (positive diff)
    64→    if (diff > 0) {
    65→      setSwipeX(Math.min(diff, 150));
    66→    }
    67→  };
    68→
    69→  const handleTouchEnd = () => {
    70→    if (!swiping || hasApplied) return;
    71→    setSwiping(false);
    72→
    73→    // If swiped more than 100px, apply
    74→    if (swipeX > 100) {
    75→      onApply(job);
    76→    }
    77→
    78→    // Reset
    79→    setSwipeX(0);
    80→  };
    81→
    82→  const swipeProgress = Math.min(swipeX / 100, 1);
    83→  const backgroundColor = hasApplied
    84→    ? "transparent"
    85→    : `rgba(34, 197, 94, ${swipeProgress * 0.1})`;
    86→
    87→  return (
    88→

    89→      {/* Swipe indicator background */}
    90→      {!hasApplied && swipeX > 0 && (
    91→

    98→          <Check className="h-8 w-8 text-green-600" />
    99→
   100→            {swipeProgress >= 1 ? "Release to apply" : "Swipe to apply"}
   101→
   102→

   103→      )}
   104→
   105→      <Card
   106→        className="cursor-pointer active:scale-[0.98] transition-transform touch-pan-y"
   107→        style={{
   108→          transform: `translateX(${swipeX}px)`,
   109→          transition: swiping ? 'none' : 'transform 0.2s ease-out',
   110→          backgroundColor,
   111→        }}
   112→        onTouchStart={handleTouchStart}
   113→        onTouchMove={handleTouchMove}
   114→        onTouchEnd={handleTouchEnd}
   115→        onClick={() => !swiping && onClick(job)}
   116→      >
   117→        <CardContent className="p-5">
   118→

   119→

   120→

   121→                <Badge variant="secondary" className="text-sm shrink-0 py-1 px-3">
   122→                  {tradeLabelFromId(job.trade)}
   123→                </Badge>
   124→                {hasApplied && (
   125→                  <Badge className="text-sm bg-green-600 shrink-0 py-1 px-3">
   126→                    <Check className="h-3.5 w-3.5 mr-1" />
   127→                    Applied
   128→                  </Badge>
   129→                )}
   130→

   131→
{job.title}

   132→

   133→

   134→
   135→

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

   169→        </CardContent>
   170→      </Card>
   171→

   172→  );
   173→}
   174→
   175→export default function WorkerJobsPage() {
   176→  const supabase = createClient();
   177→  const [jobs, setJobs] = useState<Job[]>([]);
   178→  const [loading, setLoading] = useState(true);
   179→  const [activeTrade, setActiveTrade] = useState("All");
   180→  const [selectedJob, setSelectedJob] = useState<Job | null>(null);
   181→  const [applying, setApplying] = useState(false);
   182→  const [appliedJobs, setAppliedJobs] = useState<Set<string>>(new Set());
   183→  const [userId, setUserId] = useState<string | null>(null);
   184→  const [currentPage, setCurrentPage] = useState(1);
   185→  const [totalPages, setTotalPages] = useState(1);
   186→  const [totalCount, setTotalCount] = useState(0);
   187→  const ITEMS_PER_PAGE = 10;
   188→
   189→  useEffect(() => {
   190→    async function load() {
   191→      const { data: { user } } = await supabase.auth.getUser();
   192→      if (user) setUserId(user.id);
   193→
   194→      // Fetch active jobs with pagination
   195→      const from = (currentPage - 1) * ITEMS_PER_PAGE;
   196→      const to = from + ITEMS_PER_PAGE - 1;
   197→
   198→      // Get total count first
   199→      const { count } = await supabase
   200→        .from("jobs")
   201→        .select("*", { count: "exact", head: true })
   202→        .eq("status", "active");
   203→
   204→      if (count) {
   205→        setTotalCount(count);
   206→        setTotalPages(Math.ceil(count / ITEMS_PER_PAGE));
   207→      }
   208→
   209→      // Fetch paginated jobs
   210→      const { data } = await supabase
   211→        .from("jobs")
   212→        .select("id, title, trade, suburb, rate_min, rate_max, rate_type, start_date, workers_needed, status, created_at, location")
   213→        .eq("status", "active")
   214→        .order("created_at", { ascending: false })
   215→        .range(from, to);
   216→
   217→      if (data) {
   218→        // Convert PostgreSQL point to JSON format
   219→        const convertedJobs = data.map((job: any) => ({
   220→          ...job,
   221→          location: postgresPointToJson(job.location)
   222→        }));
   223→        setJobs(convertedJobs);
   224→      }
   225→
   226→      // Fetch already-applied jobs
   227→      if (user) {
   228→        const { data: matches } = await supabase
   229→          .from("matches")
   230→          .select("job_id")
   231→          .eq("worker_id", user.id);
   232→
   233→        if (matches) {
   234→          setAppliedJobs(new Set(matches.map((m: { job_id: string }) => m.job_id)));
   235→        }
   236→      }
   237→
   238→      setLoading(false);
   239→    }
   240→    load();
   241→  }, [supabase, currentPage]);
   242→
   243→  const filteredJobs =
   244→    activeTrade === "All"
   245→      ? jobs
   246→      : jobs.filter(
   247→          (j) => tradeLabelFromId(j.trade).toLowerCase() === activeTrade.toLowerCase()
   248→        );
   249→
   250→  async function handleApply(job: Job) {
   251→    if (!userId || appliedJobs.has(job.id)) return;
   252→    setApplying(true);
   253→
   254→    // Haptic feedback (if supported)
   255→    if (navigator.vibrate) {
   256→      navigator.vibrate(50);
   257→    }
   258→
   259→    const { error } = await supabase.from("matches").insert({
   260→      job_id: job.id,
   261→      worker_id: userId,
   262→      status: "applied",
   263→      contractor_action: "pending",
   264→      worker_action: "accepted",
   265→    });
   266→
   267→    if (error) {
   268→      logger.error("Failed to apply for job:", error);
   269→      toast.error("Failed to apply for job. Please try again.");
   270→      setApplying(false);
   271→      return;
   272→    }
   273→
   274→    setAppliedJobs((prev) => new Set(prev).add(job.id));
   275→    toast.success("Application submitted!");
   276→    setApplying(false);
   277→    setSelectedJob(null);
   278→  }
   279→
   280→  if (loading) {
   281→    return (
   282→

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

   285→    );
   286→  }
   287→
   288→  return (
   289→

   290→
   291→        {/* Trade filters — horizontal scroll */}
   292→

   293→          {TRADE_FILTERS.map((trade) => (
   294→            <button
   295→              key={trade}
   296→              onClick={() => setActiveTrade(trade)}
   297→              className={`shrink-0 rounded-full px-5 py-3 text-base font-semibold transition-all active:scale-95 min-h-[48px] ${
   298→                activeTrade === trade
   299→                  ? "bg-primary text-primary-foreground"
   300→                  : "bg-muted text-muted-foreground"
   301→              }`}
   302→            >
   303→              {trade}
   304→            </button>
   305→          ))}
   306→

   307→
   308→        {/* Swipe hint */}
   309→        {filteredJobs.length > 0 && !loading && (
   310→

   311→

   312→              👉 Swipe right to quick apply • Tap to view details
   313→

   314→

   315→        )}
   316→
   317→        {/* Job list */}
   318→        {filteredJobs.length === 0 ? (
   319→

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

   322→

   323→              {activeTrade === "All"
   324→                ? "No jobs posted yet. Check back soon."
   325→                : `No ${activeTrade} jobs right now. Try another trade.`}
   326→

   327→

   328→        ) : (
   329→          <>
   330→

   331→              {filteredJobs.map((job) => (
   332→                <SwipeableJobCard
   333→                  key={job.id}
   334→                  job={job}
   335→                  hasApplied={appliedJobs.has(job.id)}
   336→                  onApply={handleApply}
   337→                  onClick={setSelectedJob}
   338→                />
   339→              ))}
   340→

   341→
   342→            {/* Pagination Controls */}
   343→            {totalPages > 1 && (
   344→

   345→                <Button
   346→                  variant="outline"
   347→                  size="sm"
   348→                  onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}
   349→                  disabled={currentPage === 1}
   350→                >
   351→                  Previous
   352→                </Button>
   353→
   354→

   355→                  {Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
   356→                    let pageNum;
   357→                    if (totalPages <= 5) {
   358→                      pageNum = i + 1;
   359→                    } else if (currentPage <= 3) {
   360→                      pageNum = i + 1;
   361→                    } else if (currentPage >= totalPages - 2) {
   362→                      pageNum = totalPages - 4 + i;
   363→                    } else {
   364→                      pageNum = currentPage - 2 + i;
   365→                    }
   366→
   367→                    return (
   368→                      <Button
   369→                        key={pageNum}
   370→                        variant={currentPage === pageNum ? "default" : "outline"}
   371→                        size="sm"
   372→                        onClick={() => setCurrentPage(pageNum)}
   373→                        className="min-w-[36px]"
   374→                      >
   375→                        {pageNum}
   376→                      </Button>
   377→                    );
   378→                  })}
   379→

   380→
   381→                <Button
   382→                  variant="outline"
   383→                  size="sm"
   384→                  onClick={() => setCurrentPage(prev => Math.min(totalPages, prev + 1))}
   385→                  disabled={currentPage === totalPages}
   386→                >
   387→                  Next
   388→                </Button>
   389→

   390→            )}
   391→          </>
   392→        )}
   393→
   394→
   395→      {/* Job Detail Sheet (bottom sheet style) */}
   396→      {selectedJob && (
   397→

   398→
 setSelectedJob(null)}
   401→          />
   402→

   403→            {/* Handle */}
   404→

   405→

   406→

   407→
   408→

   409→              <Badge variant="secondary" className="mb-3 text-sm py-1.5 px-3">
   410→                {tradeLabelFromId(selectedJob.trade)}
   411→              </Badge>
   412→
{selectedJob.title}

   413→
   414→

   415→                {selectedJob.suburb && (
   416→
   417→                    <MapPin className="h-5 w-5" />
   418→                    {selectedJob.site_address || selectedJob.suburb}
   419→
   420→                )}
   421→                {(selectedJob.rate_min || selectedJob.rate_max) && (
   422→
   423→                    <DollarSign className="h-5 w-5" />
   424→                    {selectedJob.rate_min && selectedJob.rate_max
   425→                      ? `$${selectedJob.rate_min}–$${selectedJob.rate_max}`
   426→                      : selectedJob.rate_min
   427→                      ? `From $${selectedJob.rate_min}`
   428→                      : `Up to $${selectedJob.rate_max}`}
   429→                    {selectedJob.rate_type === "hourly" ? "/hr" : "/day"}
   430→
   431→                )}
   432→

   433→
   434→

   435→                {selectedJob.start_date && (
   436→
   437→                    <Calendar className="h-5 w-5" />
   438→                    Starts{" "}
   439→                    {new Date(selectedJob.start_date).toLocaleDateString("en-AU", {
   440→                      weekday: "short",
   441→                      day: "numeric",
   442→                      month: "short",
   443→                    })}
   444→
   445→                )}
   446→                {selectedJob.duration && (
   447→                  Duration: {selectedJob.duration}
   448→                )}
   449→                {selectedJob.workers_needed > 1 && (
   450→
   451→                    <HardHat className="h-5 w-5" />
   452→                    {selectedJob.workers_needed} workers needed
   453→
   454→                )}
   455→

   456→
   457→              {selectedJob.description && (
   458→

   459→
About the Job

   460→

   461→                    {selectedJob.description}
   462→

   463→

   464→              )}
   465→
   466→              {selectedJob.certifications_required?.length > 0 && (
   467→

   468→
Required Certs

   469→

   470→                    {selectedJob.certifications_required.map((cert) => (
   471→                      <Badge key={cert} variant="outline" className="text-sm py-1.5 px-3">
   472→                        {cert}
   473→                      </Badge>
   474→                    ))}
   475→

   476→

   477→              )}
   478→

   479→
   480→            {/* Sticky Bottom CTA */}
   481→

   482→

   483→                {appliedJobs.has(selectedJob.id) ? (
   484→                  <Button disabled className="w-full h-16 text-lg font-semibold bg-green-600">
   485→                    <Check className="mr-2 h-6 w-6" />
   486→                    Already Applied
   487→                  </Button>
   488→                ) : (
   489→                  <Button
   490→                    onClick={() => handleApply(selectedJob)}
   491→                    className="w-full h-16 text-lg font-semibold"
   492→                    disabled={applying}
   493→                  >
   494→                    {applying ? (
   495→                      <>
   496→                        <Loader2 className="mr-2 h-6 w-6 animate-spin" />
   497→                        Applying...
   498→                      </>
   499→                    ) : (
   500→                      <>
   501→                        Apply with One Tap
   502→                        <Check className="ml-2 h-6 w-6" />
   503→                      </>
   504→                    )}
   505→                  </Button>
   506→                )}
   507→

   508→                  Always free for workers. No fees ever.
   509→

   510→

   511→

   512→

   513→

   514→      )}
   515→
   516→      <BottomNav userType="worker" />
   517→

   518→  );
   519→}
   520→

<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 worker profile pages, signup components, and shared components.


1→import { redirect } from "next/navigation";
     2→import Link from "next/link";
     3→import { createClient } from "@/lib/supabase/server";
     4→import { Button } from "@/components/ui/button";
     5→import { Card, CardContent } from "@/components/ui/card";
     6→import { Badge } from "@/components/ui/badge";
     7→import {
     8→  HardHat,
     9→  MapPin,
    10→  Clock,
    11→  Shield,
    12→  Zap,
    13→  Edit3,
    14→  Wrench,
    15→  Calendar,
    16→  Sparkles,
    17→  Camera,
    18→  Star,
    19→} from "lucide-react";
    20→import { BottomNavWrapper } from "@/components/bottom-nav-wrapper";
    21→import { AvatarUpload } from "@/components/avatar-upload";
    22→import { RatingsSummary } from "@/components/ratings-summary";
    23→import { RatingsList } from "@/components/ratings-list";
    24→
    25→export default async function WorkerProfilePage() {
    26→  const supabase = await createClient();
    27→
    28→  const {
    29→    data: { user },
    30→  } = await supabase.auth.getUser();
    31→
    32→  if (!user) {
    33→    redirect("/auth/login");
    34→  }
    35→
    36→  const { data: profile } = await supabase
    37→    .from("profiles")
    38→    .select("id, name, avatar_url, suburb, onboarding_completed")
    39→    .eq("id", user.id)
    40→    .single();
    41→
    42→  // If onboarding not completed, send to build
    43→  if (!profile?.onboarding_completed) {
    44→    redirect("/worker/profile/build");
    45→  }
    46→
    47→  const { data: workerProfile } = await supabase
    48→    .from("worker_profiles")
    49→    .select("profile_id, trades, experience_years, certifications, bio, ai_extracted, availability")
    50→    .eq("profile_id", user.id)
    51→    .single();
    52→
    53→  if (!workerProfile) {
    54→    redirect("/worker/profile/build");
    55→  }
    56→
    57→  const trades = workerProfile.trades || [];
    58→  const skills = (workerProfile.ai_extracted as { skills?: string[] })?.skills || [];
    59→  const certs = workerProfile.certifications || [];
    60→  const expLabel = getExperienceLabel(workerProfile.experience_years);
    61→
    62→  // Fetch ratings with profile information
    63→  const { data: ratings } = await supabase
    64→    .from("ratings")
    65→    .select(`
    66→      *,
    67→      from_profile:profiles!ratings_from_id_fkey(
    68→        id,
    69→        name,
    70→        avatar_url
    71→      )
    72→    `)
    73→    .eq("to_id", user.id)
    74→    .order("created_at", { ascending: false })
    75→    .limit(3); // Show latest 3 ratings
    76→
    77→  const avgRating = ratings && ratings.length > 0
    78→    ? (ratings.reduce((sum, r) => sum + r.score, 0) / ratings.length).toFixed(1)
    79→    : null;
    80→
    81→  const totalRatings = ratings?.length || 0;
    82→
    83→  return (
    84→

    85→
    86→        {/* Profile header */}
    87→

    88→          <AvatarUpload
    89→            currentUrl={profile.avatar_url}
    90→            userName={profile.name || user.email || ""}
    91→          />
    92→
    93→
{profile.name || "Worker"}

    94→
    95→

    96→            {trades.map((t: string) => (
    97→              <Badge key={t} variant="secondary" className="text-sm">
    98→                <Wrench className="mr-1 h-3 w-3" />
    99→                {t}
   100→              </Badge>
   101→            ))}
   102→

   103→
   104→

   105→            {profile.suburb && (
   106→
   107→                <MapPin className="h-3.5 w-3.5" />
   108→                {profile.suburb}
   109→
   110→            )}
   111→            {expLabel && (
   112→
   113→                <Clock className="h-3.5 w-3.5" />
   114→                {expLabel}
   115→
   116→            )}
   117→            {avgRating && (
   118→
   119→                ⭐ {avgRating} ({ratings!.length})
   120→
   121→            )}
   122→

   123→
   124→          {workerProfile.availability && (
   125→            <Badge variant="outline" className="mt-2">
   126→              <Calendar className="mr-1 h-3 w-3" />
   127→              {workerProfile.availability}
   128→            </Badge>
   129→          )}
   130→

   131→
   132→        {/* Bio */}
   133→        {workerProfile.bio && (
   134→          <Card className="mt-6">
   135→            <CardContent className="pt-5">
   136→

   137→                <Sparkles className="h-4 w-4" />
   138→                About me
   139→

   140→
{workerProfile.bio}

   141→            </CardContent>
   142→          </Card>
   143→        )}
   144→
   145→        {/* Skills */}
   146→        {skills.length > 0 && (
   147→          <Card className="mt-3">
   148→            <CardContent className="pt-5">
   149→

   150→                <Zap className="h-4 w-4" />
   151→                Skills
   152→

   153→

   154→                {skills.map((skill: string) => (
   155→                  <Badge key={skill} className="text-sm">
   156→                    {skill}
   157→                  </Badge>
   158→                ))}
   159→

   160→            </CardContent>
   161→          </Card>
   162→        )}
   163→
   164→        {/* Certifications */}
   165→        {certs.length > 0 && (
   166→          <Card className="mt-3">
   167→            <CardContent className="pt-5">
   168→

   169→                <Shield className="h-4 w-4" />
   170→                Certifications
   171→

   172→

   173→                {certs.map((cert: string) => (
   174→

   178→                    <Shield className="h-4 w-4 text-primary" />
   179→                    {cert}
   180→

   181→                ))}
   182→

   183→            </CardContent>
   184→          </Card>
   185→        )}
   186→
   187→        {/* Ratings */}
   188→        {(ratings && ratings.length > 0) && (
   189→

   190→            <RatingsSummary
   191→              averageRating={parseFloat(avgRating || "0")}
   192→              totalRatings={totalRatings}
   193→            />
   194→
   195→

   196→              <RatingsList
   197→                ratings={ratings}
   198→                title="Recent Reviews"
   199→              />
   200→
   201→              {totalRatings > 3 && (
   202→                <Button asChild variant="outline" className="w-full mt-4">
   203→                  <Link href={`/ratings/${user.id}`}>
   204→                    <Star className="h-4 w-4 mr-2" />
   205→                    View All Reviews ({totalRatings})
   206→                  </Link>
   207→                </Button>
   208→              )}
   209→

   210→

   211→        )}
   212→
   213→        {/* Edit button */}
   214→

   215→          <Button asChild variant="outline" className="w-full h-12 text-base gap-2">
   216→            <Link href="/worker/profile/edit">
   217→              <Edit3 className="h-5 w-5" />
   218→              Edit Profile
   219→            </Link>
   220→          </Button>
   221→

   222→
   223→
   224→      <BottomNavWrapper userType="worker" />
   225→

   226→  );
   227→}
   228→
   229→function getExperienceLabel(years: number | null): string | null {
   230→  if (years === null || years === undefined) return null;
   231→  if (years === 0) return "< 1 year";
   232→  if (years <= 3) return `${years} years`;
   233→  if (years <= 5) return "3-5 years";
   234→  if (years <= 10) return "5-10 years";
   235→  return "10+ years";
   236→}
   237→

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


1→"use client";
     2→
     3→import { useState, useEffect } from "react";
     4→import { useRouter } from "next/navigation";
     5→import { logger } from "@/lib/logger";
     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 { AvatarUpload } from "@/components/avatar-upload";
    13→import {
    14→  ArrowLeft,
    15→  Loader2,
    16→  Save,
    17→  Wrench,
    18→  Clock,
    19→  MapPin,
    20→  Calendar,
    21→  Shield,
    22→  Sparkles,
    23→} from "lucide-react";
    24→import { TRADES } from "@/lib/constants";
    25→
    26→const EXPERIENCE_OPTIONS = [
    27→  { label: "Less than 1 year", value: 0 },
    28→  { label: "1-3 years", value: 2 },
    29→  { label: "3-5 years", value: 4 },
    30→  { label: "5-10 years", value: 7 },
    31→  { label: "10+ years", value: 12 },
    32→];
    33→
    34→const AVAILABILITY_OPTIONS = [
    35→  { label: "Full-time", value: "full-time" as const, desc: "Mon-Fri, regular hours" },
    36→  { label: "Casual", value: "casual" as const, desc: "As needed, flexible" },
    37→  { label: "Weekends", value: "weekends" as const, desc: "Saturday & Sunday" },
    38→  { label: "Flexible", value: "flexible" as const, desc: "Any time, any days" },
    39→];
    40→
    41→export default function EditProfilePage() {
    42→  const router = useRouter();
    43→  const supabase = createClient();
    44→
    45→  const [loading, setLoading] = useState(true);
    46→  const [saving, setSaving] = useState(false);
    47→
    48→  // Profile data
    49→  const [userName, setUserName] = useState("");
    50→  const [avatarUrl, setAvatarUrl] = useState<string | null>(null);
    51→  const [bio, setBio] = useState("");
    52→  const [selectedTrades, setSelectedTrades] = useState<string[]>([]);
    53→  const [experience, setExperience] = useState<number | null>(null);
    54→  const [suburb, setSuburb] = useState("");
    55→  const [availability, setAvailability] = useState<string | null>(null);
    56→  const [certifications, setCertifications] = useState<string[]>([]);
    57→  const [newCert, setNewCert] = useState("");
    58→
    59→  useEffect(() => {
    60→    async function loadProfile() {
    61→      const { data: { user } } = await supabase.auth.getUser();
    62→      if (!user) {
    63→        router.push("/auth/login");
    64→        return;
    65→      }
    66→
    67→      const [profileRes, workerRes] = await Promise.all([
    68→        supabase
    69→          .from("profiles")
    70→          .select("name, avatar_url, suburb")
    71→          .eq("id", user.id)
    72→          .single(),
    73→        supabase
    74→          .from("worker_profiles")
    75→          .select("trades, experience_years, certifications, bio, availability")
    76→          .eq("profile_id", user.id)
    77→          .single(),
    78→      ]);
    79→
    80→      if (profileRes.data) {
    81→        setUserName(profileRes.data.name || "");
    82→        setAvatarUrl(profileRes.data.avatar_url);
    83→        setSuburb(profileRes.data.suburb || "");
    84→      }
    85→
    86→      if (workerRes.data) {
    87→        setBio(workerRes.data.bio || "");
    88→        setSelectedTrades(workerRes.data.trades || []);
    89→        setExperience(workerRes.data.experience_years);
    90→        setAvailability(workerRes.data.availability);
    91→        setCertifications(workerRes.data.certifications || []);
    92→      }
    93→
    94→      setLoading(false);
    95→    }
    96→    loadProfile();
    97→  }, [supabase, router]);
    98→
    99→  function toggleTrade(trade: string) {
   100→    setSelectedTrades((prev) =>
   101→      prev.includes(trade) ? prev.filter((t) => t !== trade) : [...prev, trade]
   102→    );
   103→  }
   104→
   105→  function addCert() {
   106→    const trimmed = newCert.trim();
   107→    if (trimmed && !certifications.includes(trimmed)) {
   108→      setCertifications((prev) => [...prev, trimmed]);
   109→      setNewCert("");
   110→    }
   111→  }
   112→
   113→  function removeCert(cert: string) {
   114→    setCertifications((prev) => prev.filter((c) => c !== cert));
   115→  }
   116→
   117→  async function handleSave() {
   118→    if (selectedTrades.length === 0) {
   119→      toast.error("Select at least one trade");
   120→      return;
   121→    }
   122→    if (!suburb.trim()) {
   123→      toast.error("Enter your suburb");
   124→      return;
   125→    }
   126→
   127→    setSaving(true);
   128→    try {
   129→      const { data: { user } } = await supabase.auth.getUser();
   130→      if (!user) throw new Error("Not authenticated");
   131→
   132→      const [profileUpdate, workerUpdate] = await Promise.all([
   133→        supabase
   134→          .from("profiles")
   135→          .update({ suburb: suburb.trim() })
   136→          .eq("id", user.id),
   137→        supabase
   138→          .from("worker_profiles")
   139→          .update({
   140→            bio: bio.trim(),
   141→            trades: selectedTrades,
   142→            experience_years: experience,
   143→            availability,
   144→            certifications,
   145→          })
   146→          .eq("profile_id", user.id),
   147→      ]);
   148→
   149→      if (profileUpdate.error) throw profileUpdate.error;
   150→      if (workerUpdate.error) throw workerUpdate.error;
   151→
   152→      toast.success("Profile updated!");
   153→      router.push("/worker/profile");
   154→      router.refresh();
   155→    } catch (error) {
   156→      logger.error("Save error:", error);
   157→      toast.error("Failed to save. Please try again.");
   158→    } finally {
   159→      setSaving(false);
   160→    }
   161→  }
   162→
   163→  if (loading) {
   164→    return (
   165→

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

   168→    );
   169→  }
   170→
   171→  return (
   172→

   173→
   174→        {/* Header */}
   175→

   176→          <Button
   177→            variant="ghost"
   178→            size="icon"
   179→            onClick={() => router.back()}
   180→            className="h-10 w-10"
   181→          >
   182→            <ArrowLeft className="h-5 w-5" />
   183→          </Button>
   184→
Edit Profile

   185→

   186→
   187→        {/* Avatar */}
   188→

   189→          <AvatarUpload currentUrl={avatarUrl} userName={userName} />
   190→

   191→
   192→        {/* Bio */}
   193→

   194→
   195→            <Sparkles className="h-4 w-4 text-primary" />
   196→            About Me
   197→
   198→          <textarea
   199→            value={bio}
   200→            onChange={(e) => setBio(e.target.value)}
   201→            rows={4}
   202→            className="w-full rounded-xl border-2 border-border bg-background px-4 py-3 text-base focus:border-primary focus:outline-none"
   203→            placeholder="Tell contractors about yourself..."
   204→          />
   205→

   206→
   207→        {/* Trades */}
   208→

   209→
   210→            <Wrench className="h-4 w-4 text-primary" />
   211→            Trades
   212→
   213→

   214→            {TRADES.map((trade) => (
   215→              <button
   216→                key={trade}
   217→                type="button"
   218→                onClick={() => toggleTrade(trade)}
   219→                className={`rounded-xl border-2 px-4 py-3 text-left text-sm font-medium transition-colors active:scale-95 ${
   220→                  selectedTrades.includes(trade)
   221→                    ? "border-primary bg-primary/10 text-primary"
   222→                    : "border-border hover:border-primary/50"
   223→                }`}
   224→              >
   225→                {trade}
   226→              </button>
   227→            ))}
   228→

   229→

   230→
   231→        {/* Experience */}
   232→

   233→
   234→            <Clock className="h-4 w-4 text-primary" />
   235→            Experience
   236→
   237→

   238→            {EXPERIENCE_OPTIONS.map((opt) => (
   239→              <button
   240→                key={opt.value}
   241→                type="button"
   242→                onClick={() => setExperience(opt.value)}
   243→                className={`rounded-xl border-2 px-4 py-3 text-sm font-medium transition-colors active:scale-95 ${
   244→                  experience === opt.value
   245→                    ? "border-primary bg-primary/10 text-primary"
   246→                    : "border-border hover:border-primary/50"
   247→                }`}
   248→              >
   249→                {opt.label}
   250→              </button>
   251→            ))}
   252→

   253→

   254→
   255→        {/* Location */}
   256→

   257→
   258→            <MapPin className="h-4 w-4 text-primary" />
   259→            Suburb
   260→
   261→          <Input
   262→            id="suburb"
   263→            value={suburb}
   264→            onChange={(e) => setSuburb(e.target.value)}
   265→            placeholder="e.g. Parramatta"
   266→            className="h-14 text-base"
   267→          />
   268→

   269→
   270→        {/* Availability */}
   271→

   272→
   273→            <Calendar className="h-4 w-4 text-primary" />
   274→            Availability
   275→
   276→

   277→            {AVAILABILITY_OPTIONS.map((opt) => (
   278→              <button
   279→                key={opt.value}
   280→                type="button"
   281→                onClick={() => setAvailability(opt.value)}
   282→                className={`rounded-xl border-2 px-4 py-3 text-left transition-colors active:scale-95 ${
   283→                  availability === opt.value
   284→                    ? "border-primary bg-primary/10"
   285→                    : "border-border hover:border-primary/50"
   286→                }`}
   287→              >
   288→                <p className={`text-sm font-semibold ${availability === opt.value ? "text-primary" : ""}`}>
   289→                  {opt.label}
   290→

   291→
{opt.desc}

   292→              </button>
   293→            ))}
   294→

   295→

   296→
   297→        {/* Certifications */}
   298→

   299→
   300→            <Shield className="h-4 w-4 text-primary" />
   301→            Certifications
   302→
   303→

   304→            {certifications.map((cert) => (
   305→

   309→                {cert}
   310→                <button
   311→                  type="button"
   312→                  onClick={() => removeCert(cert)}
   313→                  className="text-xs text-muted-foreground hover:text-destructive"
   314→                >
   315→                  Remove
   316→                </button>
   317→

   318→            ))}
   319→

   320→

   321→            <Input
   322→              value={newCert}
   323→              onChange={(e) => setNewCert(e.target.value)}
   324→              placeholder="e.g. White Card"
   325→              className="h-12 text-base"
   326→              onKeyDown={(e) => {
   327→                if (e.key === "Enter") {
   328→                  e.preventDefault();
   329→                  addCert();
   330→                }
   331→              }}
   332→            />
   333→            <Button
   334→              type="button"
   335→              variant="outline"
   336→              onClick={addCert}
   337→              disabled={!newCert.trim()}
   338→              className="h-12 px-4"
   339→            >
   340→              Add
   341→            </Button>
   342→

   343→

   344→
   345→
   346→      {/* Sticky save button */}
   347→

   348→

   349→          <Button
   350→            onClick={handleSave}
   351→            className="h-14 w-full gap-2 text-base font-semibold"
   352→            disabled={saving}
   353→          >
   354→            {saving ? (
   355→              <>
   356→                <Loader2 className="h-5 w-5 animate-spin" />
   357→                Saving...
   358→              </>
   359→            ) : (
   360→              <>
   361→                <Save className="h-5 w-5" />
   362→                Save Changes
   363→              </>
   364→            )}
   365→          </Button>
   366→

   367→

   368→

   369→  );
   370→}
   371→

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