"use client"; import { useState } from "react"; import { useRouter } from "next/navigation"; import { Loader2, Save } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { ConfirmDialog } from "@/components/admin/ui/confirm-dialog"; import { authClient, signOut } from "@/lib/auth/auth-client"; import { VOICE_CATALOG } from "@/lib/ai/voices"; import { LANGUAGES } from "@/lib/episodes/options"; import { savePreferencesAction, deleteAccountAction } from "@/app/(app)/settings/actions"; const NO_VOICE = "__none__"; interface Preferences { defaultVoiceId: string | null; defaultLanguage: string; emailOnEpisodeReady: boolean; productEmails: boolean; } export function SettingsClient({ name, email, preferences, }: { name: string; email: string; preferences: Preferences; }) { const router = useRouter(); const [displayName, setDisplayName] = useState(name); const [savingProfile, setSavingProfile] = useState(false); const [savingPw, setSavingPw] = useState(false); // Defaults const [voiceId, setVoiceId] = useState(preferences.defaultVoiceId ?? NO_VOICE); const [language, setLanguage] = useState(preferences.defaultLanguage); const [savingDefaults, setSavingDefaults] = useState(false); // Notifications const [emailOnReady, setEmailOnReady] = useState(preferences.emailOnEpisodeReady); const [productEmails, setProductEmails] = useState(preferences.productEmails); const [savingNotif, setSavingNotif] = useState(false); // Danger zone const [confirmEmail, setConfirmEmail] = useState(""); async function saveProfile(e: React.FormEvent) { e.preventDefault(); setSavingProfile(true); const { error } = await authClient.updateUser({ name: displayName }); setSavingProfile(false); if (error) toast.error(error.message ?? "Could not update"); else { toast.success("Profile updated"); router.refresh(); } } async function changePassword(e: React.FormEvent) { e.preventDefault(); const form = new FormData(e.currentTarget); setSavingPw(true); const { error } = await authClient.changePassword({ currentPassword: String(form.get("current")), newPassword: String(form.get("new")), revokeOtherSessions: true, }); setSavingPw(false); if (error) toast.error(error.message ?? "Could not change password"); else { toast.success("Password changed"); e.currentTarget.reset(); } } async function saveDefaults() { setSavingDefaults(true); const res = await savePreferencesAction({ defaultVoiceId: voiceId === NO_VOICE ? null : voiceId, defaultLanguage: language, }); setSavingDefaults(false); if (res.ok) toast.success("Defaults saved"); else toast.error(res.error ?? "Could not save"); } async function saveNotifications(next: Partial) { const emailVal = next.emailOnEpisodeReady ?? emailOnReady; const productVal = next.productEmails ?? productEmails; setEmailOnReady(emailVal); setProductEmails(productVal); setSavingNotif(true); const res = await savePreferencesAction({ emailOnEpisodeReady: emailVal, productEmails: productVal, }); setSavingNotif(false); if (!res.ok) { toast.error(res.error ?? "Could not save"); // Revert optimistic state. setEmailOnReady(preferences.emailOnEpisodeReady); setProductEmails(preferences.productEmails); } else { toast.success("Notification preferences saved"); } } return (
Profile Update your name and see your email.
setDisplayName(e.target.value)} />
Password Change your account password.
Defaults Pre-select a voice and language when creating new episodes.
Notifications Choose which emails you want to receive.

Episode ready

Email me when an episode finishes generating.

saveNotifications({ emailOnEpisodeReady: v })} aria-label="Email me when an episode is ready" />

Product updates

Occasional product news, tips and announcements.

saveNotifications({ productEmails: v })} aria-label="Receive product update emails" />
Danger zone Permanently delete your account and all of your episodes. This cannot be undone. Delete account} title="Delete your account?" description="This permanently deletes your account, every episode, series, and all generated content. This action is irreversible." confirmLabel="Delete my account" successMessage="Account deleted" body={
setConfirmEmail(e.target.value)} placeholder={email} autoComplete="off" />
} onConfirm={async () => { const res = await deleteAccountAction(confirmEmail); if (res.ok) { await signOut(); router.push("/"); } return res; }} />
); }