From 49c6a8f9b7a5ce6bd83d91ce2783b82993f1073e Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Fri, 3 Nov 2023 21:00:11 +0100 Subject: [PATCH] feat(client): add form for change email in settings page --- .../ChangeUsernameForm/ChangeUsernameForm.tsx | 86 +++++++++++++++++++ .../components/ChangeUsernameForm/index.ts | 1 + .../SecurityContainer/SecurityContainer.tsx | 13 ++- src/app/(dashboard)/settings/page.tsx | 2 +- src/client/messages/en.json | 12 +++ 5 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 src/app/(dashboard)/settings/components/ChangeUsernameForm/ChangeUsernameForm.tsx create mode 100644 src/app/(dashboard)/settings/components/ChangeUsernameForm/index.ts diff --git a/src/app/(dashboard)/settings/components/ChangeUsernameForm/ChangeUsernameForm.tsx b/src/app/(dashboard)/settings/components/ChangeUsernameForm/ChangeUsernameForm.tsx new file mode 100644 index 00000000..924226c8 --- /dev/null +++ b/src/app/(dashboard)/settings/components/ChangeUsernameForm/ChangeUsernameForm.tsx @@ -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; + + 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({ + resolver: zodResolver(schema), + }); + + const onSubmit = (values: FormValues) => { + changeUsernameMutation.execute(values); + }; + + return ( +
+ + + + + + {t('password-needed')} + + +
+

{t('change-username.form.password-needed-hint')}

+ + + +
+
+
+
+
+ ); +}; diff --git a/src/app/(dashboard)/settings/components/ChangeUsernameForm/index.ts b/src/app/(dashboard)/settings/components/ChangeUsernameForm/index.ts new file mode 100644 index 00000000..3402de5f --- /dev/null +++ b/src/app/(dashboard)/settings/components/ChangeUsernameForm/index.ts @@ -0,0 +1 @@ +export { ChangeUsernameForm } from './ChangeUsernameForm'; diff --git a/src/app/(dashboard)/settings/components/SecurityContainer/SecurityContainer.tsx b/src/app/(dashboard)/settings/components/SecurityContainer/SecurityContainer.tsx index b7ba29f6..7248babd 100644 --- a/src/app/(dashboard)/settings/components/SecurityContainer/SecurityContainer.tsx +++ b/src/app/(dashboard)/settings/components/SecurityContainer/SecurityContainer.tsx @@ -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 (
+
+ +

{t('change-username.title')}

+
+

{t('change-username.subtitle')}

+

{t('change-password-title')}

diff --git a/src/app/(dashboard)/settings/page.tsx b/src/app/(dashboard)/settings/page.tsx index 9a66a4a8..f8c020c5 100644 --- a/src/app/(dashboard)/settings/page.tsx +++ b/src/app/(dashboard)/settings/page.tsx @@ -38,7 +38,7 @@ export default async function SettingsPage({ searchParams }: { searchParams: { t - +
diff --git a/src/client/messages/en.json b/src/client/messages/en.json index e67c5ee6..e5c9bb68 100644 --- a/src/client/messages/en.json +++ b/src/client/messages/en.json @@ -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" + } } } },