mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-15 06:33:55 +01:00
[web] Use 6-digits code to confirm existing email in Account Settings (#23931)
* Rename `checkSecondaryEmailConfirmationCode` to `checkAddSecondaryEmailConfirmationCode` * Create function `sendCodeAndStoreInSession` * Create function `sendExistingSecondaryEmailConfirmationCode` * Create function `_checkConfirmationCode` * Create function `checkExistingEmailConfirmationCode` * Rename `resendSecondaryEmailConfirmationCode` to `resendAddSecondaryEmailConfirmationCode` * Create function `_resendConfirmationCode` * Create function `resendExistingSecondaryEmailConfirmationCode` * Add `ResendConfirmationCodeModal` * Remove `ResendConfirmationEmailButton` * `bin/run web npm run extract-translations` * Update frontend test * Fix: don't throw on render when send-confirmation-code fails! * Update phrasing in the UI Per https://docs.google.com/document/d/1PE1vlZWQN--PjmXpyHR9rV2YPd7OIPIsUbnZaHj0cDI/edit?usp=sharing * Add unit test * Don't share the "send-confirmation" and "resend-confirmation" rate-limits * Update frontend test after copy change * Rename `checkAddSecondaryEmailConfirmationCode` to `checkNewSecondaryEmailConfirmationCode` and `resendAddSecondaryEmailConfirmationCode` to `resendNewSecondaryEmailConfirmationCode` * Rename `cb` to `beforeConfirmEmail` Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com> * Return `422` on missing session data Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com> * Add `userId` to log * Replace `isSecondary` param by `welcomeUser` Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com> * Rename `resend-confirm-email-code`'s `existingEmail` to `email` * Remove "secondary" from rate-limiters Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com> * Remove unnecessary `userId` check behind `AuthenticationController.requireLogin()` * Only open the modal if the code was sent successfully --------- Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com> GitOrigin-RevId: df892064641d9f722785699777383b2d863124e1
This commit is contained in:
committed by
Copybot
co-authored by
Jakob Ackermann
parent
e52f3543a7
commit
c4e6dfbbbd
@@ -508,6 +508,7 @@
|
||||
"enter_6_digit_code": "",
|
||||
"enter_any_size_including_units_or_valid_latex_command": "",
|
||||
"enter_image_url": "",
|
||||
"enter_the_code": "",
|
||||
"enter_the_confirmation_code": "",
|
||||
"enter_the_number_of_users_youd_like_to_add_to_see_the_cost_breakdown": "",
|
||||
"equation_generator": "",
|
||||
@@ -1184,7 +1185,6 @@
|
||||
"please_ask_the_project_owner_to_upgrade_more_editors": "",
|
||||
"please_ask_the_project_owner_to_upgrade_to_track_changes": "",
|
||||
"please_change_primary_to_remove": "",
|
||||
"please_check_your_inbox": "",
|
||||
"please_check_your_inbox_to_confirm": "",
|
||||
"please_compile_pdf_before_download": "",
|
||||
"please_compile_pdf_before_word_count": "",
|
||||
@@ -1975,6 +1975,7 @@
|
||||
"we_do_not_share_personal_information": "",
|
||||
"we_got_your_request": "",
|
||||
"we_logged_you_in": "",
|
||||
"we_sent_code": "",
|
||||
"we_sent_new_code": "",
|
||||
"we_will_charge_you_now_for_the_cost_of_your_additional_users_based_on_remaining_months": "",
|
||||
"we_will_charge_you_now_for_your_new_plan_based_on_the_remaining_months_of_your_current_subscription": "",
|
||||
|
||||
+13
-6
@@ -25,6 +25,7 @@ type ConfirmEmailFormProps = {
|
||||
email?: string
|
||||
onSuccessfulConfirmation?: () => void
|
||||
interstitial: boolean
|
||||
isModal?: boolean
|
||||
onCancel?: () => void
|
||||
}
|
||||
|
||||
@@ -37,6 +38,7 @@ export function ConfirmEmailForm({
|
||||
email = getMeta('ol-email'),
|
||||
onSuccessfulConfirmation,
|
||||
interstitial,
|
||||
isModal,
|
||||
onCancel,
|
||||
}: ConfirmEmailFormProps) {
|
||||
const { t } = useTranslation()
|
||||
@@ -156,6 +158,13 @@ export function ConfirmEmailForm({
|
||||
)
|
||||
}
|
||||
|
||||
let intro = <h5 className="h5">{t('confirm_your_email')}</h5>
|
||||
if (isModal) intro = <h5 className="h5">{t('we_sent_code')}</h5>
|
||||
if (interstitial)
|
||||
intro = (
|
||||
<h1 className="h3 interstitial-header">{t('confirm_your_email')}</h1>
|
||||
)
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={submitHandler}
|
||||
@@ -172,14 +181,12 @@ export function ConfirmEmailForm({
|
||||
/>
|
||||
)}
|
||||
|
||||
{interstitial ? (
|
||||
<h1 className="h3 interstitial-header">{t('confirm_your_email')}</h1>
|
||||
) : (
|
||||
<h5 className="h5">{t('confirm_your_email')}</h5>
|
||||
)}
|
||||
{intro}
|
||||
|
||||
<OLFormLabel htmlFor="one-time-code">
|
||||
{t('enter_the_confirmation_code', { email })}
|
||||
{isModal
|
||||
? t('enter_the_code', { email })
|
||||
: t('enter_the_confirmation_code', { email })}
|
||||
</OLFormLabel>
|
||||
<input
|
||||
id="one-time-code"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { UserEmailData } from '../../../../../../types/user-email'
|
||||
import ResendConfirmationEmailButton from './resend-confirmation-email-button'
|
||||
import { ssoAvailableForInstitution } from '../../utils/sso'
|
||||
import OLBadge from '@/features/ui/components/ol/ol-badge'
|
||||
import { isBootstrap5 } from '@/features/utils/bootstrap-5'
|
||||
import classnames from 'classnames'
|
||||
import ResendConfirmationCodeModal from '@/features/settings/components/emails/resend-confirmation-code-modal'
|
||||
|
||||
type EmailProps = {
|
||||
userEmailData: UserEmailData
|
||||
@@ -29,13 +29,10 @@ function Email({ userEmailData }: EmailProps) {
|
||||
{userEmailData.email}
|
||||
{!userEmailData.confirmedAt && (
|
||||
<div className="small">
|
||||
<strong>
|
||||
{t('unconfirmed')}.
|
||||
{!ssoAvailable && <span> {t('please_check_your_inbox')}.</span>}
|
||||
</strong>
|
||||
<strong>{t('unconfirmed')}.</strong>
|
||||
<br />
|
||||
{!ssoAvailable && (
|
||||
<ResendConfirmationEmailButton email={userEmailData.email} />
|
||||
<ResendConfirmationCodeModal email={userEmailData.email} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Icon from '../../../../shared/components/icon'
|
||||
import { FetchError, postJSON } from '@/infrastructure/fetch-json'
|
||||
import useAsync from '../../../../shared/hooks/use-async'
|
||||
import { UserEmailData } from '../../../../../../types/user-email'
|
||||
import { useUserEmailsContext } from '../../context/user-email-context'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import OLModal, {
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
import { ConfirmEmailForm } from '@/features/settings/components/emails/confirm-email-form'
|
||||
|
||||
type ResendConfirmationEmailButtonProps = {
|
||||
email: UserEmailData['email']
|
||||
}
|
||||
|
||||
function ResendConfirmationCodeModal({
|
||||
email,
|
||||
}: ResendConfirmationEmailButtonProps) {
|
||||
const { t } = useTranslation()
|
||||
const { error, isLoading, isError, runAsync } = useAsync()
|
||||
const {
|
||||
state,
|
||||
setLoading: setUserEmailsContextLoading,
|
||||
getEmails,
|
||||
} = useUserEmailsContext()
|
||||
const [modalVisible, setModalVisible] = useState(false)
|
||||
|
||||
// Update global isLoading prop
|
||||
useEffect(() => {
|
||||
setUserEmailsContextLoading(isLoading)
|
||||
}, [setUserEmailsContextLoading, isLoading])
|
||||
|
||||
const handleResendConfirmationEmail = async () => {
|
||||
await runAsync(
|
||||
postJSON('/user/emails/send-confirmation-code', { body: { email } })
|
||||
)
|
||||
.then(() => setModalVisible(true))
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<>
|
||||
<Icon type="refresh" spin fw /> {t('sending')}…
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const rateLimited =
|
||||
error && error instanceof FetchError && error.response?.status === 429
|
||||
|
||||
return (
|
||||
<>
|
||||
{modalVisible && (
|
||||
<OLModal
|
||||
animation
|
||||
show={modalVisible}
|
||||
onHide={() => setModalVisible(false)}
|
||||
id="action-project-modal"
|
||||
backdrop="static"
|
||||
>
|
||||
<OLModalHeader closeButton>
|
||||
<OLModalTitle>{t('confirm_your_email')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
|
||||
<OLModalBody>
|
||||
<ConfirmEmailForm
|
||||
isModal
|
||||
flow="resend"
|
||||
interstitial={false}
|
||||
resendEndpoint="/user/emails/resend-confirmation-code"
|
||||
confirmationEndpoint="/user/emails/confirm-code"
|
||||
email={email}
|
||||
onSuccessfulConfirmation={() => {
|
||||
getEmails()
|
||||
setModalVisible(false)
|
||||
}}
|
||||
/>
|
||||
</OLModalBody>
|
||||
<OLModalFooter>
|
||||
<OLButton
|
||||
variant="secondary"
|
||||
disabled={isLoading}
|
||||
onClick={() => setModalVisible(false)}
|
||||
>
|
||||
{t('cancel')}
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
)}
|
||||
<OLButton
|
||||
variant="link"
|
||||
disabled={state.isLoading || isLoading}
|
||||
onClick={handleResendConfirmationEmail}
|
||||
className="btn-inline-link"
|
||||
>
|
||||
{t('resend_confirmation_code')}
|
||||
</OLButton>
|
||||
<br />
|
||||
{isError && (
|
||||
<div className="text-danger">
|
||||
{rateLimited
|
||||
? t('too_many_requests')
|
||||
: t('generic_something_went_wrong')}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ResendConfirmationCodeModal
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Icon from '../../../../shared/components/icon'
|
||||
import { FetchError, postJSON } from '../../../../infrastructure/fetch-json'
|
||||
import useAsync from '../../../../shared/hooks/use-async'
|
||||
import { UserEmailData } from '../../../../../../types/user-email'
|
||||
import { useUserEmailsContext } from '../../context/user-email-context'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
type ResendConfirmationEmailButtonProps = {
|
||||
email: UserEmailData['email']
|
||||
}
|
||||
|
||||
function ResendConfirmationEmailButton({
|
||||
email,
|
||||
}: ResendConfirmationEmailButtonProps) {
|
||||
const { t } = useTranslation()
|
||||
const { error, isLoading, isError, runAsync } = useAsync()
|
||||
const { state, setLoading: setUserEmailsContextLoading } =
|
||||
useUserEmailsContext()
|
||||
|
||||
// Update global isLoading prop
|
||||
useEffect(() => {
|
||||
setUserEmailsContextLoading(isLoading)
|
||||
}, [setUserEmailsContextLoading, isLoading])
|
||||
|
||||
const handleResendConfirmationEmail = () => {
|
||||
runAsync(
|
||||
postJSON('/user/emails/resend_confirmation', {
|
||||
body: {
|
||||
email,
|
||||
},
|
||||
})
|
||||
).catch(() => {})
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<>
|
||||
<Icon type="refresh" spin fw /> {t('sending')}…
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const rateLimited =
|
||||
error && error instanceof FetchError && error.response?.status === 429
|
||||
|
||||
return (
|
||||
<>
|
||||
<OLButton
|
||||
variant="link"
|
||||
disabled={state.isLoading || isLoading}
|
||||
onClick={handleResendConfirmationEmail}
|
||||
className="btn-inline-link"
|
||||
>
|
||||
{t('resend_confirmation_email')}
|
||||
</OLButton>
|
||||
<br />
|
||||
{isError && (
|
||||
<div className="text-danger">
|
||||
{rateLimited
|
||||
? t('too_many_requests')
|
||||
: t('generic_something_went_wrong')}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ResendConfirmationEmailButton
|
||||
@@ -204,8 +204,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
#settings-page-root {
|
||||
.confirm-email-form {
|
||||
#settings-page-root,
|
||||
#action-project-modal {
|
||||
&#settings-page-root .confirm-email-form {
|
||||
background: var(--bg-light-secondary);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user