import React, { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import { Box, Button, IconButton, LinearProgress, Paper, Stack, Typography } from "@mui/material"; import CheckCircleIcon from "@mui/icons-material/CheckCircle"; import CloseIcon from "@mui/icons-material/Close"; import RadioButtonUncheckedIcon from "@mui/icons-material/RadioButtonUnchecked"; import { alpha, useTheme } from "@mui/material/styles"; import { api } from "../api"; import { useI18n } from "../i18n/I18nProvider"; import { getUserKeyFromToken } from "../themePrefs"; type MeResponse = { email?: string | null; firstName?: string | null; lastName?: string | null; displayName?: string | null; }; type CareerProfileResponse = { cvText?: string | null; profile?: { summary?: string[]; jobs?: unknown[]; education?: unknown[]; projects?: unknown[]; certifications?: unknown[]; languages?: unknown[]; otherSections?: unknown[]; skills?: string[]; interests?: string[]; }; }; type ConnectionStatus = { connected?: boolean }; export default function OnboardingChecklist({ hasJobs }: { hasJobs: boolean }) { const theme = useTheme(); const navigate = useNavigate(); const { t } = useI18n(); const dismissalKey = `onboardingDismissed:${getUserKeyFromToken()}`; const [dismissed, setDismissed] = useState(() => window.localStorage.getItem(dismissalKey) === "true"); const [status, setStatus] = useState<{ profile: boolean; cv: boolean; email: boolean } | null>(null); useEffect(() => { let active = true; Promise.all([ api.get("/auth/me"), api.get("/career/profile"), Promise.all(["/gmail/status", "/microsoft-graph/status", "/imap/status"].map((url) => api.get(url).then((r) => Boolean(r.data?.connected)).catch(() => false), )), ]).then(([me, career, connections]) => { if (!active) return; const profile = career.data?.profile; const structuredCount = [ ...(profile?.summary ?? []), ...(profile?.jobs ?? []), ...(profile?.education ?? []), ...(profile?.projects ?? []), ...(profile?.certifications ?? []), ...(profile?.languages ?? []), ...(profile?.otherSections ?? []), ...(profile?.skills ?? []), ...(profile?.interests ?? []), ].length; setStatus({ profile: Boolean(me.data?.displayName?.trim() || me.data?.firstName?.trim() || me.data?.lastName?.trim()), cv: structuredCount > 0 || Boolean(career.data?.cvText?.trim()), email: connections.some(Boolean), }); }).catch(() => { if (active) setStatus({ profile: false, cv: false, email: false }); }); return () => { active = false; }; }, []); if (!status || dismissed) return null; const steps = [ { done: true, label: t("onboardingStepSignup"), actionLabel: "" }, { done: true, label: t("onboardingStepVerify"), actionLabel: "" }, { done: status.profile, label: t("onboardingStepProfile"), action: () => navigate("/profile"), actionLabel: t("onboardingStepProfileAction") }, { done: status.cv, label: t("onboardingStepCv"), action: () => navigate("/career"), actionLabel: t("onboardingStepCvAction") }, { done: status.email, label: t("onboardingStepEmail"), action: () => navigate("/settings/connected-accounts"), actionLabel: t("onboardingStepEmailAction") }, { done: hasJobs, label: t("onboardingStepJob"), action: () => navigate("/jobs"), actionLabel: t("onboardingStepJobAction") }, ]; const completed = steps.filter((step) => step.done).length; if (completed === steps.length) return null; return ( {t("onboardingTitle")} { window.localStorage.setItem(dismissalKey, "true"); setDismissed(true); }} > {t("onboardingBody")} {completed} / {steps.length} {steps.map((step) => ( {step.done ? : } {step.label} {!step.done && step.action ? : null} ))} ); }