"use client"; import { useState, useCallback, useEffect, useRef } from "react"; import { useTranslations } from "next-intl"; import { Card } from "@/components/ui/card"; import { PACKAGE_CATALOG, type PackageDef } from "@/lib/packages"; type Step = "welcome" | "configure" | "billing" | "confirm"; const STEPS: Step[] = ["welcome", "configure", "billing", "confirm"]; // Inline fallbacks — only used if the API call to /api/workspace-defaults fails const FALLBACK_SOUL = `# AI Assistant You are a helpful AI assistant for {company}. You are professional, concise, and friendly. ## Guidelines - Answer questions accurately and helpfully - If you don't know something, say so - Keep responses clear and to the point - Respect privacy and confidentiality `; const FALLBACK_AGENTS = `# Agents On session start, read the following workspace files in order: 1. SOUL.md — your personality and behavioural guidelines 2. TOOLS.md — available tools and how to use them 3. USER.md — information about the current user (if present) Follow the instructions in SOUL.md for every interaction. `; const FALLBACK_TOOLS = `# Tools The following tools are available to you as an AI assistant. ## LLM You have access to a large language model for text generation, summarisation, translation, and general question answering. `; const CATEGORIES = [ { key: "channel" as const, labelKey: "categories.channels" }, { key: "skill" as const, labelKey: "categories.skills" }, ] as const; interface WizardProps { orgName: string; onComplete: () => void; } export function OnboardingWizard({ orgName, onComplete }: WizardProps) { const t = useTranslations("onboarding"); const tPkg = useTranslations("packages"); const tCommon = useTranslations("common"); const [step, setStep] = useState("welcome"); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(""); const [advancedOpen, setAdvancedOpen] = useState(false); const [defaultsLoaded, setDefaultsLoaded] = useState(false); const [config, setConfig] = useState({ agentName: "Assistant", soulMd: FALLBACK_SOUL.replace("{company}", orgName), agentsMd: FALLBACK_AGENTS, packages: [] as string[], billingAddress: { company: orgName, street: "", city: "", postalCode: "", country: "CH", }, billingNotes: "", }); // TOOLS.md preview — readonly, auto-generated const [toolsMdPreview, setToolsMdPreview] = useState(FALLBACK_TOOLS); // Per-package collected secrets: { "telegram": { "bot-token": "123:ABC" }, ... } const [packageSecrets, setPackageSecrets] = useState< Record> >({}); // Per-package disclaimer acceptance const [disclaimerAccepted, setDisclaimerAccepted] = useState< Record >({}); // Fetch DB-stored defaults on mount useEffect(() => { fetch(`/api/workspace-defaults?orgName=${encodeURIComponent(orgName)}`) .then((r) => (r.ok ? r.json() : null)) .then((data) => { if (data) { setConfig((prev) => ({ ...prev, soulMd: data.soulMd ?? prev.soulMd, agentsMd: data.agentsMd ?? prev.agentsMd, })); setToolsMdPreview(data.toolsMd ?? FALLBACK_TOOLS); setDefaultsLoaded(true); } }) .catch(() => { /* use inline fallbacks */ }); }, [orgName]); // Re-fetch TOOLS.md preview when packages change const packagesKey = config.packages.sort().join(","); const prevPackagesKey = useRef(packagesKey); useEffect(() => { if (prevPackagesKey.current === packagesKey && defaultsLoaded) return; prevPackagesKey.current = packagesKey; fetch( `/api/workspace-defaults?orgName=${encodeURIComponent(orgName)}&packages=${encodeURIComponent(packagesKey)}` ) .then((r) => (r.ok ? r.json() : null)) .then((data) => { if (data?.toolsMd) setToolsMdPreview(data.toolsMd); }) .catch(() => {}); }, [packagesKey, orgName, defaultsLoaded]); const stepIndex = STEPS.indexOf(step); const goNext = () => { if (stepIndex < STEPS.length - 1) setStep(STEPS[stepIndex + 1]); }; const goBack = () => { if (stepIndex > 0) setStep(STEPS[stepIndex - 1]); }; const togglePackage = useCallback((pkgId: string) => { setConfig((prev) => { const removing = prev.packages.includes(pkgId); if (removing) { setPackageSecrets((s) => { const next = { ...s }; delete next[pkgId]; return next; }); setDisclaimerAccepted((d) => { const next = { ...d }; delete next[pkgId]; return next; }); } return { ...prev, packages: removing ? prev.packages.filter((p) => p !== pkgId) : [...prev.packages, pkgId], }; }); }, []); const updateSecret = useCallback( (pkgId: string, key: string, value: string) => { setPackageSecrets((prev) => ({ ...prev, [pkgId]: { ...(prev[pkgId] || {}), [key]: value }, })); }, [] ); // Validate that all secret-requiring enabled packages have complete credentials const packageCredentialsValid = (): boolean => { for (const pkgId of config.packages) { const def = PACKAGE_CATALOG.find((p) => p.id === pkgId); if (!def?.requiresSecrets) continue; const secrets = packageSecrets[pkgId] || {}; for (const field of def.secrets || []) { if (!secrets[field.key]?.trim()) return false; } if (def.disclaimerKey && !disclaimerAccepted[pkgId]) return false; } return true; }; const handleSubmit = async () => { setSubmitting(true); setError(""); try { // Build secrets payload — only for packages that require them const secretsPayload: Record> = {}; for (const pkgId of config.packages) { const def = PACKAGE_CATALOG.find((p) => p.id === pkgId); if (def?.requiresSecrets && packageSecrets[pkgId]) { secretsPayload[pkgId] = packageSecrets[pkgId]; } } const res = await fetch("/api/onboarding", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ...config, packageSecrets: Object.keys(secretsPayload).length > 0 ? secretsPayload : undefined, }), }); if (!res.ok) { const data = await res.json(); throw new Error(data.error || "Submission failed"); } onComplete(); } catch (err: any) { setError(err.message); } finally { setSubmitting(false); } }; // Step indicator const StepIndicator = () => (
{STEPS.map((s, i) => (
{i < STEPS.length - 1 && (
)}
))}
); return (
{/* Step: Welcome */} {step === "welcome" && (

{t("welcomeTitle")}

{t("welcomeDescription")}

{["swissHosted", "privacy", "customizable"].map((key) => (
{t(`welcomeFeature_${key}`)}
))}
)} {/* Step: Configure */} {step === "configure" && (

{t("configureTitle")}

{t("configureDescription")}

setConfig((prev) => ({ ...prev, agentName: e.target.value })) } className="w-full px-3 py-2 bg-surface-2 border border-border rounded-lg text-sm text-text-primary focus:outline-none focus:ring-1 focus:ring-accent focus:border-accent transition-colors" />