feat(client): add form for change email in settings page
This commit is contained in:
parent
955b4ccc18
commit
49c6a8f9b7
5 changed files with 110 additions and 4 deletions
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
export { ChangeUsernameForm } from './ChangeUsernameForm';
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue