Explore the codebase for:
1. Any existing matching API endpoints or logic (e.g., /api/match/)
2. The matches table schema and any related database code
3. The signup form/page components (both worker and contractor signup)
4. Any existing ToS or privacy policy pages

Search for files related to: match, matching, signup, register, terms, privacy, tos. Report back full file paths and contents.


I'll help you explore the codebase for those areas. Let me search systematically for files related to matching, signup, terms, and privacy.


No files found


Let me refine my search to focus on the source code directory structure:


Bash

find /home/ccuser/the-50-dollar-app/src -type f -name "*match*" -o -name "*signup*" -o -name "*auth*" -o -name "*terms*" -o -name "*privacy*" 2>/dev/null | head -50


find /home/ccuser/the-50-dollar-app -path ./node_modules -prune -o -type f \( -name "*match*" -o -name "*signup*" \) -print 2>/dev/null | grep -v node_modules | head -30


Now let me explore the directory structure to understand the app layout:


ls -la /home/ccuser/the-50-dollar-app/src/app/ 2>/dev/null | head -20


total 96
drwxrwxr-x 6 ccuser ccuser  4096 Feb  8 12:15 (authenticated)
drwxrwxr-x 7 ccuser ccuser  4096 Feb  8 11:18 .
drwxrwxr-x 5 ccuser ccuser  4096 Feb  8 11:18 ..
drwxrwxr-x 9 ccuser ccuser  4096 Feb  8 11:18 api
drwxrwxr-x 6 ccuser ccuser  4096 Feb  8 12:16 auth
-rw-rw-r-- 1 ccuser ccuser   713 Feb  8 11:18 error.tsx
drwxrwxr-x 2 ccuser ccuser  4096 Feb  8 11:18 faq
-rw-rw-r-- 1 ccuser ccuser 25931 Feb  8 11:18 favicon.ico
-rw-rw-r-- 1 ccuser ccuser   827 Feb  8 11:18 global-error.tsx
-rw-rw-r-- 1 ccuser ccuser  4598 Feb  8 11:18 globals.css
-rw-rw-r-- 1 ccuser ccuser  3056 Feb  8 11:18 layout.tsx
-rw-rw-r-- 1 ccuser ccuser  2566 Feb  8 11:18 not-found.tsx
drwxrwxr-x 2 ccuser ccuser  4096 Feb  8 11:18 offline
-rw-rw-r-- 1 ccuser ccuser 12543 Feb  8 11:18 page.tsx


ls -la /home/ccuser/the-50-dollar-app/src/app/api/ 2>/dev/null


total 36
drwxrwxr-x 9 ccuser ccuser 4096 Feb  8 11:18 .
drwxrwxr-x 7 ccuser ccuser 4096 Feb  8 11:18 ..
drwxrwxr-x 2 ccuser ccuser 4096 Feb  8 11:18 abn-lookup
drwxrwxr-x 5 ccuser ccuser 4096 Feb  8 11:18 ai
drwxrwxr-x 4 ccuser ccuser 4096 Feb  8 11:18 auth
drwxrwxr-x 2 ccuser ccuser 4096 Feb  8 11:18 company-logo
drwxrwxr-x 4 ccuser ccuser 4096 Feb  8 11:18 messages
drwxrwxr-x 3 ccuser ccuser 4096 Feb  8 11:18 payments
drwxrwxr-x 3 ccuser ccuser 4096 Feb  8 11:18 webhooks


find /home/ccuser/the-50-dollar-app/src -type f \( -name "*.ts" -o -name "*.tsx" \) | grep -E "(match|signup|auth|terms|privacy)" | head -30


Now let me read the key files. I'll start with the signup pages and API endpoints:


1→"use client";
     2→import { useState } from "react";
     3→import { useRouter } from "next/navigation";
     4→import Link from "next/link";
     5→import { createClient } from "@/lib/supabase/client";
     6→import { Button } from "@/components/ui/button";
     7→import { Input } from "@/components/ui/input";
     8→import { Label } from "@/components/ui/label";
     9→import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
    10→import { HardHat, Building2 } from "lucide-react";
    11→import type { UserType } from "@/lib/types";
    12→
    13→export default function SignupPage() {
    14→  const [email, setEmail] = useState("");
    15→  const [password, setPassword] = useState("");
    16→  const [userType, setUserType] = useState<UserType | null>(null);
    17→  const [error, setError] = useState<string | null>(null);
    18→  const [loading, setLoading] = useState(false);
    19→  const router = useRouter();
    20→  const supabase = createClient();
    21→
    22→  async function handleSignup(e: React.FormEvent) {
    23→    e.preventDefault();
    24→    if (!userType) {
    25→      setError("Please select whether you're hiring or looking for work");
    26→      return;
    27→    }
    28→
    29→    setLoading(true);
    30→    setError(null);
    31→
    32→    const { error } = await supabase.auth.signUp({
    33→      email,
    34→      password,
    35→      options: {
    36→        data: { type: userType },
    37→        emailRedirectTo: `${window.location.origin}/auth/callback`,
    38→      },
    39→    });
    40→
    41→    if (error) {
    42→      setError(error.message);
    43→      setLoading(false);
    44→      return;
    45→    }
    46→
    47→    // Redirect to email verification page instead of dashboard
    48→    router.push("/auth/verify-email");
    49→    router.refresh();
    50→  }
    51→
    52→  return (
    53→

    54→      <Card className="w-full max-w-md">
    55→        <CardHeader className="text-center">
    56→          <CardTitle className="text-2xl font-bold">Create your account</CardTitle>
    57→          <CardDescription>
    58→            Sign up to find work or hire workers
    59→          </CardDescription>
    60→        </CardHeader>
    61→        <CardContent>
    62→          <form onSubmit={handleSignup} className="space-y-4">
    63→

    64→

    65→                Email
    66→                <Input
    67→                  id="email"
    68→                  type="email"
    69→                  value={email}
    70→                  onChange={(e) => setEmail(e.target.value)}
    71→                  required
    72→                  placeholder="you@example.com"
    73→                  className="h-12 text-base"
    74→                />
    75→

    76→

    77→                Password
    78→                <Input
    79→                  id="password"
    80→                  type="password"
    81→                  value={password}
    82→                  onChange={(e) => setPassword(e.target.value)}
    83→                  required
    84→                  placeholder="Create a password"
    85→                  minLength={6}
    86→                  className="h-12 text-base"
    87→                />
    88→

    89→

    90→
    91→

    92→              I am a...
    93→

    94→                <Button
    95→                  type="button"
    96→                  variant={userType === "contractor" ? "default" : "outline"}
    97→                  onClick={() => setUserType("contractor")}
    98→                  className="h-24 flex-col gap-2"
    99→                >
   100→                  <Building2 className="h-6 w-6" />
   101→                  Contractor
   102→                  Hiring workers
   103→                </Button>
   104→                <Button
   105→                  type="button"
   106→                  variant={userType === "worker" ? "default" : "outline"}
   107→                  onClick={() => setUserType("worker")}
   108→                  className="h-24 flex-col gap-2"
   109→                >
   110→                  <HardHat className="h-6 w-6" />
   111→                  Worker
   112→                  Looking for work
   113→                </Button>
   114→

   115→

   116→
   117→            {error && (
   118→

   119→
{error}

   120→

   121→            )}
   122→
   123→            <Button type="submit" className="w-full h-12 text-base font-semibold" disabled={loading}>
   124→              {loading ? "Creating account..." : "Create account"}
   125→            </Button>
   126→
   127→

   128→              Already have an account?{" "}
   129→              <Link href="/auth/login" className="text-primary underline hover:no-underline">
   130→                Sign in
   131→              </Link>
   132→

   133→          </form>
   134→        </CardContent>
   135→      </Card>
   136→

   137→  );
   138→}
   139→

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

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

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

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

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

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

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

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

   303→

   304→

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

   307→

   308→
