From dad54bad03cb92ae5d3a6656a4c827a78f5dd99a Mon Sep 17 00:00:00 2001 From: Dan Griffiths Date: Sun, 3 Nov 2024 01:18:39 -0600 Subject: [PATCH 1/3] Add profile option to change email address Signed-off-by: Dan Griffiths --- .../ChangeEmailModal/ChangeEmailMessageBox.js | 42 ++++++ .../ChangeEmailModal/ChangeEmailModal.js | 127 ++++++++++++++++++ src/components/ChangeEmailModal/index.js | 1 + src/components/ProfileHeader/ProfileHeader.js | 16 +++ src/store/actionTypes.js | 9 ++ src/store/actions/authentication.js | 11 ++ 6 files changed, 206 insertions(+) create mode 100644 src/components/ChangeEmailModal/ChangeEmailMessageBox.js create mode 100644 src/components/ChangeEmailModal/ChangeEmailModal.js create mode 100644 src/components/ChangeEmailModal/index.js diff --git a/src/components/ChangeEmailModal/ChangeEmailMessageBox.js b/src/components/ChangeEmailModal/ChangeEmailMessageBox.js new file mode 100644 index 00000000..6b0d8186 --- /dev/null +++ b/src/components/ChangeEmailModal/ChangeEmailMessageBox.js @@ -0,0 +1,42 @@ +import PropTypes from 'prop-types' + +import ApiErrorBox from '~/components/MessageBox/ApiErrorBox' + +import MessageBox from '../MessageBox' + + + + + +function getErrorText (error) { + switch (error.status) { + default: + return undefined + } +} + +function ChangeEmailMessageBox (props) { + const { result } = props + + return result.success + ? ( + + {'E-Mail changed! Please login to continue.'} + + ) + : ( + + ) +} + +ChangeEmailMessageBox.propTypes = { + result: PropTypes.object, +} + + + + + +export default ChangeEmailMessageBox diff --git a/src/components/ChangeEmailModal/ChangeEmailModal.js b/src/components/ChangeEmailModal/ChangeEmailModal.js new file mode 100644 index 00000000..82ee523c --- /dev/null +++ b/src/components/ChangeEmailModal/ChangeEmailModal.js @@ -0,0 +1,127 @@ +import Router from 'next/router' +import PropTypes from 'prop-types' +import { useCallback, useMemo, useState } from 'react' +import { useDispatch, useSelector } from 'react-redux' + +import asModal, { ModalContent, ModalFooter } from '~/components/asModal' +import EmailFieldset from '~/components/Fieldsets/EmailFieldset' +import useForm from '~/hooks/useForm' +import { changeEmail } from '~/store/actions/authentication' +import { logout } from '~/store/actions/session' +import { + selectCurrentUserId, + selectUserById, + withCurrentUserId, +} from '~/store/selectors' +import getResponseError from '~/util/getResponseError' + +import ChangeEmailMessageBox from './ChangeEmailMessageBox' + +// Component Constants +const SUBMIT_AUTO_CLOSE_DELAY_TIME = 3000 + + + + + +function ChangeEmailModal (props) { + const { + onClose, + isOpen, + } = props + + const [result, setResult] = useState({}) + const { email } = useSelector(withCurrentUserId(selectUserById))?.attributes ?? {} + + const dispatch = useDispatch() + const onSubmit = useCallback(async (formData) => { + if (result.submitted) { + setResult({ submitted: true }) + } + const response = await dispatch(changeEmail(formData)) + + const error = getResponseError(response) + + setResult({ + error, + success: !error, + submitted: true, + }) + + if (!error) { + setTimeout(() => { + if (isOpen) { + onClose() + } + }, SUBMIT_AUTO_CLOSE_DELAY_TIME) + + await dispatch(logout()) + Router.reload() + } + }, [dispatch, isOpen, onClose, result.submitted]) + + + const userId = useSelector(selectCurrentUserId) + const data = useMemo(() => { + return { + id: userId, + attributes: { + email: '', + }, + } + }, [userId]) + + const { Form, submitting, canSubmit } = useForm({ data, onSubmit }) + + return ( + + + + { + !result.submitted && ( +
+
+ {'Current E-Mail: '} + {email} +
+ {'Enter the new e-mail address you would like to associate with your account below.'} +
+ ) + } + + + + +
+
+ +
+ + + ) +} + +ChangeEmailModal.propTypes = { + isOpen: PropTypes.any, + onClose: PropTypes.func.isRequired, +} + + + + + +export default asModal({ + className: 'email-change-dialog', + title: 'Change E-Mail Address', +})(ChangeEmailModal) diff --git a/src/components/ChangeEmailModal/index.js b/src/components/ChangeEmailModal/index.js new file mode 100644 index 00000000..05047539 --- /dev/null +++ b/src/components/ChangeEmailModal/index.js @@ -0,0 +1 @@ +export { default } from './ChangeEmailModal' diff --git a/src/components/ProfileHeader/ProfileHeader.js b/src/components/ProfileHeader/ProfileHeader.js index d6871be2..256e03eb 100644 --- a/src/components/ProfileHeader/ProfileHeader.js +++ b/src/components/ProfileHeader/ProfileHeader.js @@ -11,6 +11,7 @@ import { } from '~/store/selectors' import formatAsEliteDateTime from '~/util/date/formatAsEliteDateTime' +import ChangeEmailModal from '../ChangeEmailModal' import ChangePasswordModal from '../ChangePasswordModal' import DisableProfileModal from '../DisableProfileModal' import ProfileUserAvatar from '../ProfileUserAvatar' @@ -21,9 +22,16 @@ import UnverifiedUserBanner from './UnverifiedUserBanner' function ProfileHeader () { + const [showChangeEmail, setShowChangeEmail] = useState(false) const [showChangePassword, setShowChangePassword] = useState(false) const [showDisableProfile, setShowDisableProfile] = useState(false) + const handleToggleChangeEmail = useCallback(() => { + setShowChangeEmail((state) => { + return !state + }) + }, []) + const handleToggleChangePassword = useCallback(() => { setShowChangePassword((state) => { return !state @@ -77,6 +85,11 @@ function ProfileHeader () {
+
+ diff --git a/src/store/actionTypes.js b/src/store/actionTypes.js index ead0ed14..f73927d2 100644 --- a/src/store/actionTypes.js +++ b/src/store/actionTypes.js @@ -97,6 +97,14 @@ const oauth = { +const emails = { + update: 'emails/update', +} + + + + + const passwords = { reset: 'passwords/reset', requestReset: 'passwords/requestReset', @@ -171,6 +179,7 @@ const actionTypes = { leaderboard, nicknames, oauth, + emails, passwords, rats, rescues, diff --git a/src/store/actions/authentication.js b/src/store/actions/authentication.js index a8be50b2..54e2ba3f 100644 --- a/src/store/actions/authentication.js +++ b/src/store/actions/authentication.js @@ -19,6 +19,17 @@ const SESSION_TOKEN_LENGTH = 365 // days +export const changeEmail = ({ id, ...data }) => { + return frApiPlainRequest( + actionTypes.emails.update, + { + url: `/users/${id}/email`, + method: 'patch', + data: createRequestBody('email-changes', data), + }, + ) +} + export const changePassword = ({ id, ...data }) => { From 93c198c033ca90402efa476b93f37d244475cf22 Mon Sep 17 00:00:00 2001 From: Dan Griffiths Date: Mon, 4 Nov 2024 08:07:58 -0600 Subject: [PATCH 2/3] Update changelog Signed-off-by: Dan Griffiths --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fec1f41..c4f75491 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ For detailed rules of this file, see [Changelog Rules](#changelog-rules) ### ✨ Added -* +* An option for users to update their registered email address through the user profile. ### ⚡ Changed From 5a97cf5d7f37f26d283b29bd0b4c7b5d810b24ba Mon Sep 17 00:00:00 2001 From: Dan Griffiths Date: Mon, 4 Nov 2024 17:12:43 -0600 Subject: [PATCH 3/3] Move email to user actions Signed-off-by: Dan Griffiths --- .../ChangeEmailModal/ChangeEmailModal.js | 2 +- src/store/actionTypes.js | 12 +++--------- src/store/actions/authentication.js | 13 ------------- src/store/actions/user.js | 14 ++++++++++++-- 4 files changed, 16 insertions(+), 25 deletions(-) diff --git a/src/components/ChangeEmailModal/ChangeEmailModal.js b/src/components/ChangeEmailModal/ChangeEmailModal.js index 82ee523c..e18f836a 100644 --- a/src/components/ChangeEmailModal/ChangeEmailModal.js +++ b/src/components/ChangeEmailModal/ChangeEmailModal.js @@ -6,8 +6,8 @@ import { useDispatch, useSelector } from 'react-redux' import asModal, { ModalContent, ModalFooter } from '~/components/asModal' import EmailFieldset from '~/components/Fieldsets/EmailFieldset' import useForm from '~/hooks/useForm' -import { changeEmail } from '~/store/actions/authentication' import { logout } from '~/store/actions/session' +import { changeEmail } from '~/store/actions/user' import { selectCurrentUserId, selectUserById, diff --git a/src/store/actionTypes.js b/src/store/actionTypes.js index f73927d2..4a9d57d9 100644 --- a/src/store/actionTypes.js +++ b/src/store/actionTypes.js @@ -70,6 +70,9 @@ const users = { avatar: { update: 'users/avatar/update', }, + email: { + update: 'users/email/update', + }, } @@ -97,14 +100,6 @@ const oauth = { -const emails = { - update: 'emails/update', -} - - - - - const passwords = { reset: 'passwords/reset', requestReset: 'passwords/requestReset', @@ -179,7 +174,6 @@ const actionTypes = { leaderboard, nicknames, oauth, - emails, passwords, rats, rescues, diff --git a/src/store/actions/authentication.js b/src/store/actions/authentication.js index 54e2ba3f..01789fa4 100644 --- a/src/store/actions/authentication.js +++ b/src/store/actions/authentication.js @@ -19,19 +19,6 @@ const SESSION_TOKEN_LENGTH = 365 // days -export const changeEmail = ({ id, ...data }) => { - return frApiPlainRequest( - actionTypes.emails.update, - { - url: `/users/${id}/email`, - method: 'patch', - data: createRequestBody('email-changes', data), - }, - ) -} - - - export const changePassword = ({ id, ...data }) => { return frApiPlainRequest( actionTypes.passwords.update, diff --git a/src/store/actions/user.js b/src/store/actions/user.js index be547059..1eb070be 100644 --- a/src/store/actions/user.js +++ b/src/store/actions/user.js @@ -7,8 +7,7 @@ import createRequestBody from '~/util/jsonapi/createRequestBody' import actionTypes from '../actionTypes' import { deletesResource, deletesRelationship, createsRelationship, RESOURCE } from '../reducers/frAPIResources' import { withCurrentUserId, selectUserById, selectCurrentUserId } from '../selectors' -import { frApiRequest } from './services' - +import { frApiRequest, frApiPlainRequest } from './services' export const getNickname = (nickId) => { return frApiRequest( @@ -93,3 +92,14 @@ export const updateAvatar = (data) => { return dispatch(frApiRequest(actionTypes.users.avatar.update, request)) } } + +export const changeEmail = ({ id, ...data }) => { + return frApiPlainRequest( + actionTypes.users.email.update, + { + url: `/users/${id}/email`, + method: 'patch', + data: createRequestBody('email-changes', data), + }, + ) +}