From a23753f65272dca3f0b54bed16d96512a3cbe20d Mon Sep 17 00:00:00 2001 From: schererleander Date: Fri, 26 Dec 2025 18:08:25 +0100 Subject: refactor(settings): split settings page into separate form components --- src/app/settings/settings-content.tsx | 537 ---------------------------------- 1 file changed, 537 deletions(-) delete mode 100644 src/app/settings/settings-content.tsx (limited to 'src/app/settings/settings-content.tsx') diff --git a/src/app/settings/settings-content.tsx b/src/app/settings/settings-content.tsx deleted file mode 100644 index 8916b9e..0000000 --- a/src/app/settings/settings-content.tsx +++ /dev/null @@ -1,537 +0,0 @@ -"use client" - -import { useState } from "react" -import { useSession } from "next-auth/react" -import { useRouter } from "next/navigation" -import { Shield, Loader2, Copy } from "lucide-react" -import { toast } from "sonner" -import Image from "next/image" - -import { Button } from "@/components/ui/button" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog" -import { Separator } from "@/components/ui/separator" -import Navbar from "@/components/Navbar" -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" -import { Camera, Lock, Save, Trash2, Upload, User } from "lucide-react" -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form" -import { useForm } from "react-hook-form" -import { zodResolver } from "@hookform/resolvers/zod" -import { updateProfileSchema, updatePasswordSchema, type UpdateProfileInput } from "@/lib/validation" -import { z } from "zod" - -// Re-using existing types and schemas from previous implementation -const passwordChangeSchema = updatePasswordSchema.extend({ - confirmPassword: z.string() -}).refine((data) => data.newPassword === data.confirmPassword, { - message: "Passwords don't match", - path: ["confirmPassword"], -}) - -type ProfileFormData = UpdateProfileInput -type PasswordFormData = z.infer - -interface SettingsContentProps { - initialUser: { - name: string - email: string - image: string | null - twoFactorEnabled: boolean - } -} - -export default function SettingsContent({ initialUser }: SettingsContentProps) { - const { update } = useSession() - const router = useRouter() - const [twoFactorEnabled, setTwoFactorEnabled] = useState(initialUser.twoFactorEnabled) - const [is2FALoading, setIs2FALoading] = useState(false) - const [setupData, setSetupData] = useState<{ secret: string;qrCode: string } | null>(null) - const [verificationCode, setVerificationCode] = useState("") - const [isDialogOpen, setIsDialogOpen] = useState(false) - - // Existing state for other forms - const [isLoading, setIsLoading] = useState(false) - const [isImageLoading, setIsImageLoading] = useState(false) - const [profileImageUrl, setProfileImageUrl] = useState(initialUser.image) - - const profileForm = useForm({ - resolver: zodResolver(updateProfileSchema), - defaultValues: { - name: initialUser.name, - email: initialUser.email, - }, - }) - - const passwordForm = useForm({ - resolver: zodResolver(passwordChangeSchema), - defaultValues: { - currentPassword: "", - newPassword: "", - confirmPassword: "", - }, - }) - - // 2FA Handlers - const start2FASetup = async () => { - setIs2FALoading(true) - try { - const res = await fetch("/api/user/2fa", { method: "PUT" }) - const data = await res.json() - if (data.error) throw new Error(data.error) - setSetupData(data) - setIsDialogOpen(true) - } catch (error) { - toast.error("Failed to start 2FA setup") - } finally { - setIs2FALoading(false) - } - } - - const verifyAndEnable2FA = async () => { - if (verificationCode.length !== 6) { - toast.error("Please enter a 6-digit code") - return - } - - setIs2FALoading(true) - try { - const res = await fetch("/api/user/2fa", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - code: verificationCode, - secret: setupData?.secret, - }), - }) - const data = await res.json() - - if (data.error) throw new Error(data.error) - - setTwoFactorEnabled(true) - setIsDialogOpen(false) - toast.success("Two-factor authentication enabled") - router.refresh() - } catch (error) { - toast.error("Invalid verification code") - } finally { - setIs2FALoading(false) - setVerificationCode("") - } - } - - const disable2FA = async () => { - if (!confirm("Are you sure you want to disable 2FA? This will make your account less secure.")) return - - setIs2FALoading(true) - try { - const res = await fetch("/api/user/2fa", { method: "DELETE" }) - const data = await res.json() - - if (data.error) throw new Error(data.error) - - setTwoFactorEnabled(false) - toast.success("Two-factor authentication disabled") - router.refresh() - } catch (error) { - toast.error("Failed to disable 2FA") - } finally { - setIs2FALoading(false) - } - } - - const copyToClipboard = () => { - if (setupData?.secret) { - navigator.clipboard.writeText(setupData.secret) - toast.success("Secret copied to clipboard") - } - } - - // Existing Handlers (Profile, Password, Image) - const onProfileSubmit = async (data: ProfileFormData) => { - setIsLoading(true) - try { - const response = await fetch("/api/user/profile", { - method: "PATCH", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(data), - }) - const result = await response.json() - if (!response.ok) { - toast.error(result.error || "Failed to update profile") - return - } - await update({ name: data.name, email: data.email }) - toast.success("Profile updated successfully!") - } catch { - toast.error("An unexpected error occurred") - } finally { - setIsLoading(false) - } - } - - const onPasswordSubmit = async (data: PasswordFormData) => { - setIsLoading(true) - try { - const response = await fetch("/api/user/password", { - method: "PATCH", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - currentPassword: data.currentPassword, - newPassword: data.newPassword, - }), - }) - const result = await response.json() - if (!response.ok) { - toast.error(result.error || "Failed to update password") - return - } - toast.success("Password updated successfully!") - passwordForm.reset() - } catch { - toast.error("An unexpected error occurred") - } finally { - setIsLoading(false) - } - } - - const handleImageUpload = async (event: React.ChangeEvent) => { - const file = event.target.files?.[0] - if (!file) return - setIsImageLoading(true) - try { - const formData = new FormData() - formData.append('image', file) - const response = await fetch('/api/user/profile-image', { - method: 'POST', - body: formData, - }) - const result = await response.json() - if (!response.ok) { - toast.error(result.error || 'Failed to upload image') - return - } - setProfileImageUrl(result.profileImage.url) - toast.success('Profile image uploaded successfully!') - await update({ image: result.profileImage.url }) - } catch { - toast.error('An unexpected error occurred') - } finally { - setIsImageLoading(false) - } - } - - const handleImageDelete = async () => { - setIsImageLoading(true) - try { - const response = await fetch('/api/user/profile-image', { method: 'DELETE' }) - const result = await response.json() - if (!response.ok) { - toast.error(result.error || 'Failed to delete image') - return - } - setProfileImageUrl(null) - toast.success('Profile image deleted successfully!') - await update({ image: null }) - } catch { - toast.error('An unexpected error occurred') - } finally { - setIsImageLoading(false) - } - } - - return ( -
- - -
-
-
-