Create your account

   309→

   310→                  Always free for workers
   311→

   312→

   313→

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

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

   329→

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

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

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

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

   376→

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

   382→

   383→

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

   386→

   387→
Build your profile

   388→

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

   391→

   392→

   393→
   394→

   395→              <button
   396→                onClick={() => setStep("trade")}
   397→                className="flex w-full items-center gap-4 rounded-xl border-2 border-primary bg-primary/5 p-5 text-left transition-colors hover:bg-primary/10"
   398→              >
   399→

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

   402→

   403→
Type my answers

   404→

   405→                    5 quick questions, under 2 minutes
   406→

   407→

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

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

   418→

   419→
Send a voice note

   420→

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

   423→

   424→              </button>
   425→

   426→

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

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

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

   453→
   454→

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

   471→

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

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

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

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

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

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

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

   540→
   541→

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

   559→            </form>
   560→

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

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

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

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

   590→
{opt.desc}

   591→

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

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

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

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

   617→              {CERT_OPTIONS.map((cert) => (
   618→                <button
   619→                  key={cert}
   620→                  onClick={() => toggleCert(cert)}
   621→                  className={`flex w-full items-center gap-3 rounded-xl border-2 px-5 py-3.5 text-left text-sm font-medium transition-colors ${
   622→                    certifications.includes(cert)
   623→                      ? "border-primary bg-primary/10 text-primary"
   624→                      : "border-border hover:border-primary/50"
   625→                  }`}
   626→                >
   627→

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

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

   642→
   643→

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

   659→

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

   665→

   666→

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

   669→

   670→
Your profile

   671→

   672→                  Review and confirm
   673→

   674→

   675→

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

   728→

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

   731→

   732→                    AI-generated bio
   733→

   734→

   735→                    {generateBio(workerData)}
   736→

   737→

   738→

   739→

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

   767→        )}
   768→
   769→

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

   784→

   785→        {icon}
   786→

   787→

   788→
{title}

   789→
{subtitle}

   790→

   791→

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

   812→

   813→
{label}

   814→
{value}

   815→

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

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


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

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

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

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

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

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

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

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

   327→

   328→

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

   331→

   332→
Create your account

   333→

   334→                  Quick signup to get started
   335→

   336→

   337→

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

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

   353→

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

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

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

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

   400→

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

   406→

   407→

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

   410→

   411→
Your company

   412→

   413→                  We'll verify it via ABR
   414→

   415→

   416→

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

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

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

   434→

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

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

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

   464→

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

   467→
Verifying your business

   468→

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

   471→
   472→

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

   477→

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

   483→

   484→

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

   487→

   488→
Confirm your profile

   489→

   490→                  Tap any field to edit
   491→

   492→

   493→

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

   556→

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

   559→

   560→                    Verified via Australian Business Register
   561→

   562→

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

   565→

   566→

   567→

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

   595→        )}
   596→
   597→

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

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

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

   635→        {label}
   636→

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

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

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

   670→

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

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

   694→      ) : (
   695→
{icon}

   696→      )}
   697→

   698→
{label}

   699→
{value}

   700→

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

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


find /home/ccuser/the-50-dollar-app/src -type d -name "matches" -o -name "matching" 2>/dev/null


/home/ccuser/the-50-dollar-app/src/app/(authenticated)/contractor/matches


Great! Let me continue exploring the matches area and check for API endpoints and database files:


find /home/ccuser/the-50-dollar-app/src -type f -name "*.ts" -o -name "*.tsx" | xargs grep -l "matches\|matching" | head -20


ls -la /home/ccuser/the-50-dollar-app/supabase/migrations/ | tail -20


total 32
drwxrwxr-x 2 ccuser ccuser  4096 Feb  8 12:15 .
drwxrwxr-x 6 ccuser ccuser  4096 Feb  8 12:16 ..
-rw-rw-r-- 1 ccuser ccuser  1577 Feb  8 11:59 loading.tsx
-rw-rw-r-- 1 ccuser ccuser 19738 Feb  8 12:15 page.tsx


total 28
drwxrwxr-x 2 ccuser ccuser  4096 Feb  8 12:15 .
drwxrwxr-x 5 ccuser ccuser  4096 Feb  8 12:16 ..
-rw-rw-r-- 1 ccuser ccuser  1270 Feb  8 11:59 loading.tsx
-rw-rw-r-- 1 ccuser ccuser 15327 Feb  8 12:15 page.tsx


total 28
drwxrwxr-x 2 ccuser ccuser 4096 Feb  8 11:47 .
drwxrwxr-x 4 ccuser ccuser 4096 Feb  8 11:18 ..
-rw-rw-r-- 1 ccuser ccuser 3356 Feb  8 11:18 20260208000001_add_abn_uniqueness_constraint.sql
-rw-r--r-- 1 root   root   1775 Feb  8 11:37 20260209000001_add_abn_unique_constraint.sql
-rw-rw-r-- 1 ccuser ccuser 3590 Feb  8 11:41 20260209000002_add_delete_policies.sql
-rw-rw-r-- 1 ccuser ccuser 2541 Feb  8 11:47 20260209000002_deep_audit_fixes.sql
-rw-rw-r-- 1 ccuser ccuser 1290 Feb  8 11:18 ABN_MIGRATION_SUMMARY.md


