From d3caadf4aff85d51508dd291c6ccc132b9a8a0bb Mon Sep 17 00:00:00 2001 From: "kasia.oczkowska" Date: Fri, 27 Sep 2024 11:48:59 +0100 Subject: [PATCH] feat: refactor update-current-user mutation with the REST API endpoint --- .../backend/src/graphql/mutation-resolvers.js | 2 - .../graphql/mutations/update-current-user.js | 11 -- packages/backend/src/graphql/schema.graphql | 7 - .../components/AcceptInvitationForm/index.jsx | 3 +- .../graphql/mutations/update-current-user.js | 10 - .../web/src/hooks/useUpdateCurrentUser.js | 19 ++ .../src/hooks/useUpdateCurrentUserPassword.js | 14 ++ packages/web/src/locales/en.json | 5 + .../web/src/pages/ProfileSettings/index.jsx | 186 +++++++++++++----- 9 files changed, 180 insertions(+), 77 deletions(-) delete mode 100644 packages/backend/src/graphql/mutations/update-current-user.js delete mode 100644 packages/web/src/graphql/mutations/update-current-user.js create mode 100644 packages/web/src/hooks/useUpdateCurrentUser.js create mode 100644 packages/web/src/hooks/useUpdateCurrentUserPassword.js diff --git a/packages/backend/src/graphql/mutation-resolvers.js b/packages/backend/src/graphql/mutation-resolvers.js index 2c2379ec..f29b5f57 100644 --- a/packages/backend/src/graphql/mutation-resolvers.js +++ b/packages/backend/src/graphql/mutation-resolvers.js @@ -1,6 +1,5 @@ // Converted mutations import verifyConnection from './mutations/verify-connection.js'; -import updateCurrentUser from './mutations/update-current-user.js'; import generateAuthUrl from './mutations/generate-auth-url.js'; import resetConnection from './mutations/reset-connection.js'; import updateConnection from './mutations/update-connection.js'; @@ -9,7 +8,6 @@ const mutationResolvers = { generateAuthUrl, resetConnection, updateConnection, - updateCurrentUser, verifyConnection, }; diff --git a/packages/backend/src/graphql/mutations/update-current-user.js b/packages/backend/src/graphql/mutations/update-current-user.js deleted file mode 100644 index 187d78c9..00000000 --- a/packages/backend/src/graphql/mutations/update-current-user.js +++ /dev/null @@ -1,11 +0,0 @@ -const updateCurrentUser = async (_parent, params, context) => { - const user = await context.currentUser.$query().patchAndFetch({ - email: params.input.email, - password: params.input.password, - fullName: params.input.fullName, - }); - - return user; -}; - -export default updateCurrentUser; diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index 27c5d980..d3b753d3 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -5,7 +5,6 @@ type Mutation { generateAuthUrl(input: GenerateAuthUrlInput): AuthLink resetConnection(input: ResetConnectionInput): Connection updateConnection(input: UpdateConnectionInput): Connection - updateCurrentUser(input: UpdateCurrentUserInput): User verifyConnection(input: VerifyConnectionInput): Connection } @@ -223,12 +222,6 @@ input UserRoleInput { id: String } -input UpdateCurrentUserInput { - email: String - password: String - fullName: String -} - """ The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). """ diff --git a/packages/web/src/components/AcceptInvitationForm/index.jsx b/packages/web/src/components/AcceptInvitationForm/index.jsx index df26f735..8e04b6c6 100644 --- a/packages/web/src/components/AcceptInvitationForm/index.jsx +++ b/packages/web/src/components/AcceptInvitationForm/index.jsx @@ -86,7 +86,6 @@ export default function ResetPasswordForm() { : '' } /> - diff --git a/packages/web/src/graphql/mutations/update-current-user.js b/packages/web/src/graphql/mutations/update-current-user.js deleted file mode 100644 index 936d5ee2..00000000 --- a/packages/web/src/graphql/mutations/update-current-user.js +++ /dev/null @@ -1,10 +0,0 @@ -import { gql } from '@apollo/client'; -export const UPDATE_CURRENT_USER = gql` - mutation UpdateCurrentUser($input: UpdateCurrentUserInput) { - updateCurrentUser(input: $input) { - id - fullName - email - } - } -`; diff --git a/packages/web/src/hooks/useUpdateCurrentUser.js b/packages/web/src/hooks/useUpdateCurrentUser.js new file mode 100644 index 00000000..f9a22d79 --- /dev/null +++ b/packages/web/src/hooks/useUpdateCurrentUser.js @@ -0,0 +1,19 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import api from 'helpers/api'; + +export default function useUpdateCurrentUser(userId) { + const queryClient = useQueryClient(); + + const query = useMutation({ + mutationFn: async (payload) => { + const { data } = await api.patch(`/v1/users/${userId}`, payload); + + return data; + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['users', 'me'] }); + }, + }); + + return query; +} diff --git a/packages/web/src/hooks/useUpdateCurrentUserPassword.js b/packages/web/src/hooks/useUpdateCurrentUserPassword.js new file mode 100644 index 00000000..82c9a439 --- /dev/null +++ b/packages/web/src/hooks/useUpdateCurrentUserPassword.js @@ -0,0 +1,14 @@ +import { useMutation } from '@tanstack/react-query'; +import api from 'helpers/api'; + +export default function useUpdateCurrentUserPassword(userId) { + const query = useMutation({ + mutationFn: async (payload) => { + const { data } = await api.patch(`/v1/users/${userId}/password`, payload); + + return data; + }, + }); + + return query; +} diff --git a/packages/web/src/locales/en.json b/packages/web/src/locales/en.json index 8e0a4d1e..cc1ba1d3 100644 --- a/packages/web/src/locales/en.json +++ b/packages/web/src/locales/en.json @@ -102,7 +102,12 @@ "profileSettings.fullName": "Full name", "profileSettings.email": "Email", "profileSettings.updateProfile": "Update your profile", + "profileSettings.updatePassword": "Update your password", "profileSettings.updatedProfile": "Your profile has been updated.", + "profileSettings.updatedPassword": "Your password has been updated.", + "profileSettings.updateProfileError": "Something went wrong while updating your profile.", + "profileSettings.updatePasswordError": "Something went wrong while updating your password.", + "profileSettings.currentPassword": "Current password", "profileSettings.newPassword": "New password", "profileSettings.confirmNewPassword": "Confirm new password", "profileSettings.deleteMyAccount": "Delete my account", diff --git a/packages/web/src/pages/ProfileSettings/index.jsx b/packages/web/src/pages/ProfileSettings/index.jsx index cac39e2d..d0479525 100644 --- a/packages/web/src/pages/ProfileSettings/index.jsx +++ b/packages/web/src/pages/ProfileSettings/index.jsx @@ -1,4 +1,3 @@ -import { useMutation } from '@apollo/client'; import { yupResolver } from '@hookform/resolvers/yup'; import Alert from '@mui/material/Alert'; import AlertTitle from '@mui/material/AlertTitle'; @@ -15,19 +14,26 @@ import DeleteAccountDialog from 'components/DeleteAccountDialog/index.ee'; import Form from 'components/Form'; import PageTitle from 'components/PageTitle'; import TextField from 'components/TextField'; -import { UPDATE_CURRENT_USER } from 'graphql/mutations/update-current-user'; import useCurrentUser from 'hooks/useCurrentUser'; import useFormatMessage from 'hooks/useFormatMessage'; -import { useQueryClient } from '@tanstack/react-query'; +import useUpdateCurrentUser from 'hooks/useUpdateCurrentUser'; +import useUpdateCurrentUserPassword from 'hooks/useUpdateCurrentUserPassword'; -const validationSchema = yup +const validationSchemaProfile = yup .object({ - fullName: yup.string().required(), - email: yup.string().email().required(), - password: yup.string(), + fullName: yup.string().required('Full name is required'), + email: yup.string().email().required('Email is required'), + }) + .required(); + +const validationSchemaPassword = yup + .object({ + currentPassword: yup.string().required('Current password is required'), + password: yup.string().required('New password is required'), confirmPassword: yup .string() - .oneOf([yup.ref('password')], 'Passwords must match'), + .oneOf([yup.ref('password')], 'Passwords must match') + .required('Confirm password is required'), }) .required(); @@ -37,6 +43,24 @@ const StyledForm = styled(Form)` flex-direction: column; `; +const getErrorMessage = (error) => { + const errors = error?.response?.data?.errors || {}; + const errorMessages = Object.entries(errors) + .map(([key, messages]) => { + if (Array.isArray(messages) && messages.length) { + return `${key} ${messages.join(', ')}`; + } + if (typeof messages === 'string') { + return `${key} ${messages}`; + } + return ''; + }) + .filter((message) => !!message) + .join(' '); + + return errorMessages; +}; + function ProfileSettings() { const [showDeleteAccountConfirmation, setShowDeleteAccountConfirmation] = React.useState(false); @@ -44,42 +68,65 @@ function ProfileSettings() { const { data } = useCurrentUser(); const currentUser = data?.data; const formatMessage = useFormatMessage(); - const [updateCurrentUser] = useMutation(UPDATE_CURRENT_USER); - const queryClient = useQueryClient(); + const { mutateAsync: updateCurrentUser } = useUpdateCurrentUser( + currentUser?.id, + ); + const { mutateAsync: updateCurrentUserPassword } = + useUpdateCurrentUserPassword(currentUser?.id); const handleProfileSettingsUpdate = async (data) => { - const { fullName, password, email } = data; - const mutationInput = { - fullName, - email, - }; + try { + const { fullName, email } = data; - if (password) { - mutationInput.password = password; - } + await updateCurrentUser({ fullName, email }); - await updateCurrentUser({ - variables: { - input: mutationInput, - }, - optimisticResponse: { - updateCurrentUser: { - __typename: 'User', - id: currentUser.id, - fullName, - email, + enqueueSnackbar(formatMessage('profileSettings.updatedProfile'), { + variant: 'success', + SnackbarProps: { + 'data-test': 'snackbar-update-profile-settings-success', }, - }, - }); + }); + } catch (error) { + enqueueSnackbar( + getErrorMessage(error) || + formatMessage('profileSettings.updateProfileError'), + { + variant: 'error', + SnackbarProps: { + 'data-test': 'snackbar-update-profile-settings-error', + }, + }, + ); + } + }; - await queryClient.invalidateQueries({ queryKey: ['users', 'me'] }); + const handlePasswordUpdate = async (data) => { + try { + const { password, currentPassword } = data; - enqueueSnackbar(formatMessage('profileSettings.updatedProfile'), { - variant: 'success', - SnackbarProps: { - 'data-test': 'snackbar-update-profile-settings-success', - }, - }); + await updateCurrentUserPassword({ + currentPassword, + password, + }); + + enqueueSnackbar(formatMessage('profileSettings.updatedPassword'), { + variant: 'success', + SnackbarProps: { + 'data-test': 'snackbar-update-password-success', + }, + }); + } catch (error) { + enqueueSnackbar( + getErrorMessage(error) || + formatMessage('profileSettings.updatePasswordError'), + { + variant: 'error', + SnackbarProps: { + 'data-test': 'snackbar-update-password-error', + }, + }, + ); + } }; return ( @@ -93,11 +140,9 @@ function ProfileSettings() { - + + + )} + /> + + + + ( + <> + - - )}