Account Settings

-

- Manage your account information and security settings -

-
- - {/* Profile Information */} - - - - - Profile Information - - Update your personal information - - -
- - ( - - Full Name - - - - - - )} - /> - ( - - Email Address - - - - - - )} - /> - - - -
-
- - - - {/* Profile Image */} - - - - - Profile Image - - Upload or update your profile picture - - -
- - - - {initialUser.name.charAt(0).toUpperCase()} - - - -
-
-
- -
- - {profileImageUrl && ( - - )} -
-

- Supported formats: JPEG, PNG, WebP, GIF. Maximum size: 10MB. -

-
-
-
-
- - - - {/* Two-Factor Authentication */} - - - - - Two-Factor Authentication - - - Add an extra layer of security to your account - - - -
-
-

Status: {twoFactorEnabled ? "Enabled" : "Disabled"}

-

- {twoFactorEnabled - ? "Your account is secured with 2FA." - : "Protect your account by enabling 2FA."} -

-
- {twoFactorEnabled ? ( - - ) : ( - - - - - - - Set up Two-Factor Authentication - - Scan the QR code with your authenticator app (like Google Authenticator or Authy). - - - - {setupData && ( -
-
- 2FA QR Code -
- -
- - {setupData.secret} - - -
- -
- - setVerificationCode(e.target.value.slice(0, 6))} - maxLength={6} - /> -
- - -
- )} -
-
- )} -
-
-
- - - - {/* Password Change */} - - - - - Change Password - - - Update your password to keep your account secure - - - -
- - ( - - Current Password - - - - - - )} - /> - ( - - New Password - - - - - - )} - /> - ( - - Confirm New Password - - - - - - )} - /> - - - -
-
-
-
-
- ) -} -- cgit v1.3.1