import { useState, useEffect } from "react"; import { Link, useNavigate } from "react-router-dom"; import Layout from "@/components/Layout"; import LoadingScreen from "@/components/LoadingScreen"; import { useMemo } from "react"; import { SkeletonOnboardingStep } from "@/components/Skeleton"; import UserTypeSelection from "@/components/onboarding/UserTypeSelection"; import PersonalInfo from "@/components/onboarding/PersonalInfo"; import Experience from "@/components/onboarding/Experience"; import Interests from "@/components/onboarding/Interests"; import Welcome from "@/components/onboarding/Welcome"; import { useAuth } from "@/contexts/AuthContext"; import { aethexUserService, aethexAchievementService, } from "@/lib/aethex-database-adapter"; import { aethexToast } from "@/lib/aethex-toast"; export type UserType = "game-developer" | "client" | "member" | "customer"; export interface OnboardingData { userType: UserType | null; personalInfo: { firstName: string; lastName: string; email: string; company?: string; }; experience: { level: string; skills: string[]; previousProjects?: string; }; interests: { primaryGoals: string[]; preferredServices: string[]; }; } const initialData: OnboardingData = { userType: null, personalInfo: { firstName: "", lastName: "", email: "", company: "", }, experience: { level: "", skills: [], previousProjects: "", }, interests: { primaryGoals: [], preferredServices: [], }, }; export default function Onboarding() { const [currentStep, setCurrentStep] = useState(0); const [data, setData] = useState(initialData); const [isLoading, setIsLoading] = useState(true); const [isTransitioning, setIsTransitioning] = useState(false); const [isFinishing, setIsFinishing] = useState(false); const navigate = useNavigate(); const { user, refreshProfile } = useAuth(); const steps = [ { title: "Choose Your Path", component: UserTypeSelection }, { title: "Personal Information", component: PersonalInfo }, { title: "Experience Level", component: Experience }, { title: "Interests & Goals", component: Interests }, { title: "Welcome to AeThex", component: Welcome }, ]; useEffect(() => { const timer = setTimeout(() => { setIsLoading(false); }, 1200); return () => clearTimeout(timer); }, []); const updateData = (newData: Partial) => { setData((prev) => ({ ...prev, ...newData })); }; const nextStep = () => { if (currentStep < steps.length - 1) { setIsTransitioning(true); setTimeout(() => { setCurrentStep((prev) => prev + 1); setIsTransitioning(false); }, 300); } }; const prevStep = () => { if (currentStep > 0) { setIsTransitioning(true); setTimeout(() => { setCurrentStep((prev) => prev - 1); setIsTransitioning(false); }, 300); } }; const CurrentStepComponent = steps[currentStep].component; // Precompute decorative particles using useMemo at top-level to avoid hooks in JSX const particles = useMemo(() => { if (typeof window === "undefined") return []; return Array.from({ length: 8 }).map(() => ({ left: `${Math.floor(Math.random() * 100)}%`, top: `${Math.floor(Math.random() * 100)}%`, delay: `${Math.random().toFixed(2)}s`, duration: `${3 + Math.floor(Math.random() * 2)}s`, })); }, []); const finishOnboarding = async () => { if (!user) { navigate("/login"); return; } setIsFinishing(true); try { const userTypeMap: Record = { "game-developer": "game_developer", client: "client", member: "community_member", customer: "customer", }; const payload = { username: `${data.personalInfo.firstName || user.email?.split("@")[0] || "user"}`, full_name: `${data.personalInfo.firstName} ${data.personalInfo.lastName}`.trim(), user_type: (userTypeMap[data.userType || "member"] as any) || "community_member", experience_level: (data.experience.level as any) || "beginner", bio: data.experience.previousProjects || undefined, } as any; // Ensure profile via server (uses service role) const ensureResp = await fetch(`/api/profile/ensure`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: user.id, profile: payload }), }); // If server endpoint missing (404) or returns server error, fallback to direct Supabase client if (!ensureResp.ok) { // If endpoint not found, attempt client-side upsert via Supabase SDK if (ensureResp.status === 404) { try { await aethexUserService.updateProfile(user.id, payload as any); } catch (err) { const text = await ensureResp.text().catch(() => ""); let parsedError: any = undefined; try { parsedError = JSON.parse(text); } catch {} const message = parsedError?.error || text || `HTTP ${ensureResp.status}`; throw new Error( `Server endpoint missing and client fallback failed: ${message}`, ); } } else { const text = await ensureResp.text().catch(() => ""); let parsedError: any = undefined; try { parsedError = JSON.parse(text); } catch {} const message = parsedError?.error || text || `HTTP ${ensureResp.status}`; throw new Error(message); } } // Fire-and-forget interests via server const interests = Array.from( new Set([ ...(data.interests.primaryGoals || []), ...(data.interests.preferredServices || []), ]), ); Promise.allSettled([ interests.length ? fetch(`/api/interests`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ user_id: user.id, interests }), }) : Promise.resolve(), aethexAchievementService.checkAndAwardOnboardingAchievement(user.id), ]).catch(() => undefined); // Mark onboarding complete locally (UI fallback) try { localStorage.setItem("onboarding_complete", "1"); } catch {} // Refresh profile so UI updates immediately await refreshProfile(); // Success toast aethexToast.success({ title: "You're all set!", description: "Profile setup complete. Welcome to your dashboard.", }); // Navigate after success (with hard redirect fallback) navigate("/dashboard", { replace: true }); setTimeout(() => { if (location.pathname.includes("onboarding")) { window.location.replace("/dashboard"); } }, 400); } catch (e) { function formatError(err: any) { if (!err) return "Unknown error"; if (typeof err === "string") return err; if (err instanceof Error) return err.message + (err.stack ? `\n${err.stack}` : ""); if ((err as any).message) return (err as any).message; try { return JSON.stringify(err); } catch { return String(err); } } const formatted = formatError(e as any); console.error("Finalize onboarding failed:", formatted, e); aethexToast.error({ title: "Onboarding failed", description: formatted || "Please try again", }); } finally { setIsFinishing(false); } }; if (isLoading) { return ( ); } return (
{/* Progress Bar */}

Join AeThex

Step {currentStep + 1} of {steps.length}
Already have an account?{" "} Sign In
{/* Step Indicators */}
{steps.map((_, index) => (
))}
{/* Step Content */}

{steps[currentStep].title}

{isTransitioning ? ( ) : (
{currentStep === steps.length - 1 ? ( ) : ( )}
)}
{/* Floating particles effect (performance-friendly) */}
{particles.map((p, i) => (
))}
); }