Files
silo-server/web/src/pages/SetupWizard.tsx
T

116 lines
4.8 KiB
TypeScript

import { Navigate } from "react-router";
import { useAuth } from "@/hooks/useAuth";
import { useDocumentTitle } from "@/hooks/useDocumentTitle";
import { WizardProvider, useWizardContext } from "./setup-wizard/WizardContext";
import { useWizardSteps } from "./setup-wizard/useWizardSteps";
import { StepIndicator } from "./setup-wizard/StepIndicator";
import { AccountStep } from "./setup-wizard/steps/AccountStep";
import { ProfileStep } from "./setup-wizard/steps/ProfileStep";
import { LibraryStep } from "./setup-wizard/steps/LibraryStep";
import { ServerStorageStep } from "./setup-wizard/steps/ServerStorageStep";
import { IntegrationsStep } from "./setup-wizard/steps/IntegrationsStep";
import { DownloadsStep } from "./setup-wizard/steps/DownloadsStep";
import { RecommendationsStep } from "./setup-wizard/steps/RecommendationsStep";
import { NodesFinishStep } from "./setup-wizard/steps/NodesFinishStep";
import type { WizardStepId } from "./setup-wizard/useWizardSteps";
const STEP_TITLES: Record<WizardStepId, string> = {
account: "Create your account",
profile: "Add a profile",
server: "Server & storage",
integrations: "Integrations",
downloads: "Downloads",
recommendations: "Recommendations",
library: "Add a library",
nodes: "You're all set",
};
const STEP_DESCRIPTIONS: Record<WizardStepId, string> = {
account: "This will be the admin account for managing your server.",
profile: "Profiles let different people track their own watch history and preferences.",
server: "Configure core infrastructure. All fields are optional and can be changed later.",
integrations: "Configure subtitle providers for automatic subtitle downloading.",
downloads: "Allow users to download media files for offline viewing.",
recommendations: "AI-powered recommendations using embeddings. Requires pgvector.",
library: "Point Silo at your media files. You can add more libraries later.",
nodes: "Silo is ready. Start exploring or fine-tune in admin settings.",
};
function WizardContent() {
const { user, profiles, librariesLoading, profilesLoading } = useWizardContext();
const { steps, currentStep } = useWizardSteps();
const isAdmin = user?.role === "admin";
const profileComplete = profiles.length > 0;
if (profilesLoading || (user && profileComplete && isAdmin && librariesLoading)) {
return (
<div className="auth-shell items-start py-10 sm:py-14">
<div className="glass panel-border relative z-1 w-full max-w-2xl rounded-2xl p-7 sm:p-10">
<div className="space-y-3">
<div className="flex gap-1">
{Array.from({ length: 8 }).map((_, i) => (
<div key={i} className="bg-foreground/[0.06] h-1 flex-1 rounded-full" />
))}
</div>
<div className="bg-foreground/[0.06] h-3 w-32 animate-pulse rounded" />
</div>
<div className="bg-foreground/[0.06] mt-8 h-8 w-48 animate-pulse rounded" />
<div className="bg-foreground/[0.06] mt-3 h-4 w-72 animate-pulse rounded" />
</div>
</div>
);
}
return (
<div className="auth-shell items-start py-10 sm:py-14">
<div className="glass panel-border relative z-1 w-full max-w-2xl rounded-2xl p-7 sm:p-10">
{/* Header */}
<div className="mb-10">
<StepIndicator steps={steps} />
<h1 className="text-foreground mt-6 text-[1.7rem] leading-tight font-bold tracking-[-0.03em] sm:text-3xl">
{STEP_TITLES[currentStep]}
</h1>
<p className="text-muted-foreground mt-2 text-sm leading-relaxed">
{STEP_DESCRIPTIONS[currentStep]}
</p>
</div>
{/* Step content — keyed to trigger entrance animation on step change */}
<div key={currentStep} className="animate-[fade-in_0.25s_ease-out]">
{currentStep === "account" && <AccountStep />}
{currentStep === "profile" && <ProfileStep />}
{currentStep === "library" && <LibraryStep />}
{currentStep === "server" && <ServerStorageStep />}
{currentStep === "integrations" && <IntegrationsStep />}
{currentStep === "downloads" && <DownloadsStep />}
{currentStep === "recommendations" && <RecommendationsStep />}
{currentStep === "nodes" && <NodesFinishStep />}
</div>
</div>
</div>
);
}
export default function SetupWizard() {
const { user, loading, setupLoading, setupRequired } = useAuth();
useDocumentTitle("Setup");
if (loading || setupLoading) {
return <div className="text-muted-foreground p-8 text-sm">Loading...</div>;
}
if (!setupRequired && !user) {
return <Navigate to="/login" replace />;
}
if (user && user.role !== "admin") {
return <Navigate to="/profiles" replace />;
}
return (
<WizardProvider>
<WizardContent />
</WizardProvider>
);
}