feat(client): add form for change email in settings page

This commit is contained in:
Nicolas Meienberger 2023-11-03 21:00:11 +01:00 committed by Nicolas Meienberger
parent 955b4ccc18
commit 49c6a8f9b7
5 changed files with 110 additions and 4 deletions

View file

@ -0,0 +1,86 @@
import React from 'react';
import { Input } from '@/components/ui/Input';
import { Button } from '@/components/ui/Button';
import { useRouter } from 'next/navigation';
import { toast } from 'react-hot-toast';
import { useTranslations } from 'next-intl';
import { useAction } from 'next-safe-action/hook';
import { changeUsernameAction } from '@/actions/settings/change-username';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/Dialog';
import { useDisclosure } from '@/client/hooks/useDisclosure';
import { z } from 'zod';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
type Props = {
username?: string;
};
export const ChangeUsernameForm = ({ username }: Props) => {
const router = useRouter();
const changeUsernameDisclosure = useDisclosure();
const t = useTranslations('settings.security');
const schema = z.object({
newUsername: z.string().email(t('change-username.form.invalid-username')),
password: z.string().min(1),
});
type FormValues = z.infer<typeof schema>;
const changeUsernameMutation = useAction(changeUsernameAction, {
onSuccess: (data) => {
if (!data.success) {
toast.error(data.failure.reason);
} else {
toast.success(t('change-username.success'));
router.push('/');
}
},
});
const { register, handleSubmit, formState } = useForm<FormValues>({
resolver: zodResolver(schema),
});
const onSubmit = (values: FormValues) => {
changeUsernameMutation.execute(values);
};
return (
<div className="mb-4">
<Input disabled type="email" value={username} />
<Button className="mt-3" onClick={() => changeUsernameDisclosure.open()}>
{t('change-username.form.submit')}
</Button>
<Dialog open={changeUsernameDisclosure.isOpen} onOpenChange={changeUsernameDisclosure.toggle}>
<DialogContent size="sm">
<DialogHeader>
<DialogTitle>{t('password-needed')}</DialogTitle>
</DialogHeader>
<DialogDescription className="d-flex flex-column">
<form onSubmit={handleSubmit(onSubmit)} className="w-100">
<p className="text-muted">{t('change-username.form.password-needed-hint')}</p>
<Input
error={formState.errors.newUsername?.message}
disabled={changeUsernameMutation.status === 'executing'}
type="email"
placeholder={t('change-username.form.new-username')}
{...register('newUsername')}
/>
<Input
className="mt-2"
error={formState.errors.password?.message}
disabled={changeUsernameMutation.status === 'executing'}
type="password"
placeholder={t('form.password')}
{...register('password')}
/>
<Button loading={changeUsernameMutation.status === 'executing'} type="submit" className="btn-success mt-3">
{t('change-username.form.submit')}
</Button>
</form>
</DialogDescription>
</DialogContent>
</Dialog>
</div>
);
};

View file

@ -0,0 +1 @@
export { ChangeUsernameForm } from './ChangeUsernameForm';

View file

@ -1,17 +1,24 @@
'use client';
import React from 'react';
import { IconLock, IconKey } from '@tabler/icons-react';
import { IconLock, IconKey, IconUser } from '@tabler/icons-react';
import { useTranslations } from 'next-intl';
import { OtpForm } from '../OtpForm';
import { ChangePasswordForm } from '../ChangePasswordForm';
import { ChangeUsernameForm } from '../ChangeUsernameForm';
export const SecurityContainer = (props: { totpEnabled: boolean }) => {
const { totpEnabled } = props;
export const SecurityContainer = (props: { totpEnabled: boolean; username?: string }) => {
const { totpEnabled, username } = props;
const t = useTranslations('settings.security');
return (
<div className="card-body">
<div className="d-flex">
<IconUser className="me-2" />
<h2>{t('change-username.title')}</h2>
</div>
<p className="text-muted">{t('change-username.subtitle')}</p>
<ChangeUsernameForm username={username} />
<div className="d-flex">
<IconKey className="me-2" />
<h2>{t('change-password-title')}</h2>

View file

@ -38,7 +38,7 @@ export default async function SettingsPage({ searchParams }: { searchParams: { t
<SettingsContainer initialValues={settings} currentLocale={locale} />
</TabsContent>
<TabsContent value="security">
<SecurityContainer totpEnabled={Boolean(user?.totpEnabled)} />
<SecurityContainer totpEnabled={Boolean(user?.totpEnabled)} username={user?.username} />
</TabsContent>
</Tabs>
</div>

View file

@ -284,6 +284,18 @@
"confirm-password": "Confirm new password",
"change-password": "Change password",
"password": "Password"
},
"change-username": {
"title": "Change username",
"subtitle": "Changing your username will log you out of all devices.",
"success": "Username changed successfully",
"form": {
"new-username": "New username",
"invalid-username": "Must be a valid email address",
"password": "Password",
"password-needed-hint": "Your password is required to change your username.",
"submit": "Change username"
}
}
}
},