feat: tempcommit

This commit is contained in:
Maximilian Liebmann 2025-06-30 13:59:08 +02:00
parent b191090f90
commit f508f26531
2 changed files with 74 additions and 24 deletions

View file

@ -11,6 +11,7 @@ export default function LabeledInput({
subtext, subtext,
placeholder, placeholder,
value, value,
defaultValue,
name, name,
icon, icon,
variantSize = 'default', variantSize = 'default',
@ -31,7 +32,7 @@ export default function LabeledInput({
error?: string; error?: string;
} & React.InputHTMLAttributes<HTMLInputElement>) { } & React.InputHTMLAttributes<HTMLInputElement>) {
const [passwordVisible, setPasswordVisible] = React.useState(false); const [passwordVisible, setPasswordVisible] = React.useState(false);
const [inputValue, setInputValue] = React.useState(value || ''); const [inputValue, setInputValue] = React.useState(value || defaultValue || '');
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value); setInputValue(e.target.value);

View file

@ -22,7 +22,11 @@ import {
} from '@/components/ui/select'; } from '@/components/ui/select';
import { SettingsDropdown } from '@/components/misc/settings-dropdown'; import { SettingsDropdown } from '@/components/misc/settings-dropdown';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useDeleteApiUserMe, useGetApiUserMe } from '@/generated/api/user/user'; import {
useDeleteApiUserMe,
useGetApiUserMe,
usePatchApiUserMePassword,
} from '@/generated/api/user/user';
import { ThemePicker } from './theme-picker'; import { ThemePicker } from './theme-picker';
import LabeledInput from '../custom-ui/labeled-input'; import LabeledInput from '../custom-ui/labeled-input';
import { GroupWrapper } from '../wrappers/group-wrapper'; import { GroupWrapper } from '../wrappers/group-wrapper';
@ -55,17 +59,55 @@ import {
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from '../ui/dialog'; } from '../ui/dialog';
import useZodForm from '@/lib/hooks/useZodForm';
import { updateUserPasswordServerSchema } from '@/app/api/user/me/validation';
export default function SettingsPage() { export default function SettingsPage() {
const router = useRouter(); const router = useRouter();
const [currentSection, setCurrentSection] = useState('general'); const [currentSection, setCurrentSection] = useState('general');
const { data } = useGetApiUserMe(); const { data } = useGetApiUserMe();
const deleteUser = useDeleteApiUserMe(); const deleteUser = useDeleteApiUserMe();
const updatePassword = usePatchApiUserMePassword();
// Move password state hooks here const { handleSubmit, formState, register, setError } = useZodForm(
const [currentPassword, setCurrentPassword] = useState(''); updateUserPasswordServerSchema,
const [newPassword, setNewPassword] = useState(''); );
const [repeatPassword, setRepeatPassword] = useState('');
const onSubmit = handleSubmit(async (data) => {
await updatePassword.mutateAsync(
{
data: data,
},
{
onSuccess: () => {
router.refresh();
},
onError: (error) => {
if (error instanceof Error) {
setError('root', {
message: error.response?.data.message,
});
} else {
setError('root', {
message: 'An unknown error occurred.',
});
}
},
},
);
});
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>
);
}
const renderSettingsContent = () => { const renderSettingsContent = () => {
switch (currentSection) { switch (currentSection) {
@ -85,7 +127,7 @@ export default function SettingsPage() {
type='text' type='text'
label='First Name' label='First Name'
placeholder='First Name' placeholder='First Name'
defaultValue={data?.data.user.first_name ?? ''} defaultValue={data.data.user.first_name ?? ''}
></LabeledInput> ></LabeledInput>
</div> </div>
<div> <div>
@ -93,7 +135,7 @@ export default function SettingsPage() {
type='text' type='text'
label='Last Name' label='Last Name'
placeholder='Last Name' placeholder='Last Name'
defaultValue={data?.data.user.last_name ?? ''} defaultValue={data.data.user.last_name ?? ''}
></LabeledInput> ></LabeledInput>
</div> </div>
<div className='space-y-2'> <div className='space-y-2'>
@ -102,7 +144,7 @@ export default function SettingsPage() {
label='Display Name' label='Display Name'
icon={UserPen} icon={UserPen}
placeholder='Display Name' placeholder='Display Name'
defaultValue={data?.data.user.name} defaultValue={data.data.user.name}
></LabeledInput> ></LabeledInput>
</div> </div>
<div className='space-y-2 space-b-2'> <div className='space-y-2 space-b-2'>
@ -111,7 +153,7 @@ export default function SettingsPage() {
label='Email Address' label='Email Address'
icon={MailOpen} icon={MailOpen}
placeholder='Your E-Mail' placeholder='Your E-Mail'
defaultValue={data?.data.user.email ?? ''} defaultValue={data.data.user.email ?? ''}
></LabeledInput> ></LabeledInput>
<span className='text-sm text-muted-foreground'> <span className='text-sm text-muted-foreground'>
@ -123,16 +165,18 @@ export default function SettingsPage() {
{/*-------------------- General Settings --------------------*/} {/*-------------------- General Settings --------------------*/}
{/*-------------------- Reset Password --------------------*/} {/*-------------------- Reset Password --------------------*/}
<GroupWrapper title='Reset Password'> <GroupWrapper title='Reset Password'>
<div className='flex flex-col sm:flex-row items-center gap-6'> <div className='flex flex-col items-center gap-6'>
<div className='flex flex-col sm:flex-row gap-6 w-full'> <form
{/* Use the state variables directly */} onSubmit={onSubmit}
className='flex flex-col sm:flex-row gap-6 w-full'
>
<div className='flex-1'> <div className='flex-1'>
<LabeledInput <LabeledInput
type='password' type='password'
label='Current Password' label='Current Password'
icon={FileKey} icon={FileKey}
value={currentPassword} {...register('current_password')}
onChange={(e) => setCurrentPassword(e.target.value)} error={formState.errors.current_password?.message}
/> />
</div> </div>
<div className='flex-1'> <div className='flex-1'>
@ -140,8 +184,8 @@ export default function SettingsPage() {
type='password' type='password'
label='New Password' label='New Password'
icon={FileKey2} icon={FileKey2}
value={newPassword} {...register('new_password')}
onChange={(e) => setNewPassword(e.target.value)} error={formState.errors.new_password?.message}
/> />
</div> </div>
<div className='flex-1'> <div className='flex-1'>
@ -149,34 +193,39 @@ export default function SettingsPage() {
type='password' type='password'
label='Repeat Password' label='Repeat Password'
icon={RotateCcwKey} icon={RotateCcwKey}
value={repeatPassword} {...register('confirm_new_password')}
onChange={(e) => setRepeatPassword(e.target.value)} error={formState.errors.confirm_new_password?.message}
/> />
</div> </div>
<div className='flex items-end'> <div className='flex items-end'>
<Button <Button
variant={ variant={
currentPassword || newPassword || repeatPassword formState.isValid
? 'outline_secondary' ? 'outline_secondary'
: 'outline_muted' : 'outline_muted'
} }
size='icon' size='icon'
className='w-full md:size-9' className='w-full md:size-9'
disabled={ disabled={
!(currentPassword || newPassword || repeatPassword) !formState.isValid || formState.isSubmitting
} }
> >
<BookKey className='h-[1.2rem] w-[1.2rem]' /> <BookKey className='h-[1.2rem] w-[1.2rem]' />
</Button> </Button>
</div> </div>
</div> </form>
{formState.errors.root && (
<p className='text-red-500 text-sm mt-1'>
{formState.errors.root.message}
</p>
)}
</div> </div>
</GroupWrapper> </GroupWrapper>
{/*-------------------- Reset Password --------------------*/} {/*-------------------- Reset Password --------------------*/}
{/*-------------------- Profile Picture --------------------*/} {/*-------------------- Profile Picture --------------------*/}
<GroupWrapper title='Profile Picture'> <GroupWrapper title='Profile Picture'>
<div className='space-y-2 grid grid-cols-[1fr_auto]'> <div className='space-y-2 grid grid-cols-[1fr_auto]'>
<ProfilePictureUpload className='file:border file:rounded-md file:hover:bg-disabled-destructive' /> <ProfilePictureUpload />
</div> </div>
</GroupWrapper> </GroupWrapper>
{/*-------------------- Profile Picture --------------------*/} {/*-------------------- Profile Picture --------------------*/}
@ -216,7 +265,7 @@ export default function SettingsPage() {
> >
<div className='flex items-center justify-evenly sm:flex-row flex-col gap-6'> <div className='flex items-center justify-evenly sm:flex-row flex-col gap-6'>
<Dialog> <Dialog>
<DialogTrigger> <DialogTrigger asChild>
<Button variant='destructive'>Delete Account</Button> <Button variant='destructive'>Delete Account</Button>
</DialogTrigger> </DialogTrigger>
<DialogContent> <DialogContent>