"use client"; import { useState, useCallback, useEffect } from "react"; import { useRouter } from "next/navigation"; import { Check, Plus, X, Loader2, ArrowRight, Building2, ArrowLeft, Link as LinkIcon, Hash, Settings2, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select } from "@/components/ui/select"; import { Card, CardContent } from "@/components/ui/card"; import PayrollJurisdictionStep, { type PayrollOnboardingValues } from "@/components/onboarding/PayrollJurisdictionStep"; import { setupOrganisation, createInvitations, joinOrganisationByCode, } from "@/lib/supabase/employees-actions"; import { savePayrollSettings } from "@/lib/supabase/payroll-settings-actions"; import { NATIVE_COUNTRIES, EU_COUNTRIES } from "@/lib/payroll-settings-types"; const COUNTRIES = [ { code: "AU", name: "Australia" }, { code: "US", name: "United States" }, { code: "GB", name: "United Kingdom" }, { code: "CA", name: "Canada" }, { code: "NZ", name: "New Zealand" }, ]; type JoinMethod = "invite" | "code"; export default function OnboardingPage() { const router = useRouter(); const [step, setStep] = useState(1); const [view, setView] = useState<"create" | "join">("create"); const [joinMethod, setJoinMethod] = useState("invite"); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [orgId, setOrgId] = useState(null); const [joinedOrgName, setJoinedOrgName] = useState(null); const [companyName, setCompanyName] = useState(""); const [slug, setSlug] = useState(""); const [country, setCountry] = useState("AU"); const [joinCode, setJoinCode] = useState(""); const [inviteEmail, setInviteEmail] = useState(""); const [invites, setInvites] = useState([]); useEffect(() => { if (companyName && !slug) { setSlug(companyName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")); } }, [companyName]); const addInvite = useCallback(() => { if (!inviteEmail.trim() || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(inviteEmail) || invites.includes(inviteEmail.trim())) return; setInvites((p) => [...p, inviteEmail.trim()]); setInviteEmail(""); }, [inviteEmail, invites]); const removeInvite = useCallback((i: number) => { setInvites((p) => p.filter((_, idx) => idx !== i)); }, []); const handleStep1 = async () => { if (!companyName.trim()) { setError("Company name is required"); return; } if (!slug.trim()) { setError("Slug is required"); return; } setError(null); setSaving(true); try { const result = await setupOrganisation(companyName.trim(), slug.trim(), country); if (!result.success || !result.organisationId) { setError(result.error || "Failed to create organisation"); return; } setOrgId(result.organisationId); setStep(2); } catch (e: any) { setError(e.message || "An error occurred"); } finally { setSaving(false); } }; const handleStep2 = async () => { setError(null); setSaving(true); try { if (invites.length > 0 && orgId) await createInvitations(invites, orgId); setStep(3); // Go to payroll jurisdiction step } catch (e: any) { setError(e.message || "An error occurred"); } finally { setSaving(false); } }; const handlePayrollSettings = async (values: PayrollOnboardingValues) => { setError(null); setSaving(true); try { await savePayrollSettings({ country_code: values.country_code, country_name: values.country_name, currency: values.currency, is_natively_supported: values.is_natively_supported, state_code: values.state_code || null, state_name: values.state_name || null, state_tax_rate: values.state_tax_rate ?? null, is_singapore_citizen_or_pr: values.is_singapore_citizen_or_pr ?? null, flat_income_tax_rate: values.flat_income_tax_rate ?? null, employer_pension_rate: values.employer_pension_rate ?? null, employer_tax_rate: values.employer_tax_rate ?? null, tax_free_allowance: values.tax_free_allowance ?? null, }); setStep(4); } catch (e: any) { setError(e.message || "Failed to save payroll settings"); } finally { setSaving(false); } }; const handleJoinByCode = async () => { if (!joinCode.trim()) { setError("Join code is required"); return; } setError(null); setSaving(true); try { const result = await joinOrganisationByCode(joinCode); if (!result.success) { setError(result.error || "Failed to join organisation"); return; } setJoinedOrgName(result.orgName || "your organisation"); setStep(3); // Go to payroll step } catch (e: any) { setError(e.message || "An error occurred"); } finally { setSaving(false); } }; return (

Vela

Let's get you set up

{[1, 2, 3, 4].map((s) => (
{s < step ? : s}
{s < 4 &&
}
))}
{error && (
{error}
)} {/* STEP 1: Create */} {step === 1 && view === "create" && (

Set up your workspace

Create your own organisation or join an existing one

or create your own
setCompanyName(e.target.value)} className="mt-1" placeholder="Acme Corp" />
setSlug(e.target.value)} className="mt-1" placeholder="acme-corp" />

This will be your workspace URL

Used for payroll jurisdiction

Already have a workspace?{" "}

)} {/* STEP 1: Join */} {step === 1 && view === "join" && (

Join a workspace

Use a join code to join an existing team

setJoinCode(e.target.value.toUpperCase().replace(/[^A-Z0-9-]/g, ""))} className="mt-1 font-mono text-lg tracking-wider text-center" placeholder="FIN-XXXXXX" maxLength={10} />

Enter the code your admin shared with you

)} {/* STEP 2: Invite Team */} {step === 2 && (

Invite your team

Add colleagues by email (optional)

setInviteEmail(e.target.value)} onKeyDown={(e) => e.key === "Enter" && addInvite()} placeholder="colleague@company.com" className="flex-1" />
{invites.length > 0 && (
{invites.map((email, i) => (
{email}
))}
)}
)} {/* STEP 3: Payroll Jurisdiction */} {step === 3 && ( setStep(2)} isLoading={saving} /> )} {/* STEP 4: Done */} {step === 4 && (

You're all set!

{joinedOrgName ? `Welcome to ${joinedOrgName} on Vela!` : companyName ? `Welcome to Vela, ${companyName}!` : "Welcome to Vela!"}

)}
); }