"use client"; import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { api } from "@/lib/api"; import Image from "next/image"; import { GradientSpinner } from "@/components/ui/GradientSpinner"; export default function OnboardingPage() { const router = useRouter(); const [step, setStep] = useState(1); const [loading, setLoading] = useState(false); const [initialLoading, setInitialLoading] = useState(true); const [error, setError] = useState(""); const showPasswordMismatch = error === "Passwords don't match"; const showPasswordTooShort = error === "Password must be at least 6 characters"; // Step 1: Account creation const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); // Check if user is already logged in and skip to step 2 useEffect(() => { const checkExistingSession = async () => { try { const user = await api.getCurrentUser(); if (user && !user.onboardingComplete) { // User exists but hasn't completed onboarding - skip to step 2 setStep(2); } } catch (error) { // Not logged in, stay on step 1 } finally { setInitialLoading(false); } }; checkExistingSession(); }, []); // Step 2: Integrations const [lidarr, setLidarr] = useState({ url: "", apiKey: "", enabled: false, }); const [audiobookshelf, setAudiobookshelf] = useState({ url: "", apiKey: "", enabled: false, }); const [soulseek, setSoulseek] = useState({ username: "", password: "", enabled: false, }); // Step 3: Enrichment const [enrichmentEnabled, setEnrichmentEnabled] = useState(true); const handleRegister = async (e: React.FormEvent) => { e.preventDefault(); setError(""); if (password !== confirmPassword) { setError("Passwords don't match"); return; } if (password.length < 6) { setError("Password must be at least 6 characters"); return; } setLoading(true); try { const response = await api.post<{ token: string; user: any }>( "/onboarding/register", { username, password } ); // Store the JWT token for subsequent API calls if (response.token) { api.setToken(response.token); } setStep(2); } catch (err: any) { // Check if user already exists if (err.message?.includes("already taken")) { setError( "Username already taken. If this is you, please refresh and continue where you left off." ); } else { setError( err.response?.data?.error || err.message || "Failed to create account" ); } } finally { setLoading(false); } }; const testConnection = async ( type: "lidarr" | "audiobookshelf" | "soulseek" ) => { setError(""); setLoading(true); try { // Use dedicated test endpoints that actually verify the connection if (type === "lidarr") { if (!lidarr.url || !lidarr.apiKey) { throw new Error("URL and API key are required"); } await api.post("/system-settings/test-lidarr", { url: lidarr.url, apiKey: lidarr.apiKey, }); } else if (type === "audiobookshelf") { if (!audiobookshelf.url || !audiobookshelf.apiKey) { throw new Error("URL and API key are required"); } await api.post("/system-settings/test-audiobookshelf", { url: audiobookshelf.url, apiKey: audiobookshelf.apiKey, }); } else if (type === "soulseek") { if (!soulseek.username || !soulseek.password) { throw new Error("Username and password are required"); } await api.post("/system-settings/test-soulseek", { username: soulseek.username, password: soulseek.password, }); } setError(`${type} connected successfully!`); } catch (err: any) { const errorMessage = err.response?.data?.error || err.response?.data?.details || err.message || `Failed to connect to ${type}`; setError(errorMessage); } finally { setLoading(false); } }; const handleNextStep = async () => { setError(""); setLoading(true); try { if (step === 2) { // Save all integration configs await Promise.all([ api.post("/onboarding/lidarr", lidarr), api.post("/onboarding/audiobookshelf", audiobookshelf), api.post("/onboarding/soulseek", soulseek), ]); setStep(3); } else if (step === 3) { // Save enrichment preference and complete await api.post("/onboarding/enrichment", { enabled: enrichmentEnabled, }); await api.post("/onboarding/complete"); // Redirect to sync page router.push("/sync"); } } catch (err: any) { setError( err.response?.data?.error || "Failed to save configuration" ); } finally { setLoading(false); } }; return (
Loading...
Welcome to your personal music streaming platform
Let's get you set up with your personal music library
Optional integrations to enhance your music library
{error}
Enhance your library with additional metadata
Enrichment fetches additional metadata like artist bios, high-quality images, genres, and relationships from external sources. This powers smart features and provides a richer listening experience.
Recommended for the best experience
{error}
© 2025 Lidify. Your music, your way.
{description}
These are your Soulseek network credentials, not your Slskd login
> ) : ( onApiKeyChange?.(e.target.value) } placeholder="API Key" className="w-full px-4 py-2.5 bg-white/5 border border-white/10 rounded-lg text-white text-sm placeholder:text-white/40 focus:outline-none focus:ring-2 focus:ring-brand/30 focus:border-transparent transition-all " /> )}Peer-to-peer music discovery
Create an account at{" "} slsknet.org