Now let me read the matches pages and check for API and database schema files:


     1→"use client";
     2→
     3→import { useEffect, useState, useRef } from "react";
     4→import Link from "next/link";
     5→import { createClient } from "@/lib/supabase/client";
     6→import { Card, CardContent } from "@/components/ui/card";
     7→import { Badge } from "@/components/ui/badge";
     8→import { Button } from "@/components/ui/button";
     9→import { BottomNav } from "@/components/bottom-nav";
    10→import { HirePaymentModal } from "@/components/payment/HirePaymentModal";
    11→import {
    12→  ArrowLeft,
    13→  Loader2,
    14→  Star,
    15→  MapPin,
    16→  Clock,
    17→  CheckCircle2,
    18→  UserCheck,
    19→  HardHat,
    20→  AlertCircle,
    21→} from "lucide-react";
    22→import type { Job } from "@/lib/types";
    23→import { postgresPointToJson } from "@/lib/location-utils";
    24→
    25→interface MatchWithWorker {
    26→  id: string;
    27→  job_id: string;
    28→  worker_id: string;
    29→  status: string;
    30→  match_score: number | null;
    31→  ai_summary: string | null;
    32→  contractor_action: string;
    33→  worker_action: string;
    34→  hired_at: string | null;
    35→  fee_charged: boolean;
    36→  created_at: string;
    37→  worker: {
    38→    id: string;
    39→    name: string | null;
    40→    suburb: string | null;
    41→    avatar_url: string | null;
    42→  } | null;
    43→  worker_profile: {
    44→    trades: string[];
    45→    experience_years: number | null;
    46→    certifications: string[];
    47→    availability: string | null;
    48→  } | null;
    49→}
    50→
    51→interface SwipeableMatchCardProps {
    52→  match: MatchWithWorker;
    53→  isHired: boolean;
    54→  justHired: boolean;
    55→  hiring: boolean;
    56→  onHire: (match: MatchWithWorker) => void;
    57→  onReject: (match: MatchWithWorker) => void;
    58→}
    59→
    60→function SwipeableMatchCard({ match, isHired, justHired, hiring, onHire, onReject }: SwipeableMatchCardProps) {
    61→  const [swipeX, setSwipeX] = useState(0);
    62→  const [swiping, setSwiping] = useState(false);
    63→  const startX = useRef(0);
    64→  const currentX = useRef(0);
    65→
    66→  const summary = match.ai_summary || generateAiSummary(match.worker, match.worker_profile);
    67→
    68→  const handleTouchStart = (e: React.TouchEvent) => {
    69→    if (isHired) return;
    70→    startX.current = e.touches[0].clientX;
    71→    setSwiping(true);
    72→  };
    73→
    74→  const handleTouchMove = (e: React.TouchEvent) => {
    75→    if (!swiping || isHired) return;
    76→    currentX.current = e.touches[0].clientX;
    77→    const diff = currentX.current - startX.current;
    78→    // Allow both directions
    79→    setSwipeX(Math.max(-150, Math.min(diff, 150)));
    80→  };
    81→
    82→  const handleTouchEnd = () => {
    83→    if (!swiping || isHired) return;
    84→    setSwiping(false);
    85→
    86→    // If swiped right > 100px, hire
    87→    if (swipeX > 100) {
    88→      onHire(match);
    89→    }
    90→    // If swiped left > 100px, reject
    91→    else if (swipeX < -100) {
    92→      onReject(match);
    93→    }
    94→
    95→    // Reset
    96→    setSwipeX(0);
    97→  };
    98→
    99→  const swipeProgress = Math.abs(swipeX) / 100;
   100→  const swipeDirection = swipeX > 0 ? "right" : "left";

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


     1→"use client";
     2→
     3→import { useEffect, useState, useRef } from "react";
     4→import Link from "next/link";
     5→import { createClient } from "@/lib/supabase/client";
     6→import { Card, CardContent } from "@/components/ui/card";
     7→import { Badge } from "@/components/ui/badge";
     8→import { Button } from "@/components/ui/button";
     9→import { BottomNav } from "@/components/bottom-nav";
    10→import {
    11→  MapPin,
    12→  Calendar,
    13→  DollarSign,
    14→  Loader2,
    15→  Search,
    16→  HardHat,
    17→  Check,
    18→  X,
    19→} from "lucide-react";
    20→import type { Job } from "@/lib/types";
    21→import { postgresPointToJson } from "@/lib/location-utils";
    22→
    23→const TRADE_FILTERS = [
    24→  "All",
    25→  "Steel Fixer",
    26→  "Formworker",
    27→  "Labourer",
    28→  "Concreter",
    29→  "Carpenter",
    30→  "Scaffolder",
    31→  "Other",
    32→];
    33→
    34→const TRADE_ID_TO_LABEL: Record<string, string> = {
    35→  "steel-fixer": "Steel Fixer",
    36→  "formworker": "Formworker",
    37→  "labourer": "Labourer",
    38→  "concreter": "Concreter",
    39→  "carpenter": "Carpenter",
    40→  "scaffolder": "Scaffolder",
    41→  "other": "Other",
    42→};
    43→
    44→function tradeLabelFromId(id: string): string {
    45→  return TRADE_ID_TO_LABEL[id] || id;
    46→}
    47→
    48→interface SwipeableJobCardProps {
    49→  job: Job;
    50→  hasApplied: boolean;
    51→  onApply: (job: Job) => void;
    52→  onClick: (job: Job) => void;
    53→}
    54→
    55→function SwipeableJobCard({ job, hasApplied, onApply, onClick }: SwipeableJobCardProps) {
    56→  const [swipeX, setSwipeX] = useState(0);
    57→  const [swiping, setSwiping] = useState(false);
    58→  const startX = useRef(0);
    59→  const currentX = useRef(0);
    60→
    61→  const handleTouchStart = (e: React.TouchEvent) => {
    62→    if (hasApplied) return;
    63→    startX.current = e.touches[0].clientX;
    64→    setSwiping(true);
    65→  };
    66→
    67→  const handleTouchMove = (e: React.TouchEvent) => {
    68→    if (!swiping || hasApplied) return;
    69→    currentX.current = e.touches[0].clientX;
    70→    const diff = currentX.current - startX.current;
    71→    // Only allow right swipe (positive diff)
    72→    if (diff > 0) {
    73→      setSwipeX(Math.min(diff, 150));
    74→    }
    75→  };
    76→
    77→  const handleTouchEnd = () => {
    78→    if (!swiping || hasApplied) return;
    79→    setSwiping(false);
    80→
    81→    // If swiped more than 100px, apply
    82→    if (swipeX > 100) {
    83→      onApply(job);
    84→    }
    85→
    86→    // Reset
    87→    setSwipeX(0);
    88→  };
    89→
    90→  const swipeProgress = Math.min(swipeX / 100, 1);
    91→  const backgroundColor = hasApplied
    92→    ? "transparent"
    93→    : `rgba(34, 197, 94, ${swipeProgress * 0.1})`;
    94→
    95→  return (
    96→    <div className="relative">
    97→      {/* Swipe indicator background */}
    98→      {!hasApplied && swipeX > 0 && (
    99→        <div
   100→          className="absolute inset-0 rounded-xl flex items-center justify-start pl-6 pointer-events-none"

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


find /home/ccuser/the-50-dollar-app/supabase -name "*.sql" -type f | head -10


Let me read the schema file and check for terms/privacy pages:


     1→-- RateRight v2 Database Schema
     2→-- Run this in the Supabase SQL Editor
     3→
     4→-- ============================================
     5→-- ENUMS
     6→-- ============================================
     7→
     8→CREATE TYPE user_type AS ENUM ('worker', 'contractor');
     9→CREATE TYPE availability_type AS ENUM ('full-time', 'casual', 'weekends', 'flexible');
    10→CREATE TYPE rate_type AS ENUM ('hourly', 'daily');
    11→CREATE TYPE job_status AS ENUM ('draft', 'active', 'filled', 'closed');
    12→CREATE TYPE match_status AS ENUM ('suggested', 'applied', 'accepted', 'rejected', 'hired');
    13→CREATE TYPE action_status AS ENUM ('pending', 'accepted', 'rejected');
    14→CREATE TYPE payment_status AS ENUM ('pending', 'charged', 'refunded');
    15→CREATE TYPE notification_type AS ENUM ('match', 'hire_request', 'hire_confirmed', 'rating', 'job_posted', 'system');
    16→
    17→-- ============================================
    18→-- HELPER FUNCTION: auto-update updated_at
    19→-- ============================================
    20→
    21→CREATE OR REPLACE FUNCTION update_updated_at()
    22→RETURNS trigger AS $$
    23→BEGIN
    24→  NEW.updated_at = now();
    25→  RETURN NEW;
    26→END;
    27→$$ LANGUAGE plpgsql;
    28→
    29→-- ============================================
    30→-- TABLES
    31→-- ============================================
    32→
    33→-- Profiles (extends Supabase Auth)
    34→CREATE TABLE profiles (
    35→  id uuid PRIMARY KEY REFERENCES auth.users ON DELETE CASCADE,
    36→  type user_type NOT NULL,
    37→  name text,
    38→  phone text,
    39→  email text,
    40→  avatar_url text,
    41→  location point,
    42→  suburb text,
    43→  verified boolean DEFAULT false,
    44→  onboarding_completed boolean DEFAULT false,
    45→  created_at timestamptz DEFAULT now(),
    46→  updated_at timestamptz DEFAULT now(),
    47→  ai_generated boolean DEFAULT false
    48→);
    49→
    50→CREATE TRIGGER profiles_updated_at
    51→  BEFORE UPDATE ON profiles
    52→  FOR EACH ROW EXECUTE FUNCTION update_updated_at();
    53→
    54→-- Companies (contractor-specific)
    55→CREATE TABLE companies (
    56→  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    57→  profile_id uuid NOT NULL UNIQUE REFERENCES profiles ON DELETE CASCADE,
    58→  name text NOT NULL,
    59→  abn text,
    60→  address text,
    61→  industry text,
    62→  size text,
    63→  website text,
    64→  logo_url text,
    65→  ai_research jsonb,
    66→  created_at timestamptz DEFAULT now(),
    67→  updated_at timestamptz DEFAULT now()
    68→);
    69→
    70→CREATE TRIGGER companies_updated_at
    71→  BEFORE UPDATE ON companies
    72→  FOR EACH ROW EXECUTE FUNCTION update_updated_at();
    73→
    74→-- Worker profiles (worker-specific)
    75→CREATE TABLE worker_profiles (
    76→  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    77→  profile_id uuid NOT NULL UNIQUE REFERENCES profiles ON DELETE CASCADE,
    78→  trades text[] DEFAULT '{}',
    79→  experience_years integer,
    80→  certifications text[] DEFAULT '{}',
    81→  white_card_number text,
    82→  white_card_verified boolean DEFAULT false,
    83→  availability availability_type,
    84→  languages text[] DEFAULT '{}',
    85→  bio text,
    86→  voice_note_url text,
    87→  ai_extracted jsonb,
    88→  created_at timestamptz DEFAULT now(),
    89→  updated_at timestamptz DEFAULT now()
    90→);
    91→
    92→CREATE TRIGGER worker_profiles_updated_at
    93→  BEFORE UPDATE ON worker_profiles
    94→  FOR EACH ROW EXECUTE FUNCTION update_updated_at();
    95→
    96→-- Jobs
    97→CREATE TABLE jobs (
    98→  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    99→  company_id uuid NOT NULL REFERENCES companies ON DELETE CASCADE,
   100→  title text NOT NULL,
   101→  description text,
   102→  trade text NOT NULL,
   103→  location point,
   104→  suburb text,
   105→  site_address text,
   106→  rate_min decimal,
   107→  rate_max decimal,
   108→  rate_type rate_type DEFAULT 'hourly',
   109→  start_date date,
   110→  duration text,
   111→  workers_needed integer DEFAULT 1,
   112→  certifications_required text[] DEFAULT '{}',
   113→  status job_status DEFAULT 'draft',
   114→  ai_generated boolean DEFAULT false,
   115→  created_at timestamptz DEFAULT now(),
   116→  updated_at timestamptz DEFAULT now()
   117→);
   118→
   119→CREATE TRIGGER jobs_updated_at
   120→  BEFORE UPDATE ON jobs
   121→  FOR EACH ROW EXECUTE FUNCTION update_updated_at();
   122→
   123→-- Matches
   124→CREATE TABLE matches (
   125→  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
   126→  job_id uuid NOT NULL REFERENCES jobs ON DELETE CASCADE,
   127→  worker_id uuid NOT NULL REFERENCES profiles ON DELETE CASCADE,
   128→  status match_status DEFAULT 'suggested',
   129→  match_score decimal,
   130→  ai_summary text,
   131→  contractor_action action_status DEFAULT 'pending',
   132→  worker_action action_status DEFAULT 'pending',
   133→  hired_at timestamptz,
   134→  fee_charged boolean DEFAULT false,
   135→  created_at timestamptz DEFAULT now(),
   136→  updated_at timestamptz DEFAULT now(),
   137→  UNIQUE (job_id, worker_id)
   138→);
   139→
   140→CREATE TRIGGER matches_updated_at
   141→  BEFORE UPDATE ON matches
   142→  FOR EACH ROW EXECUTE FUNCTION update_updated_at();
   143→
   144→-- Ratings
   145→CREATE TABLE ratings (
   146→  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
   147→  match_id uuid NOT NULL REFERENCES matches ON DELETE CASCADE,
   148→  from_id uuid NOT NULL REFERENCES profiles ON DELETE CASCADE,
   149→  to_id uuid NOT NULL REFERENCES profiles ON DELETE CASCADE,
   150→  score integer NOT NULL CHECK (score BETWEEN 1 AND 5),
   151→  comment text,
   152→  created_at timestamptz DEFAULT now(),
   153→  UNIQUE (match_id, from_id)
   154→);
   155→
   156→-- Payments
   157→CREATE TABLE payments (
   158→  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
   159→  match_id uuid NOT NULL REFERENCES matches ON DELETE CASCADE,
   160→  company_id uuid NOT NULL REFERENCES companies ON DELETE CASCADE,
   161→  amount decimal DEFAULT 50.00,
   162→  status payment_status DEFAULT 'pending',
   163→  stripe_payment_id text,
   164→  created_at timestamptz DEFAULT now()
   165→);
   166→
   167→-- Notifications
   168→CREATE TABLE notifications (
   169→  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
   170→  profile_id uuid NOT NULL REFERENCES profiles ON DELETE CASCADE,
   171→  type notification_type NOT NULL,
   172→  title text NOT NULL,
   173→  body text,
   174→  data jsonb,
   175→  read boolean DEFAULT false,
   176→  created_at timestamptz DEFAULT now()
   177→);
   178→
   179→-- ============================================
   180→-- INDEXES
   181→-- ============================================
   182→
   183→CREATE INDEX idx_profiles_type ON profiles (type);
   184→CREATE INDEX idx_profiles_suburb ON profiles (suburb);
   185→
   186→CREATE INDEX idx_companies_profile_id ON companies (profile_id);
   187→CREATE INDEX idx_companies_abn ON companies (abn);
   188→
   189→CREATE INDEX idx_worker_profiles_profile_id ON worker_profiles (profile_id);
   190→CREATE INDEX idx_worker_profiles_trades ON worker_profiles USING gin (trades);
   191→CREATE INDEX idx_worker_profiles_availability ON worker_profiles (availability);
   192→CREATE INDEX idx_worker_profiles_certifications ON worker_profiles USING gin (certifications);
   193→
   194→CREATE INDEX idx_jobs_company_id ON jobs (company_id);
   195→CREATE INDEX idx_jobs_status ON jobs (status);
   196→CREATE INDEX idx_jobs_trade ON jobs (trade);
   197→CREATE INDEX idx_jobs_created_at ON jobs (created_at DESC);
   198→CREATE INDEX idx_jobs_suburb ON jobs (suburb);
   199→
   200→CREATE INDEX idx_matches_job_id ON matches (job_id);
   201→CREATE INDEX idx_matches_worker_id ON matches (worker_id);
   202→CREATE INDEX idx_matches_status ON matches (status);
   203→
   204→CREATE INDEX idx_ratings_to_id ON ratings (to_id);
   205→CREATE INDEX idx_ratings_from_id ON ratings (from_id);
   206→
   207→CREATE INDEX idx_payments_match_id ON payments (match_id);
   208→CREATE INDEX idx_payments_company_id ON payments (company_id);
   209→
   210→CREATE INDEX idx_notifications_profile_id ON notifications (profile_id);
   211→CREATE INDEX idx_notifications_read ON notifications (profile_id, read);
   212→
   213→-- ============================================
   214→-- ROW LEVEL SECURITY
   215→-- ============================================
   216→
   217→ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
   218→ALTER TABLE companies ENABLE ROW LEVEL SECURITY;
   219→ALTER TABLE worker_profiles ENABLE ROW LEVEL SECURITY;
   220→ALTER TABLE jobs ENABLE ROW LEVEL SECURITY;
   221→ALTER TABLE matches ENABLE ROW LEVEL SECURITY;
   222→ALTER TABLE ratings ENABLE ROW LEVEL SECURITY;
   223→ALTER TABLE payments ENABLE ROW LEVEL SECURITY;
   224→ALTER TABLE notifications ENABLE ROW LEVEL SECURITY;
   225→
   226→-- Profiles: users can read all profiles, update their own
   227→CREATE POLICY "Public profiles are viewable by everyone"
   228→  ON profiles FOR SELECT
   229→  USING (true);
   230→
   231→CREATE POLICY "Users can update their own profile"
   232→  ON profiles FOR UPDATE
   233→  USING (auth.uid() = id);
   234→
   235→CREATE POLICY "Users can insert their own profile"
   236→  ON profiles FOR INSERT
   237→  WITH CHECK (auth.uid() = id);
   238→
   239→-- Companies: viewable by everyone, editable by owner
   240→CREATE POLICY "Companies are viewable by everyone"
   241→  ON companies FOR SELECT
   242→  USING (true);
   243→
   244→CREATE POLICY "Contractors can insert their own company"
   245→  ON companies FOR INSERT
   246→  WITH CHECK (profile_id = auth.uid());
   247→
   248→CREATE POLICY "Contractors can update their own company"
   249→  ON companies FOR UPDATE
   250→  USING (profile_id = auth.uid());
   251→
   252→-- Worker profiles: viewable by everyone, editable by owner
   253→CREATE POLICY "Worker profiles are viewable by everyone"
   254→  ON worker_profiles FOR SELECT
   255→  USING (true);
   256→
   257→CREATE POLICY "Workers can insert their own profile"
   258→  ON worker_profiles FOR INSERT
   259→  WITH CHECK (profile_id = auth.uid());
   260→
   261→CREATE POLICY "Workers can update their own profile"
   262→  ON worker_profiles FOR UPDATE
   263→  USING (profile_id = auth.uid());
   264→
   265→-- Jobs: viewable by everyone, editable by company owner
   266→CREATE POLICY "Jobs are viewable by everyone"
   267→  ON jobs FOR SELECT
   268→  USING (true);
   269→
   270→CREATE POLICY "Contractors can insert jobs for their company"
   271→  ON jobs FOR INSERT
   272→  WITH CHECK (
   273→    company_id IN (
   274→      SELECT id FROM companies WHERE profile_id = auth.uid()
   275→    )
   276→  );
   277→
   278→CREATE POLICY "Contractors can update their own jobs"
   279→  ON jobs FOR UPDATE
   280→  USING (
   281→    company_id IN (
   282→      SELECT id FROM companies WHERE profile_id = auth.uid()
   283→    )
   284→  );
   285→
   286→-- Matches: viewable by involved parties
   287→CREATE POLICY "Users can view their own matches"
   288→  ON matches FOR SELECT
   289→  USING (
   290→    worker_id = auth.uid() OR
   291→    job_id IN (
   292→      SELECT j.id FROM jobs j
   293→      JOIN companies c ON j.company_id = c.id
   294→      WHERE c.profile_id = auth.uid()
   295→    )
   296→  );
   297→
   298→CREATE POLICY "System can insert matches"
   299→  ON matches FOR INSERT
   300→  WITH CHECK (true);

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


find /home/ccuser/the-50-dollar-app/src -type f \( -name "*.tsx" -o -name "*.ts" \) | xargs grep -l "terms\|privacy\|tos" 2>/dev/null


total 20
drwxrwxr-x 2 ccuser ccuser 4096 Feb  8 11:18 .
drwxrwxr-x 7 ccuser ccuser 4096 Feb  8 11:18 ..
-rw-rw-r-- 1 ccuser ccuser 9316 Feb  8 11:18 page.tsx


Let me check for API routes and continue reading the schema:


find /home/ccuser/the-50-dollar-app/src/app/api -type f -name "*.ts" | head -30


     1→import Link from "next/link";
     2→import Image from "next/image";
     3→import type { Metadata } from "next";
     4→import { HelpCircle, Building2, Wrench, Users } from "lucide-react";
     5→import { Button } from "@/components/ui/button";
     6→import {
     7→  Accordion,
     8→  AccordionContent,
     9→  AccordionItem,
    10→  AccordionTrigger,
    11→} from "@/components/ui/accordion";
    12→
    13→export const metadata: Metadata = {
    14→  title: "FAQ — RateRight",
    15→  description:
    16→    "Frequently asked questions about RateRight, the simplest way to hire construction workers in Australia.",
    17→};
    18→
    19→export const dynamic = "force-static";
    20→
    21→const workerFaqs = [
    22→  {
    23→    q: "How does RateRight work for workers?",
    24→    a: "RateRight connects qualified construction workers with contractors who need skilled labour. Simply create your profile, verify your qualifications, and get matched with jobs that fit your skills and schedule.",
    25→  },
    26→  {
    27→    q: "Is RateRight free for workers?",
    28→    a: "Yes! It is completely free for workers to join, create a profile, and get matched with jobs. We do not charge any subscription fees or take a percentage of your earnings.",
    29→  },
    30→  {
    31→    q: "How do I get hired through RateRight?",
    32→    a: "Complete your profile with accurate information about your skills, experience, and certifications. Upload your White Card and any trade-specific licences. Contractors search our database and contact workers directly.",
    33→  },
    34→  {
    35→    q: "What trades does RateRight cover?",
    36→    a: "We cover all major construction trades including carpentry, plumbing, electrical, bricklaying, concreting, tiling, plastering, painting, landscaping, and general labour.",
    37→  },
    38→  {
    39→    q: "Do I need a White Card to join?",
    40→    a: "Yes, all workers must have a valid White Card (General Construction Induction Card) to work on Australian construction sites.",
    41→  },
    42→  {
    43→    q: "How quickly can I find work?",
    44→    a: "Many workers receive their first job invitation within 24\u201348 hours of profile approval.",
    45→  },
    46→  {
    47→    q: "How and when do I get paid?",
    48→    a: "Payment terms are agreed directly with the contractor before starting work. Most contractors pay weekly or fortnightly. RateRight does not handle payments.",
    49→  },
    50→  {
    51→    q: "Can I choose when and where I work?",
    52→    a: "Absolutely! You set your availability and preferred work locations in your profile.",
    53→  },
    54→];
    55→
    56→const contractorFaqs = [
    57→  {
    58→    q: "How does RateRight work for contractors?",
    59→    a: "RateRight gives you instant access to pre-verified construction workers. Search our database by trade, location, availability, and ratings.",
    60→  },
    61→  {
    62→    q: "What is the $50 fee about?",
    63→    a: "We charge a flat $50 fee per successful job placement. No subscriptions, no hidden costs, no percentage cuts.",
    64→  },
    65→  {
    66→    q: "How fast can I find workers?",
    67→    a: "Most contractors find suitable workers within hours. Many jobs are filled the same day they are posted.",
    68→  },
    69→  {
    70→    q: "What if the worker does not show up?",
    71→    a: "We take reliability seriously. Workers with no-shows are removed from the platform. Contact us immediately and we will help you find a replacement quickly.",
    72→  },
    73→  {
    74→    q: "Are workers verified?",
    75→    a: "Yes, all workers go through our verification process. We check White Cards, trade licences, and conduct identity verification.",
    76→  },
    77→  {
    78→    q: "How many workers can I contact?",
    79→    a: "As many as you need! There is no limit to how many workers you can view or contact.",
    80→  },
    81→  {
    82→    q: "Do I have to use RateRight for payment?",
    83→    a: "No, you handle payment directly with the worker according to your normal payroll processes.",
    84→  },
    85→  {
    86→    q: "What areas does RateRight cover?",
    87→    a: "We currently operate in Sydney, Melbourne, Brisbane, Perth, and Adelaide metropolitan areas.",
    88→  },
    89→];
    90→
    91→const generalFaqs = [
    92→  {
    93→    q: "Is RateRight a labour hire company?",
    94→    a: "No, RateRight is a marketplace platform that connects contractors directly with workers. We are not a labour hire company.",
    95→  },
    96→  {
    97→    q: "How is payment handled?",
    98→    a: "Payment is arranged directly between the contractor and worker. RateRight does not process payments or take a percentage of wages.",
    99→  },
   100→  {

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


grep -r "terms\|Terms" /home/ccuser/the-50-dollar-app/src --include="*.tsx" --include="*.ts" | head -10


/home/ccuser/the-50-dollar-app/src/app/faq/page.tsx:    a: "Payment terms are agreed directly with the contractor before starting work. Most contractors pay weekly or fortnightly. RateRight does not handle payments.",


Let me check more API routes and read key files:


     1→import { formatABN } from "@/lib/abn-utils";
     2→import { NextRequest, NextResponse } from 'next/server';
     3→import { isValidABN, cleanABN } from '@/lib/abn-utils';
     4→import { checkRateLimit, getClientIP, createRateLimitResponse } from '@/lib/rate-limit';
     5→
     6→/**
     7→ * ABN Lookup API Route — GET /api/abn-lookup?abn=XXXXXXXXXXX
     8→ *
     9→ * Validates the ABN locally (checksum), then calls the ABR JSON endpoint
    10→ * to fetch entity details.
    11→ *
    12→ * ABR JSON docs: https://abr.business.gov.au/json/
    13→ * Registration:  https://abr.business.gov.au/Tools/WebServices
    14→ *
    15→ * Required env var:
    16→ *   ABR_GUID — the authentication GUID issued after registration
    17→ *
    18→ * Optional env var:
    19→ *   ABR_DEV_MODE — set to 'true' to enable development mode with mock data
    20→ */
    21→
    22→// ── ABR configuration ──────────────────────────────────────────────
    23→const ABR_JSON_URL = 'https://abr.business.gov.au/json/AbnDetails.aspx';
    24→const ABR_GUID = process.env.ABR_GUID ?? '';
    25→const ABR_DEV_MODE = process.env.ABR_DEV_MODE === 'true' || process.env.NODE_ENV === 'development';
    26→
    27→// ── Types ──────────────────────────────────────────────────────────
    28→
    29→/** Shape returned by the ABR JSON endpoint (JSONP-unwrapped). */
    30→interface ABRJsonResponse {
    31→  Abn: string;
    32→  AbnStatus: string;
    33→  AbnStatusEffectiveFrom: string;
    34→  Acn: string;
    35→  AddressDate: string | null;
    36→  AddressPostcode: string;
    37→  AddressState: string;
    38→  BusinessName: Array<string | { Value: string; EffectiveFrom: string }>;
    39→  EntityName: string;
    40→  EntityTypeCode: string;
    41→  EntityTypeName: string;
    42→  Gst: string | null;
    43→  Message: string;
    44→}
    45→
    46→/** Director verification result */
    47→interface DirectorVerification {
    48→  status: 'pending' | 'verified' | 'mismatch' | 'not_available';
    49→  message: string;
    50→  requiresManualReview?: boolean;
    51→}
    52→
    53→/** Company data from ABR */
    54→interface CompanyData {
    55→  name: string;
    56→  abn: string;
    57→  status: string;
    58→  statusDate: string;
    59→  state: string;
    60→  postcode: string;
    61→  type: string;
    62→  typeCode: string;
    63→  gstRegistered: boolean;
    64→  acn?: string;
    65→  directorVerification?: DirectorVerification;
    66→}
    67→
    68→/** Our normalised response. */
    69→interface ABNLookupResult {
    70→  success: boolean;
    71→  data?: CompanyData;
    72→  error?: string;
    73→  devMode?: boolean;
    74→}
    75→
    76→// ── Development Mode Mock Data ─────────────────────────────────────
    77→
    78→/**
    79→ * Mock company data for development when ABR_GUID is not configured.
    80→ * Uses obviously fake ABNs to prevent confusion with real data.
    81→ */
    82→const MOCK_COMPANIES: Record<string, CompanyData> = {
    83→  // Test ABN for development - passes checksum but is obviously fake
    84→  '51824753556': {
    85→    name: 'TEST COMPANY PTY LTD (Dev Mode)',
    86→    abn: '51 824 753 556',
    87→    status: 'Active',
    88→    statusDate: '2020-01-01',
    89→    state: 'NSW',
    90→    postcode: '2000',
    91→    type: 'Australian Private Company',
    92→    typeCode: 'PRV',
    93→    gstRegistered: true,
    94→    directorVerification: {
    95→      status: 'not_available',
    96→      message: 'Director verification unavailable in development mode',
    97→    },
    98→  },
    99→  // Another test ABN
   100→  '34241177887': {
   101→    name: 'EXAMPLE BUILDERS PTY LTD (Dev Mode)',
   102→    abn: '34 241 177 887',
   103→    status: 'Active',
   104→    statusDate: '2019-06-15',
   105→    state: 'VIC',
   106→    postcode: '3000',
   107→    type: 'Australian Private Company',
   108→    typeCode: 'PRV',
   109→    gstRegistered: true,
   110→    directorVerification: {
   111→      status: 'not_available',
   112→      message: 'Director verification unavailable in development mode',
   113→    },
   114→  },
   115→};
   116→
   117→/**
   118→ * Generate mock data for any valid ABN in development mode.
   119→ */
   120→function generateMockData(abn: string): CompanyData {
   121→  const formatted = formatABN(abn);
   122→  return {
   123→    name: `MOCK COMPANY ${abn.slice(-4)} PTY LTD (Dev Mode)`,
   124→    abn: formatted,
   125→    status: 'Active',
   126→    statusDate: new Date().toISOString().split('T')[0],
   127→    state: 'NSW',
   128→    postcode: '2000',
   129→    type: 'Australian Private Company',
   130→    typeCode: 'PRV',
   131→    gstRegistered: Math.random() > 0.3,
   132→    directorVerification: {
   133→      status: 'not_available',
   134→      message: 'Director verification unavailable in development mode. Register at https://abr.business.gov.au/Tools/WebServices',
   135→    },
   136→  };
   137→}
   138→
   139→// ── Helpers ────────────────────────────────────────────────────────
   140→
   141→function buildABRUrl(abn: string): string {
   142→  const params = new URLSearchParams({
   143→    abn,
   144→    guid: ABR_GUID,
   145→    callback: 'cbr',
   146→  });
   147→  return `${ABR_JSON_URL}?${params.toString()}`;
   148→}
   149→
   150→function stripJSONP(jsonp: string): ABRJsonResponse {
   151→  const match = jsonp.match(/^cbr\((.*)\)$/);
   152→  if (!match) throw new Error('Unexpected ABR response format');
   153→  return JSON.parse(match[1]);
   154→}
   155→
   156→function normaliseABRResponse(abr: ABRJsonResponse): CompanyData {
   157→  const name = abr.EntityName ||
   158→    (Array.isArray(abr.BusinessName) && abr.BusinessName[0]) ||
   159→    'Unknown';
   160→
   161→  return {
   162→    name: typeof name === 'string' ? name : (name as any).Value || 'Unknown',
   163→    abn: formatABN(abr.Abn),
   164→    status: abr.AbnStatus,
   165→    statusDate: abr.AbnStatusEffectiveFrom,
   166→    state: abr.AddressState,
   167→    postcode: abr.AddressPostcode,
   168→    type: abr.EntityTypeName,
   169→    typeCode: abr.EntityTypeCode,
   170→    gstRegistered: abr.Gst !== null,
   171→    acn: abr.Acn || undefined,
   172→    // Director verification will be done separately via ASIC
   173→    directorVerification: {
   174→      status: 'pending',
   175→      message: 'Director verification pending. ASIC lookup required.',
   176→      requiresManualReview: true,
   177→    },
   178→  };
   179→}
   180→
   181→/**
   182→ * ASIC Director Verification (Stub)
   183→ *
   184→ * ASIC Connect API requires paid subscription.
   185→ * For MVP, we flag all signups for manual review.
   186→ *
   187→ * Future implementation:
   188→ * - Call ASIC Connect API to get director list
   189→ * - Cross-reference user email/name with director records
   190→ * - Auto-verify if match found
   191→ * - Flag for manual review if no match
   192→ *
   193→ * @see https://connectonline.asic.gov.au/
   194→ */
   195→async function verifyDirector(
   196→  abn: string,
   197→  acn?: string,
   198→  userEmail?: string
   199→): Promise<DirectorVerification> {
   200→  // TODO: Implement ASIC Connect API integration
   201→  // For now, flag all for manual review
   202→
   203→  console.log(`[ASIC] Director verification requested for ABN ${abn}, ACN ${acn || 'N/A'}`);
   204→
   205→  return {
   206→    status: 'pending',
   207→    message: 'Director verification requires manual review. ASIC integration pending.',
   208→    requiresManualReview: true,
   209→  };
   210→}
   211→
   212→// ── Route Handler ──────────────────────────────────────────────────
   213→
   214→// Rate limit: 10 requests per 15 minutes per IP (external API call)
   215→const ABN_LOOKUP_RATE_LIMIT = 10;
   216→const ABN_LOOKUP_WINDOW_MS = 15 * 60 * 1000;
   217→
   218→export async function GET(request: NextRequest) {
   219→  try {
   220→    // --- 0. Rate limiting ---------------------------------------------------
   221→    const clientIP = getClientIP(request);
   222→    const rateLimit = checkRateLimit(`abn-lookup:${clientIP}`, ABN_LOOKUP_RATE_LIMIT, ABN_LOOKUP_WINDOW_MS);
   223→
   224→    if (!rateLimit.allowed) {
   225→      const retryAfter = Math.ceil((rateLimit.resetTime - Date.now()) / 1000);
   226→      return createRateLimitResponse(clientIP, retryAfter);
   227→    }
   228→
   229→    const searchParams = request.nextUrl.searchParams;
   230→    const rawABN = searchParams.get('abn');
   231→    const userEmail = searchParams.get('email'); // Optional: for director verification
   232→
   233→    // --- 1. Validate input --------------------------------------------------
   234→    if (!rawABN) {
   235→      return NextResponse.json({
   236→        success: false,
   237→        error: 'Missing query parameter: abn'
   238→      }, { status: 400 });
   239→    }
   240→
   241→    const abn = cleanABN(rawABN);
   242→    if (!isValidABN(abn)) {
   243→      return NextResponse.json({
   244→        success: false,
   245→        error: 'Invalid ABN format. Please check the number and try again.'
   246→      }, { status: 400 });
   247→    }
   248→
   249→    // --- 2. Check if ABR_GUID is configured --------------------------------
   250→    const isGuidConfigured = ABR_GUID &&
   251→      ABR_GUID !== 'REGISTER-TOMORROW-AT-ABR-BUSINESS-GOV-AU' &&
   252→      ABR_GUID !== 'temporary-abr-guid-for-build';
   253→
   254→    if (!isGuidConfigured) {
   255→      // Development mode: return mock data
   256→      if (ABR_DEV_MODE) {
   257→        console.log(`[ABN Lookup] Dev mode - returning mock data for ${abn}`);
   258→        const mockData = MOCK_COMPANIES[abn] || generateMockData(abn);
   259→        return NextResponse.json({
   260→          success: true,
   261→          data: mockData,
   262→          devMode: true,
   263→        });
   264→      }
   265→
   266→      // Production without GUID: return error with validation info
   267→      return NextResponse.json({
   268→        success: false,
   269→        error: 'ABR service not configured',
   270→        devMode: false,
   271→      }, { status: 503 });
   272→    }
   273→
   274→    // --- 3. Call ABR web service -------------------------------------------
   275→    const url = buildABRUrl(abn);
   276→    const abrResponse = await fetch(url, {
   277→      method: 'GET',
   278→      headers: { 'User-Agent': 'RateRight/1.0' },
   279→      signal: AbortSignal.timeout(10000),
   280→    });
   281→
   282→    if (!abrResponse.ok) {
   283→      const text = await abrResponse.text();
   284→      console.error(`[ABN Lookup] ABR error ${abrResponse.status}:`, text);
   285→      return NextResponse.json({
   286→        success: false,
   287→        error: `ABR service error: ${abrResponse.status}`,
   288→      }, { status: 502 });
   289→    }
   290→
   291→    const jsonpText = await abrResponse.text();
   292→    const abrData = stripJSONP(jsonpText);
   293→
   294→    // --- 4. Handle ABR business logic ---------------------------------------
   295→    if (abrData.Message) {
   296→      return NextResponse.json({
   297→        success: false,
   298→        error: abrData.Message
   299→      }, { status: 404 });
   300→    }
   301→
   302→    const result = normaliseABRResponse(abrData);
   303→
   304→    // --- 5. Director verification (async, non-blocking) --------------------
   305→    if (result.acn || userEmail) {
   306→      result.directorVerification = await verifyDirector(
   307→        abn,
   308→        result.acn,
   309→        userEmail || undefined
   310→      );
   311→    }
   312→
   313→    return NextResponse.json({
   314→      success: true,
   315→      data: result,
   316→    });
   317→
   318→  } catch (err: any) {
   319→    console.error('[ABN Lookup] Uncaught error:', err);
   320→    return NextResponse.json({
   321→      success: false,
   322→      error: 'Internal server error',
   323→    }, { status: 500 });
   324→  }
   325→}
   326→

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