import React, { useRef, useState, useEffect, useMemo } from "react"; import { Button } from "./ui/button"; import { Input } from "./ui/input"; import { Label } from "./ui/label"; import { Dialog, DialogContent, DialogTitle } from "./ui/dialog"; import type { User as UserType } from "../App"; import { toast } from "sonner"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"; import { ChevronLeft, ChevronRight } from "lucide-react"; import { Textarea } from "./ui/textarea"; // ✅ Add Bio step. Total steps: 5 const TOTAL_STEPS = 5; type InitQ = { id: string; title: string; placeholder?: string; }; const INIT_QUESTIONS: InitQ[] = [ { id: "course_goal", title: "What’s the single most important outcome you want from this course?", placeholder: "e.g., understand LLM basics, build a project, prep for an exam, apply to work…", }, { id: "background", title: "What’s your current background (major, job, or anything relevant)?", placeholder: "One sentence is totally fine.", }, { id: "ai_experience", title: "Have you worked with AI/LLMs before? If yes, at what level?", placeholder: "e.g., none / used ChatGPT / built small projects / research…", }, { id: "python_level", title: "How comfortable are you with Python? (Beginner / Intermediate / Advanced)", placeholder: "Type one: Beginner / Intermediate / Advanced", }, { id: "preferred_format", title: "What helps you learn best? (You can list multiple, separated by commas)", placeholder: "Step-by-step, examples, visuals, concise answers, Socratic questions…", }, { id: "pace", title: "What pace do you prefer from me? (Fast / Steady / Very detailed)", placeholder: "Type one: Fast / Steady / Very detailed", }, { id: "biggest_pain", title: "Where do you typically get stuck when learning technical topics?", placeholder: "Concepts, tools, task breakdown, math, confidence, time management…", }, { id: "support_pref", title: "When you’re unsure, how should I support you?", placeholder: "Hints first / guided questions / direct answer / ask then answer…", }, ]; interface OnboardingProps { user: UserType; onComplete: (user: UserType) => void; onSkip: () => void; } export function Onboarding({ user, onComplete, onSkip }: OnboardingProps) { const [currentStep, setCurrentStep] = useState(1); // Step 1: Basic const [name, setName] = useState(user.name ?? ""); const [email, setEmail] = useState(user.email ?? ""); // Step 2: Academic const [studentId, setStudentId] = useState(user.studentId ?? ""); const [department, setDepartment] = useState(user.department ?? ""); const [yearLevel, setYearLevel] = useState(user.yearLevel ?? ""); const [major, setMajor] = useState(user.major ?? ""); // Step 3: Preferences const [learningStyle, setLearningStyle] = useState(user.learningStyle ?? "visual"); const [learningPace, setLearningPace] = useState(user.learningPace ?? "moderate"); // Step 4: Bio (8 questions -> generate bio) const [bioQIndex, setBioQIndex] = useState(0); const [bioInput, setBioInput] = useState(""); const [bioAnswers, setBioAnswers] = useState>({}); const [bioSubmitting, setBioSubmitting] = useState(false); const [generatedBio, setGeneratedBio] = useState(user.bio ?? ""); const [bioReady, setBioReady] = useState(!!(user.bio && user.bio.trim().length > 0)); const currentBioQ = useMemo(() => INIT_QUESTIONS[bioQIndex], [bioQIndex]); // Optional: if user already has bio, mark ready. useEffect(() => { if (user.bio && user.bio.trim().length > 0) { setGeneratedBio(user.bio); setBioReady(true); } }, [user.bio]); // Step 5: Photo const [photoPreview, setPhotoPreview] = useState(user.avatarUrl ?? null); const fileInputRef = useRef(null); const handlePhotoSelect = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; if (!file.type.startsWith("image/")) { toast.error("Please select an image file"); return; } if (file.size > 2 * 1024 * 1024) { toast.error("File size must be less than 2MB"); return; } const reader = new FileReader(); reader.onload = (ev) => setPhotoPreview(ev.target?.result as string); reader.readAsDataURL(file); }; const handleChangePhotoClick = () => fileInputRef.current?.click(); const handlePrevious = () => { if (currentStep > 1) setCurrentStep((s) => s - 1); }; const handleSkip = () => onSkip(); // -------------------------- // Step 4: Bio generation flow // -------------------------- const handleBioNext = async () => { const v = bioInput.trim(); if (!v) return; const q = INIT_QUESTIONS[bioQIndex]; const nextAnswers = { ...bioAnswers, [q.id]: v }; setBioAnswers(nextAnswers); setBioInput(""); const nextIndex = bioQIndex + 1; // Continue questions if (nextIndex < INIT_QUESTIONS.length) { setBioQIndex(nextIndex); return; } // Last question -> submit to backend and generate bio // NOTE: use same backend logic as before; we do NOT touch parsing/storage logic. setBioSubmitting(true); try { const r = await fetch("/api/profile/init_submit", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ user_id: email.trim() || user.email, // prefer current email input answers: nextAnswers, language_preference: "English", }), }); if (!r.ok) throw new Error("init_submit failed"); const j = await r.json(); const bio = (j?.bio || "").toString(); if (!bio.trim()) { throw new Error("empty bio"); } setGeneratedBio(bio); setBioReady(true); toast.success("Bio generated!"); } catch (e) { toast.error("Failed to generate bio. Please try again."); // allow retry: keep last answer stored; user can edit generated flow by resetting if needed } finally { setBioSubmitting(false); } }; const handleBioReset = () => { setBioQIndex(0); setBioInput(""); setBioAnswers({}); setBioSubmitting(false); setGeneratedBio(""); setBioReady(false); }; // Main Next handler (respects Step 4 gating) const handleNext = async () => { // Step 1 validation (kept) if (currentStep === 1) { if (!name.trim() || !email.trim()) { toast.error("Please fill in all required fields"); return; } } // Step 4 gating: must finish + have bioReady before moving on if (currentStep === 4) { if (!bioReady) { // If still answering questions, Next acts as “Next question” if (bioQIndex < INIT_QUESTIONS.length) { await handleBioNext(); return; } // Safety: should not happen, but block toast.error("Please finish the Bio questions first."); return; } } if (currentStep < TOTAL_STEPS) setCurrentStep((s) => s + 1); else handleComplete(); }; const handleComplete = () => { if (!name.trim() || !email.trim()) { toast.error("Please fill in all required fields"); return; } // ✅ Bio now comes from Onboarding Step 4 const finalBio = (generatedBio || user.bio || "").trim() || undefined; const next: UserType = { ...user, name: name.trim(), email: email.trim(), studentId: studentId.trim() || undefined, department: department.trim() || undefined, yearLevel: yearLevel || undefined, major: major.trim() || undefined, learningStyle: learningStyle || undefined, learningPace: learningPace || undefined, avatarUrl: photoPreview || undefined, bio: finalBio, // ✅ sync to profile via your existing onComplete->save logic onboardingCompleted: true, }; onComplete(next); toast.success("Profile setup completed!"); }; const renderStepContent = () => { switch (currentStep) { case 1: return (

Basic Information

Let's start with your basic information

setName(e.target.value)} placeholder="Enter your full name" />
setEmail(e.target.value)} placeholder="Enter your email" />
); case 2: return (

Academic Background

Tell us about your academic information

setStudentId(e.target.value)} placeholder="Enter your student ID" />
setDepartment(e.target.value)} placeholder="Enter your department" />
setMajor(e.target.value)} placeholder="Enter your major" />
); case 3: return (

Learning Preferences

Help us personalize your learning experience

); case 4: return (

Profile Bio

Answer a few quick questions and we’ll generate a Bio that syncs to your profile.

{!bioReady ? (
{currentBioQ.title}
{currentBioQ.placeholder && (
{currentBioQ.placeholder}
)}
Question {bioQIndex + 1} of {INIT_QUESTIONS.length}