'use client' import { useState } from 'react' import { Card, CardContent, CardHeader, CardTitle, CardDescription, } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Badge } from '@/components/ui/badge' import { AlertTriangle, Mail, Shield, UserCircle, Loader2, Check, Calendar, Info, } from 'lucide-react' import { useForm } from 'react-hook-form' import { z } from 'zod' import { zodResolver } from '@hookform/resolvers/zod' import { useToast } from '@/hooks/use-toast' import httpBrowserClient from '@/lib/httpBrowserClient' import { ApiEndpoints } from '@/config/api' import { useMutation, useQuery } from '@tanstack/react-query' import { Spinner } from '@/components/ui/spinner' import Link from 'next/link' import { Textarea } from '@/components/ui/textarea' import axios from 'axios' import { useSession } from 'next-auth/react' import { Routes } from '@/config/routes' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '@/components/ui/tooltip' const updateProfileSchema = z.object({ name: z.string().min(1, 'Name is required'), email: z.string().email().optional(), phone: z .string() .regex(/^\+?\d{0,14}$/, 'Invalid phone number') .optional(), }) type UpdateProfileFormData = z.infer const changePasswordSchema = z .object({ oldPassword: z.string().min(1, 'Old password is required'), newPassword: z .string() .min(8, { message: 'Password must be at least 8 characters long' }), confirmPassword: z .string() .min(4, { message: 'Please confirm your password' }), }) .superRefine((data, ctx) => { if (data.newPassword !== data.confirmPassword) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Passwords must match', path: ['confirmPassword'], }) } }) type ChangePasswordFormData = z.infer export default function AccountSettings() { const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false) const [deleteConfirmEmail, setDeleteConfirmEmail] = useState('') const { update: updateSession } = useSession() const { toast } = useToast() const { data: currentUser, isLoading: isLoadingUser, refetch: refetchCurrentUser, } = useQuery({ queryKey: ['currentUser'], queryFn: () => httpBrowserClient .get(ApiEndpoints.auth.whoAmI()) .then((res) => res.data?.data), }) const updateProfileForm = useForm({ resolver: zodResolver(updateProfileSchema), defaultValues: { name: currentUser?.name, email: currentUser?.email, phone: currentUser?.phone, }, }) const changePasswordForm = useForm({ resolver: zodResolver(changePasswordSchema), }) const handleDeleteAccount = () => { if (deleteConfirmEmail !== currentUser?.email) { toast({ title: 'Please enter your correct email address', }) return } requestAccountDeletion() } const handleVerifyEmail = () => { // TODO: Implement email verification } const { mutate: updateProfile, isPending: isUpdatingProfile, error: updateProfileError, isSuccess: isUpdateProfileSuccess, } = useMutation({ mutationFn: (data: UpdateProfileFormData) => httpBrowserClient.patch(ApiEndpoints.auth.updateProfile(), data), onSuccess: () => { refetchCurrentUser() toast({ title: 'Profile updated successfully!', }) updateSession({ name: updateProfileForm.getValues().name, phone: updateProfileForm.getValues().phone, }) }, onError: () => { toast({ title: 'Failed to update profile', }) }, }) const { mutate: changePassword, isPending: isChangingPassword, error: changePasswordError, isSuccess: isChangePasswordSuccess, } = useMutation({ mutationFn: (data: ChangePasswordFormData) => httpBrowserClient.post(ApiEndpoints.auth.changePassword(), data), onSuccess: () => { toast({ title: 'Password changed successfully!', }) changePasswordForm.reset() }, onError: (error) => { const errorMessage = (error as any).response?.data?.error changePasswordForm.setError('root.serverError', { message: errorMessage || 'Failed to change password', }) toast({ title: 'Failed to change password', }) }, }) const [deleteReason, setDeleteReason] = useState('') const { mutate: requestAccountDeletion, isPending: isRequestingAccountDeletion, error: requestAccountDeletionError, isSuccess: isRequestAccountDeletionSuccess, } = useMutation({ mutationFn: () => axios.post('/api/request-account-deletion', { message: deleteReason, }), onSuccess: () => { toast({ title: 'Account deletion request submitted', }) }, onError: () => { toast({ title: 'Failed to submit account deletion request', }) }, }) const CurrentSubscription = () => { const { data: currentSubscription, isLoading: isLoadingSubscription, error: subscriptionError, } = useQuery({ queryKey: ['currentSubscription'], queryFn: () => httpBrowserClient .get(ApiEndpoints.billing.currentSubscription()) .then((res) => res.data), }) if (isLoadingSubscription) return (
) if (subscriptionError) return (

Failed to load subscription information

) // Format price with currency symbol const formatPrice = (amount: number | null | undefined, currency: string | null | undefined) => { if (amount == null || currency == null) return 'Free'; const formatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: currency.toUpperCase() || 'USD', minimumFractionDigits: 2, }); return formatter.format(amount / 100); }; const getBillingInterval = (interval: string | null | undefined) => { if (!interval) return ''; return interval.toLowerCase() === 'month' ? 'monthly' : 'yearly'; }; return (

{currentSubscription?.plan?.name || 'Free Plan'}

Current subscription

{currentSubscription?.amount > 0 && ( {formatPrice(currentSubscription?.amount, currentSubscription?.currency)} {currentSubscription?.recurringInterval && ( / {getBillingInterval(currentSubscription?.recurringInterval)} )} )}
Active

Start Date

{currentSubscription?.subscriptionStartDate ? new Date( currentSubscription?.subscriptionStartDate ).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric', }) : 'N/A'}

Next Payment

{currentSubscription?.currentPeriodEnd ? new Date( currentSubscription?.currentPeriodEnd ).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric', }) : 'N/A'}

Usage Limits

Daily

{currentSubscription?.plan?.dailyLimit === -1 ? 'Unlimited' : currentSubscription?.plan?.dailyLimit || '0'} {currentSubscription?.plan?.dailyLimit === -1 && (

Unlimited (within monthly limit)

)}

Monthly

{currentSubscription?.plan?.monthlyLimit === -1 ? 'Unlimited' : currentSubscription?.plan?.monthlyLimit?.toLocaleString() || '0'} {currentSubscription?.plan?.monthlyLimit === -1 && (

Unlimited (within fair usage)

)}

Bulk

{currentSubscription?.plan?.bulkSendLimit === -1 ? 'Unlimited' : currentSubscription?.plan?.bulkSendLimit || '0'} {currentSubscription?.plan?.bulkSendLimit === -1 && (

Unlimited (within monthly limit)

)}

{(!currentSubscription?.plan?.name || currentSubscription?.plan?.name?.toLowerCase() === 'free') ? ( Upgrade to Pro → ) : ( Manage Subscription → )}
) } if (isLoadingUser) return (
) return (
Profile Information
Update your profile information
updateProfile(data) )} className='space-y-4' >
{updateProfileForm.formState.errors.name && (

{updateProfileForm.formState.errors.name.message}

)}
{!currentUser?.emailVerifiedAt ? ( ) : ( )}
{updateProfileForm.formState.errors.email && (

{updateProfileForm.formState.errors.email.message}

)}
{updateProfileForm.formState.errors.phone && (

{updateProfileForm.formState.errors.phone.message}

)}
{isUpdateProfileSuccess && (

Profile updated successfully!

)}
Change Password
If you signed in with google, your can reset your password{' '} here .
changePassword(data) )} className='space-y-4' >
{changePasswordForm.formState.errors.oldPassword && (

{changePasswordForm.formState.errors.oldPassword.message}

)}
{changePasswordForm.formState.errors.newPassword && (

{changePasswordForm.formState.errors.newPassword.message}

)}
{changePasswordForm.formState.errors.confirmPassword && (

{changePasswordForm.formState.errors.confirmPassword.message}

)}
{changePasswordForm.formState.errors.root?.serverError && (

{changePasswordForm.formState.errors.root.serverError.message}

)} {isChangePasswordSuccess && (

Password changed successfully!

)}
Danger Zone
Permanently delete your account and all associated data
Delete Account

Are you sure you want to delete your account? This action:

  • Cannot be undone
  • Will permanently delete all your data
  • Will cancel all active subscriptions
  • Will remove access to all services
{/* enter reason for deletion text area */}