From 79a31dd8ec64dd70d23e7b7063f36fa79d182e28 Mon Sep 17 00:00:00 2001 From: swve Date: Fri, 24 Jan 2025 22:46:05 +0100 Subject: [PATCH] feat: enhance user account management with password and profile updates - Added validation for user profile and password update forms using Yup. - Implemented user feedback for successful profile and password updates, including prompts to re-login. - Improved UI for profile editing and password change sections, enhancing user experience. - Updated password update service to include response metadata handling. --- apps/web/app/auth/reset/reset.tsx | 13 +- .../UserEditGeneral/UserEditGeneral.tsx | 311 +++++++++++++----- .../UserEditPassword/UserEditPassword.tsx | 157 ++++++--- apps/web/services/settings/password.ts | 3 +- 4 files changed, 352 insertions(+), 132 deletions(-) diff --git a/apps/web/app/auth/reset/reset.tsx b/apps/web/app/auth/reset/reset.tsx index c9d16bb9..7451d140 100644 --- a/apps/web/app/auth/reset/reset.tsx +++ b/apps/web/app/auth/reset/reset.tsx @@ -11,7 +11,7 @@ import * as Form from '@radix-ui/react-form' import { getOrgLogoMediaDirectory } from '@services/media/media' import { AlertTriangle, Info } from 'lucide-react' import Link from 'next/link' -import { getUriWithOrg } from '@services/config/config' +import { getUriWithOrg, getUriWithoutOrg } from '@services/config/config' import { useOrg } from '@components/Contexts/OrgContext' import { useRouter, useSearchParams } from 'next/navigation' import { useFormik } from 'formik' @@ -139,9 +139,14 @@ function ResetPasswordClient() { )} {message && ( -
- -
{message}
+
+
+ +
{message}
+
+ + Please login again with your new password +
)} diff --git a/apps/web/components/Dashboard/Pages/UserAccount/UserEditGeneral/UserEditGeneral.tsx b/apps/web/components/Dashboard/Pages/UserAccount/UserEditGeneral/UserEditGeneral.tsx index c4b3f9b7..7f478a2b 100644 --- a/apps/web/components/Dashboard/Pages/UserAccount/UserEditGeneral/UserEditGeneral.tsx +++ b/apps/web/components/Dashboard/Pages/UserAccount/UserEditGeneral/UserEditGeneral.tsx @@ -1,7 +1,7 @@ 'use client'; import { updateProfile } from '@services/settings/profile' import React, { useEffect } from 'react' -import { Formik, Form, Field } from 'formik' +import { Formik, Form } from 'formik' import { useLHSession } from '@components/Contexts/LHSessionContext' import { ArrowBigUpDash, @@ -9,13 +9,39 @@ import { FileWarning, Info, UploadCloud, + AlertTriangle, + LogOut } from 'lucide-react' import UserAvatar from '@components/Objects/UserAvatar' import { updateUserAvatar } from '@services/users/users' -import { constructAcceptValue } from '@/lib/constants'; +import { constructAcceptValue } from '@/lib/constants' +import * as Yup from 'yup' +import { Input } from "@components/ui/input" +import { Textarea } from "@components/ui/textarea" +import { Button } from "@components/ui/button" +import { Label } from "@components/ui/label" +import { toast } from 'react-hot-toast' +import { signOut } from 'next-auth/react' +import { getUriWithoutOrg } from '@services/config/config'; const SUPPORTED_FILES = constructAcceptValue(['image']) +const validationSchema = Yup.object().shape({ + email: Yup.string().email('Invalid email').required('Email is required'), + username: Yup.string().required('Username is required'), + first_name: Yup.string().required('First name is required'), + last_name: Yup.string().required('Last name is required'), + bio: Yup.string().max(400, 'Bio must be 400 characters or less'), +}) + +interface FormValues { + username: string; + first_name: string; + last_name: string; + email: string; + bio: string; +} + function UserEditGeneral() { const session = useLHSession() as any; const access_token = session?.data?.tokens?.access_token; @@ -40,116 +66,231 @@ function UserEditGeneral() { } } + const handleEmailChange = async (newEmail: string) => { + toast.success('Profile Updated Successfully', { duration: 4000 }) + + // Show message about logging in with new email + toast((t) => ( +
+ Please login again with your new email: {newEmail} +
+ ), { + duration: 4000, + icon: '📧' + }) + + // Wait for 4 seconds before signing out + await new Promise(resolve => setTimeout(resolve, 4000)) + signOut({ redirect: true, callbackUrl: getUriWithoutOrg('/') }) + } + useEffect(() => { }, [session, session.data]) return ( -
+
{session.data.user && ( - enableReinitialize initialValues={{ username: session.data.user.username, first_name: session.data.user.first_name, last_name: session.data.user.last_name, email: session.data.user.email, - bio: session.data.user.bio, + bio: session.data.user.bio || '', }} + validationSchema={validationSchema} onSubmit={(values, { setSubmitting }) => { + const isEmailChanged = values.email !== session.data.user.email + const loadingToast = toast.loading('Updating profile...') + setTimeout(() => { setSubmitting(false) updateProfile(values, session.data.user.id, access_token) + .then(() => { + toast.dismiss(loadingToast) + if (isEmailChanged) { + handleEmailChange(values.email) + } else { + toast.success('Profile Updated Successfully') + } + }) + .catch(() => { + toast.error('Failed to update profile', { id: loadingToast }) + }) }, 400) }} > - {({ isSubmitting }) => ( -
-
-
- {[ - { label: 'Email', name: 'email', type: 'email' }, - { label: 'Username', name: 'username', type: 'text' }, - { label: 'First Name', name: 'first_name', type: 'text' }, - { label: 'Last Name', name: 'last_name', type: 'text' }, - { label: 'Bio', name: 'bio', type: 'text' }, - ].map((field) => ( -
- - -
- ))} + {({ isSubmitting, values, handleChange, errors, touched }) => ( + +
+
+

+ Account Settings +

+

+ Manage your personal information and preferences +

- - -
-
- - {error && ( -
- - {error} -
- )} - {success && ( -
- - {success} -
- )} -
-
- {localAvatar ? ( - - ) : ( - + +
+ {/* Profile Information Section */} +
+
+ + + {touched.email && errors.email && ( +

{errors.email}

)} - {isLoading ? ( -
- - Uploading + {values.email !== session.data.user.email && ( +
+ + You will be logged out after changing your email
- ) : ( - <> - - - + )} +
+ +
+ + + {touched.username && errors.username && ( +

{errors.username}

+ )} +
+ +
+ + + {touched.first_name && errors.first_name && ( +

{errors.first_name}

+ )} +
+ +
+ + + {touched.last_name && errors.last_name && ( +

{errors.last_name}

+ )} +
+ +
+ +