"use client"; import { useState } from "react"; import * as React from "react"; import { useSession } from "next-auth/react"; import { Download, Upload, User, Database, AlertTriangle, Shield, FileText, Users, Building, Key, Eye, FileUp, ChevronDown, Info, } from "lucide-react"; import { api } from "~/trpc/react"; import { Button } from "~/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "~/components/ui/card"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "~/components/ui/collapsible"; import { Input } from "~/components/ui/input"; import { Label } from "~/components/ui/label"; import { Textarea } from "~/components/ui/textarea"; import { Badge } from "~/components/ui/badge"; import { toast } from "sonner"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "~/components/ui/alert-dialog"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "~/components/ui/dialog"; export function SettingsContent() { const { data: session } = useSession(); const [name, setName] = useState(""); const [deleteConfirmText, setDeleteConfirmText] = useState(""); const [importData, setImportData] = useState(""); const [isImportDialogOpen, setIsImportDialogOpen] = useState(false); const [importMethod, setImportMethod] = useState<"file" | "paste">("file"); // Password change state const [currentPassword, setCurrentPassword] = useState(""); const [newPassword, setNewPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); const [showCurrentPassword, setShowCurrentPassword] = useState(false); const [showNewPassword, setShowNewPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); // Queries const { data: profile, refetch: refetchProfile } = api.settings.getProfile.useQuery(); const { data: dataStats } = api.settings.getDataStats.useQuery(); // Mutations const updateProfileMutation = api.settings.updateProfile.useMutation({ onSuccess: () => { toast.success("Profile updated successfully"); void refetchProfile(); }, onError: (error: { message: string }) => { toast.error(`Failed to update profile: ${error.message}`); }, }); const changePasswordMutation = api.settings.changePassword.useMutation({ onSuccess: () => { toast.success("Password changed successfully"); setCurrentPassword(""); setNewPassword(""); setConfirmPassword(""); }, onError: (error: { message: string }) => { toast.error(`Failed to change password: ${error.message}`); }, }); const exportDataQuery = api.settings.exportData.useQuery(undefined, { enabled: false, }); // Handle download logic const handleDownload = React.useCallback((data: unknown) => { const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json", }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `beenvoice-backup-${new Date().toISOString().split("T")[0]}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); toast.success("Data backup downloaded successfully"); }, []); const importDataMutation = api.settings.importData.useMutation({ onSuccess: (result) => { toast.success( `Data imported successfully! Added ${result.imported.clients} clients, ${result.imported.businesses} businesses, and ${result.imported.invoices} invoices.`, ); setImportData(""); setIsImportDialogOpen(false); void refetchProfile(); }, onError: (error: { message: string }) => { toast.error(`Import failed: ${error.message}`); }, }); const deleteDataMutation = api.settings.deleteAllData.useMutation({ onSuccess: () => { toast.success("All data has been permanently deleted"); setDeleteConfirmText(""); }, onError: (error: { message: string }) => { toast.error(`Delete failed: ${error.message}`); }, }); const handleUpdateProfile = (e: React.FormEvent) => { e.preventDefault(); if (!name.trim()) { toast.error("Please enter your name"); return; } updateProfileMutation.mutate({ name: name.trim() }); }; const handleChangePassword = (e: React.FormEvent) => { e.preventDefault(); if (!currentPassword || !newPassword || !confirmPassword) { toast.error("Please fill in all password fields"); return; } if (newPassword !== confirmPassword) { toast.error("New passwords don't match"); return; } if (newPassword.length < 8) { toast.error("New password must be at least 8 characters"); return; } changePasswordMutation.mutate({ currentPassword, newPassword, confirmPassword, }); }; const handleExportData = async () => { try { const result = await exportDataQuery.refetch(); if (result.data) { handleDownload(result.data); } } catch (error) { toast.error( `Export failed: ${error instanceof Error ? error.message : "Unknown error"}`, ); } }; // Type guard for backup data const isValidBackupData = (data: unknown): boolean => { if (typeof data !== "object" || data === null) return false; const obj = data as Record; return !!( obj.exportDate && obj.version && obj.user && obj.clients && obj.businesses && obj.invoices && Array.isArray(obj.clients) && Array.isArray(obj.businesses) && Array.isArray(obj.invoices) ); }; const handleFileUpload = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (!file) return; if (!file.name.endsWith(".json")) { toast.error("Please select a JSON file"); return; } const reader = new FileReader(); reader.onload = (e) => { try { const content = e.target?.result as string; const parsedData: unknown = JSON.parse(content); if (isValidBackupData(parsedData)) { // @ts-expect-error Server handles validation of backup data format importDataMutation.mutate(parsedData); } else { toast.error("Invalid backup file format"); } } catch { toast.error("Invalid JSON format. Please check your backup file."); } }; reader.onerror = () => { toast.error("Failed to read file"); }; reader.readAsText(file); }; const handleImportData = () => { try { const parsedData: unknown = JSON.parse(importData); if (isValidBackupData(parsedData)) { // @ts-expect-error Server handles validation of backup data format importDataMutation.mutate(parsedData); } else { toast.error("Invalid backup file format"); } } catch { toast.error("Invalid JSON format. Please check your backup file."); } }; const handleDeleteAllData = () => { if (deleteConfirmText !== "delete all my data") { toast.error("Please type 'delete all my data' to confirm"); return; } deleteDataMutation.mutate({ confirmText: deleteConfirmText }); }; // Set initial name value when profile loads React.useEffect(() => { if (profile?.name && !name) { setName(profile.name); } }, [profile?.name, name]); const dataStatItems = [ { label: "Clients", value: dataStats?.clients ?? 0, icon: Users, color: "text-primary", bgColor: "bg-primary/10", }, { label: "Businesses", value: dataStats?.businesses ?? 0, icon: Building, color: "text-muted-foreground", bgColor: "bg-muted", }, { label: "Invoices", value: dataStats?.invoices ?? 0, icon: FileText, color: "text-primary", bgColor: "bg-accent", }, ]; return (
{/* Profile & Account Overview */}
{/* Profile Section */} Profile Information Update your personal account details
setName(e.target.value)} placeholder="Enter your full name" />

Email address cannot be changed

{/* Data Overview */} Account Data Overview of your stored information
{dataStatItems.map((item) => { const Icon = item.icon; return (
{item.label}
{item.value}
); })}
{/* Security Settings */} Security Settings Change your password and manage account security
setCurrentPassword(e.target.value)} placeholder="Enter your current password" />
setNewPassword(e.target.value)} placeholder="Enter your new password" />

Password must be at least 8 characters long

setConfirmPassword(e.target.value)} placeholder="Confirm your new password" />
{/* Data Management */} Data Management Backup, restore, or manage your account data
Import Backup Data Upload your backup JSON file or paste the contents below. This will add the data to your existing account.
{/* Import Method Selector */}
{/* File Upload Method */} {importMethod === "file" && (

Select the JSON backup file you previously exported.

)} {/* Manual Paste Method */} {importMethod === "paste" && (