MeetUp/src/components/settings/tabs/account.tsx
Dominik Stahl 2a95836dcb
Some checks failed
container-scan / Container Scan (pull_request) Successful in 2m32s
tests / Tests (pull_request) Has been cancelled
docker-build / docker (pull_request) Successful in 3m41s
fix: settings error message
2025-07-01 08:59:43 +02:00

287 lines
10 KiB
TypeScript

'use client';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Label } from '@/components/ui/label';
import { ScrollableSettingsWrapper } from '@/components/wrappers/settings-scroll';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import {
useDeleteApiUserMe,
useGetApiUserMe,
usePatchApiUserMe,
} from '@/generated/api/user/user';
import LabeledInput from '@/components/custom-ui/labeled-input';
import { GroupWrapper } from '@/components/wrappers/group-wrapper';
import ProfilePictureUpload from '@/components/misc/profile-picture-upload';
import { CalendarClock, MailOpen, UserPen } from 'lucide-react';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
import useZodForm from '@/lib/hooks/useZodForm';
import { updateUserClientSchema } from '@/app/api/user/me/validation';
import { useRouter } from 'next/navigation';
import { toast } from 'sonner';
import { ToastInner } from '@/components/misc/toast-inner';
export default function AccountTab() {
const router = useRouter();
const { data, refetch } = useGetApiUserMe();
const deleteUser = useDeleteApiUserMe();
const updateAccount = usePatchApiUserMe();
const { handleSubmit, formState, register } = useZodForm(
updateUserClientSchema,
);
const onSubmit = handleSubmit(async (submitData) => {
await updateAccount.mutateAsync(
{
data: {
first_name:
submitData?.first_name !== data?.data.user.first_name
? submitData?.first_name
: undefined,
last_name:
submitData?.last_name !== data?.data.user.last_name
? submitData?.last_name
: undefined,
name:
submitData?.name !== data?.data.user.name
? submitData?.name
: undefined,
email:
submitData?.email !== data?.data.user.email
? submitData?.email
: undefined,
image:
submitData?.image !== data?.data.user.image
? submitData?.image
: undefined,
timezone:
submitData?.timezone !== data?.data.user.timezone
? submitData?.timezone
: undefined,
},
},
{
onSuccess: () => {
refetch();
toast.custom((t) => (
<ToastInner
toastId={t}
title='Settings saved'
description='Your account settings have been updated successfully.'
variant='success'
/>
));
},
onError: (error) => {
toast.custom((t) => (
<ToastInner
toastId={t}
title='Error saving settings'
description={
error.response?.data.message || 'An unknown error occurred.'
}
variant='error'
/>
));
},
},
);
});
if (!data) {
return (
<div className='fixed inset-0 flex items-center justify-center p-4 bg-background/50 backdrop-blur-sm'>
<div className='rounded-lg border bg-card text-card-foreground shadow-xl max-w-[700px] w-full h-auto max-h-[calc(100vh-2rem)] flex flex-col'>
<div className='p-6 border-b'>
<h1 className='text-2xl font-semibold'>Loading Settings...</h1>
</div>
</div>
</div>
);
}
return (
<form onSubmit={onSubmit} className='h-full flex-grow overflow-auto'>
<Card className='pb-0 h-full flex flex-col border-0 shadow-none rounded-none'>
<ScrollableSettingsWrapper>
<CardHeader>
<CardTitle>Account Settings</CardTitle>
</CardHeader>
<CardContent className='space-y-6 my-2'>
{/*-------------------- General Settings --------------------*/}
<GroupWrapper title='General Settings'>
<div className='space-y-4'>
<div>
<LabeledInput
type='text'
label='First Name'
placeholder='First Name'
defaultValue={data.data.user.first_name ?? ''}
{...register('first_name')}
error={formState.errors.first_name?.message}
></LabeledInput>
</div>
<div>
<LabeledInput
type='text'
label='Last Name'
placeholder='Last Name'
defaultValue={data.data.user.last_name ?? ''}
{...register('last_name')}
error={formState.errors.last_name?.message}
></LabeledInput>
</div>
<div className='space-y-2'>
<LabeledInput
type='text'
label='User Name'
icon={UserPen}
placeholder='User Name'
defaultValue={data.data.user.name}
{...register('name')}
error={formState.errors.name?.message}
></LabeledInput>
</div>
<div className='space-y-2 space-b-2'>
<LabeledInput
type='email'
label='Email Address'
icon={MailOpen}
placeholder='Your E-Mail'
defaultValue={data.data.user.email ?? ''}
{...register('email')}
error={formState.errors.email?.message}
></LabeledInput>
<span className='text-sm text-muted-foreground'>
Email might be managed by your SSO provider.
</span>
</div>
</div>
{formState.errors.root && (
<p className='text-red-500 text-sm mt-1'>
{formState.errors.root.message}
</p>
)}
</GroupWrapper>
{/*-------------------- General Settings --------------------*/}
{/*-------------------- Profile Picture --------------------*/}
<GroupWrapper title='Profile Picture'>
<div className='space-y-2 grid grid-cols-[1fr_auto]'>
<ProfilePictureUpload disabled />
</div>
</GroupWrapper>
{/*-------------------- Profile Picture --------------------*/}
{/*-------------------- Regional Settings --------------------*/}
<GroupWrapper title='Regional Settings'>
<div className='space-y-2 grid sm:grid-cols-[1fr_auto] sm:flex-row gap-4'>
<div className='grid gap-1'>
<LabeledInput
type='text'
label='Timezone'
placeholder='Europe/Berlin'
icon={CalendarClock}
defaultValue={data?.data.user.timezone ?? ''}
{...register('timezone')}
error={
formState.errors.timezone?.message
? 'Invalid Timezone'
: undefined
}
></LabeledInput>
</div>
<div>
<div className='grid gap-1'>
<Label htmlFor='language'>Language</Label>
<Select disabled>
<SelectTrigger id='language'>
<SelectValue placeholder='Select language' />
</SelectTrigger>
<SelectContent>
<SelectItem value='en'>English</SelectItem>
<SelectItem value='de'>German</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
</GroupWrapper>
{/*-------------------- Regional Settings --------------------*/}
{/*-------------------- DANGER ZONE --------------------*/}
<GroupWrapper title='DANGER ZONE' className='border-destructive'>
<div className='flex items-center justify-evenly sm:flex-row flex-col gap-6'>
<Dialog>
<DialogTrigger asChild>
<Button variant='destructive'>Delete Account</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<div className='space-y-4'>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<div className='space-y-4'>
<DialogDescription>
This action cannot be undone. This will permanently
delete your account and remove your data from our
servers.
</DialogDescription>
<Button
variant='destructive'
onClick={() => {
deleteUser.mutate(undefined, {
onSuccess: () => {
router.push('/api/logout');
},
});
}}
>
Confirm Delete
</Button>
</div>
</div>
</DialogHeader>
</DialogContent>
</Dialog>
<span className='text-sm text-muted-foreground pt-1'>
Permanently delete your account and all associated data.
</span>
</div>
</GroupWrapper>
{/*-------------------- DANGER ZONE --------------------*/}
</CardContent>
</ScrollableSettingsWrapper>
<CardFooter className='border-t h-[60px] flex content-center justify-between'>
<Button
onClick={() => router.back()}
variant='secondary'
type='button'
>
Exit
</Button>
<Button variant='primary'>Save Changes</Button>
</CardFooter>
</Card>
</form>
);
}