import React from "react"; import { Box, Button, Typography, Stack, styled, } from "@mui/material"; import theme from "../theme.jsx"; import { toast } from "react-toastify"; import { useNavigate } from 'react-router-dom'; // Simple icon placeholders; replace with proper assets if desired const StepIcon = styled("div")(({ completed }) => ({ width: 28, height: 28, borderRadius: "50%", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: 700, fontSize: 14, color: completed ? "#0C111D" : "#ffffff", background: completed ? "#43D17C" : "#2F2F2F", border: completed ? "1px solid #43D17C" : "1px solid rgba(255,255,255,0.15)", flexShrink: 0, })); // Single continuous rail will be drawn once for the entire steps list const StepCard = styled(Box)(({ completed, flash }) => ({ display: "flex", gap: "14px", minHeight: 60, backgroundColor: "#212121", border: `1px solid ${flash ? "#f87171" : completed ? "#43D17C" : "rgba(255, 255, 255, 0.1)"}`, borderRadius: "12px", padding: "16px", width: "100%", flex: 1, minWidth: 0, })); const PrimaryButton = styled(Button)({ background: "linear-gradient(90deg, #FF8544 0%, #FB47A0 100%)", color: "#fff", borderRadius: 6, textTransform: "none", fontWeight: 600, fontSize: 14, padding: "8px 20px", "&:hover": { opacity: 0.95, background: "linear-gradient(90deg, #FF8544 0%, #FB47A0 100%)", }, }); const SecondaryButton = styled(Button)({ color: "#FF8544", borderRadius: 6, textTransform: "none", fontWeight: 600, fontSize: 14, padding: "8px 12px", "&:hover": { backgroundColor: "rgba(255, 255, 255, 0.08)", }, "&.Mui-disabled": { color: "rgba(255, 255, 255, 0.5)", backgroundColor: "rgba(255, 255, 255, 0.06)", }, }); const StepItem = React.forwardRef(({ step, iconRef }, ref) => ( {step.index} {step.title} {step.description && ( {step.description} )} {step.secondaryCta && ( {step.secondaryCta.label} )} {step.primaryCta && ( {step.primaryCta.label} )} )); const DashboardOnboarding = ({ open, onClose, headerTitle = "Get started with your Dashboard", headerSubtitle = "Follow these steps to unlock insights.", footer, globalUrl, onExplore, setOnboardingOpen, isProdStatusOn, isCloud, }) => { // Internal completion state only; handlers are defined separately const [completed, setCompleted] = React.useState({ docs: false, apps: false, workflow: false, wait: false, invite: false, }); const [checkingApps, setCheckingApps] = React.useState(false); const [checkingWait, setCheckingWait] = React.useState(false); const [flashKeys, setFlashKeys] = React.useState([]); const [waitProgress, setWaitProgress] = React.useState(0); const navigate = useNavigate(); // Load persisted completion state React.useEffect(() => { try { const raw = localStorage.getItem("dashboard_onboarding_completed"); if (!raw) return; const data = JSON.parse(raw); if (data && typeof data === "object") { setCompleted((prev) => ({ ...prev, ...data })); } } catch {} }, []); // Persist completion state React.useEffect(() => { try { localStorage.setItem("dashboard_onboarding_completed", JSON.stringify(completed)); } catch {} }, [completed]); // Handlers const handleDocsClick = React.useCallback(() => { window.open('/docs', '_blank'); setCompleted((prev) => ({ ...prev, docs: true })); }, []); const handleDiscoverApps = React.useCallback(() => { window.open('/apps?tab=discover_apps', '_blank'); }, []); const handleCheckAppsStatus = React.useCallback(async () => { if (checkingApps) return; setCheckingApps(true); try { const resp = await fetch(`${globalUrl}/api/v1/apps`, { method: 'GET', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, credentials: 'include', }); if (resp.status !== 200) return; const data = await resp.json(); let count = 0; if (Array.isArray(data)) { count = data.length; } else if (data && typeof data === 'object') { count = Object.keys(data).length; } if (count >= 3) { setCompleted((prev) => ({ ...prev, apps: true })); } } catch (_) { // ignore } finally { setCheckingApps(false); } }, [checkingApps]); const handleOpenWorkflow = React.useCallback(() => { window.open('/workflows/b658f2a0-7316-40d9-97ed-350a54fe3adc', '_blank'); setCompleted((prev) => ({ ...prev, workflow: true })); }, []); const handleWaitCheck = React.useCallback(async () => { if (checkingWait) return; setCheckingWait(true); try { const days = 5; const url = `${globalUrl}/api/v1/stats/workflow_executions_finished?days=${days}`; const resp = await fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, credentials: 'include', }); if (resp.status !== 200) return; const data = await resp.json(); const entries = Array.isArray(data?.entries) ? data.entries : []; const now = new Date(); let successDays = 0; for (let i = 0; i < entries.length; i++) { const e = entries[i]; const d = new Date(e?.Date || e?.date || e?.time || e?.timestamp); const diffDays = Math.floor((now - d) / (24 * 60 * 60 * 1000)); const value = Number(e?.Value ?? e?.value ?? 0); if (!isNaN(diffDays) && diffDays <= 4 && value > 0) { successDays += 1; } } const simulated = Math.min(5, successDays); if (simulated < 5) { setWaitProgress(simulated); setFlashKeys(["wait"]); setTimeout(() => setFlashKeys([]), 800); setCheckingWait(false); } else { setCompleted((prev) => ({ ...prev, wait: true })); setCheckingWait(false); } } catch (_) { // ignore } finally { setCheckingWait(false); } setCheckingWait(false); }, [checkingWait, globalUrl]); const handleOpenUsers = React.useCallback(() => { window.open('/admin?tab=users', '_blank'); setCompleted((prev) => ({ ...prev, invite: true })); }, []); const steps = [ { index: 1, key: 'docs', title: 'Read our docs to understand Shuffle', description: 'Explore the basics of Shuffle in our documentation. It will help you understand the platform and how to use it.', primaryCta: { label: 'Read docs', onClick: handleDocsClick }, completed: completed.docs, }, { index: 2, key: 'apps', title: 'Activate at least 3 apps', description: 'Go to Discover Apps and enable your first three integrations.', primaryCta: { label: 'Discover apps', onClick: handleDiscoverApps }, secondaryCta: { label: checkingApps ? 'Checking…' : 'Check status', onClick: handleCheckAppsStatus, disabled: checkingApps }, completed: completed.apps, }, { index: 3, key: 'workflow', title: 'Save a public workflow and start its scheduler', description: 'Open the public workflow, save it to your org, and start a daily scheduler.', primaryCta: { label: 'Open public workflow', onClick: handleOpenWorkflow }, completed: completed.workflow, }, { index: 4, key: 'wait', title: `Wait for 5 days of runs${completed.wait ? '' : waitProgress > 0 ? ` (${waitProgress}/5)` : ''}`, description: 'We will show daily stats after 5 runs. Come back to check again.', primaryCta: { label: checkingWait ? 'Checking…' : 'Check status', onClick: handleWaitCheck, disabled: checkingWait }, completed: completed.wait, }, { index: 5, key: 'invite', title: 'Invite your team members', description: 'Add teammates to collaborate in your org.', primaryCta: { label: 'Open users page', onClick: handleOpenUsers }, completed: completed.invite, }, ]; const mandatoryKeys = ['docs', 'apps', 'workflow', 'wait']; const handleFinalDone = React.useCallback(() => { const missing = mandatoryKeys.filter((k) => !completed[k]); if (missing.length === 0) { try { localStorage.setItem("dashboard_onboarding_complete", "true"); } catch {} if (typeof onExplore === 'function') { try { onExplore(); } catch {} } if (onClose) onClose(); return; } setFlashKeys(missing); setTimeout(() => setFlashKeys([]), 800); }, [completed, onClose, onExplore]); if (!open) return null; return ( {/* Blur overlay with visible background */} {/* Modal container */} {/* Header */} {headerTitle} {headerSubtitle} {!isCloud ? ( { navigate("/admin?admin_tab=billingstats") }} > {isProdStatusOn ? "Production" : "NOT Production"} ) : null} {/* Steps list with a single continuous rail */} {/* Base grey rail */} {/* Green segments between consecutive completed steps */} {steps.map((s, i) => ({ s, i })) .filter(({ i }) => i < steps.length - 1) .filter(({ i }) => steps[i].completed && steps[i + 1].completed) .map(({ i }) => ( ))} {steps.map((step, idx) => ( ))} {/* Footer */} {footer} Explore { setOnboardingOpen(false) toast.warn("The dashboard is not fully set up yet. You can complete the steps later from the onboarding section.", { timeout: 10000 }) }} sx={{ fontSize: 14, padding: "8px 60px", }} > Skip for now ); }; export default DashboardOnboarding;