first commit
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import { MergeAndOverride } from '../../../../../../../../types/utils'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import OLModal, {
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
import { type UserEmailData } from '../../../../../../../../types/user-email'
|
||||
|
||||
type ConfirmationModalProps = MergeAndOverride<
|
||||
React.ComponentProps<typeof OLModal>,
|
||||
{
|
||||
email: string
|
||||
isConfirmDisabled: boolean
|
||||
onConfirm: () => void
|
||||
onHide: () => void
|
||||
primary?: UserEmailData
|
||||
}
|
||||
>
|
||||
|
||||
function ConfirmationModal({
|
||||
email,
|
||||
isConfirmDisabled,
|
||||
show,
|
||||
onConfirm,
|
||||
onHide,
|
||||
primary,
|
||||
}: ConfirmationModalProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<OLModal show={show} onHide={onHide}>
|
||||
<OLModalHeader closeButton>
|
||||
<OLModalTitle>{t('confirm_primary_email_change')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
<OLModalBody className="pb-0">
|
||||
<p>
|
||||
<Trans
|
||||
i18nKey="do_you_want_to_change_your_primary_email_address_to"
|
||||
components={{ b: <b /> }}
|
||||
values={{ email }}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
/>
|
||||
</p>
|
||||
<p>{t('log_in_with_primary_email_address')}</p>
|
||||
{primary && !primary.confirmedAt && (
|
||||
<p>
|
||||
<Trans
|
||||
i18nKey="this_will_remove_primary_email"
|
||||
components={{ b: <b /> }}
|
||||
values={{ email: primary.email }}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
</OLModalBody>
|
||||
<OLModalFooter>
|
||||
<OLButton variant="secondary" onClick={onHide}>
|
||||
{t('cancel')}
|
||||
</OLButton>
|
||||
<OLButton
|
||||
variant="primary"
|
||||
disabled={isConfirmDisabled}
|
||||
onClick={onConfirm}
|
||||
>
|
||||
{t('change_primary_email')}
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfirmationModal
|
@@ -0,0 +1,132 @@
|
||||
import { useState } from 'react'
|
||||
import PrimaryButton from './primary-button'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
inReconfirmNotificationPeriod,
|
||||
institutionAlreadyLinked,
|
||||
} from '../../../../utils/selectors'
|
||||
import { postJSON } from '../../../../../../infrastructure/fetch-json'
|
||||
import {
|
||||
State,
|
||||
useUserEmailsContext,
|
||||
} from '../../../../context/user-email-context'
|
||||
import { UserEmailData } from '../../../../../../../../types/user-email'
|
||||
import { UseAsyncReturnType } from '../../../../../../shared/hooks/use-async'
|
||||
import { ssoAvailableForInstitution } from '../../../../utils/sso'
|
||||
import ConfirmationModal from './confirmation-modal'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
|
||||
const getDescription = (
|
||||
t: (s: string) => string,
|
||||
state: State,
|
||||
userEmailData: UserEmailData
|
||||
) => {
|
||||
if (inReconfirmNotificationPeriod(userEmailData)) {
|
||||
return t('please_reconfirm_your_affiliation_before_making_this_primary')
|
||||
}
|
||||
|
||||
if (userEmailData.confirmedAt) {
|
||||
return t('make_email_primary_description')
|
||||
}
|
||||
|
||||
const ssoAvailable = ssoAvailableForInstitution(
|
||||
userEmailData.affiliation?.institution || null
|
||||
)
|
||||
|
||||
if (!institutionAlreadyLinked(state, userEmailData) && ssoAvailable) {
|
||||
return t('please_link_before_making_primary')
|
||||
}
|
||||
|
||||
return t('please_confirm_your_email_before_making_it_default')
|
||||
}
|
||||
|
||||
type MakePrimaryProps = {
|
||||
userEmailData: UserEmailData
|
||||
primary?: UserEmailData
|
||||
makePrimaryAsync: UseAsyncReturnType
|
||||
}
|
||||
|
||||
function MakePrimary({
|
||||
userEmailData,
|
||||
primary,
|
||||
makePrimaryAsync,
|
||||
}: MakePrimaryProps) {
|
||||
const [show, setShow] = useState(false)
|
||||
const { t } = useTranslation()
|
||||
const { state, makePrimary, deleteEmail, resetLeaversSurveyExpiration } =
|
||||
useUserEmailsContext()
|
||||
|
||||
const handleShowModal = () => setShow(true)
|
||||
const handleHideModal = () => setShow(false)
|
||||
const handleSetDefaultUserEmail = () => {
|
||||
handleHideModal()
|
||||
|
||||
makePrimaryAsync
|
||||
.runAsync(
|
||||
// 'delete-unconfirmed-primary' is a temporary parameter here to keep backward compatibility.
|
||||
// So users with the old version of the frontend don't get their primary email deleted unexpectedly.
|
||||
// https://github.com/overleaf/internal/issues/23536
|
||||
postJSON('/user/emails/default?delete-unconfirmed-primary', {
|
||||
body: {
|
||||
email: userEmailData.email,
|
||||
},
|
||||
})
|
||||
)
|
||||
.then(() => {
|
||||
makePrimary(userEmailData.email)
|
||||
if (primary && !primary.confirmedAt) {
|
||||
deleteEmail(primary.email)
|
||||
resetLeaversSurveyExpiration(primary)
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
if (userEmailData.default) {
|
||||
return null
|
||||
}
|
||||
|
||||
const isConfirmDisabled = Boolean(
|
||||
!userEmailData.confirmedAt ||
|
||||
state.isLoading ||
|
||||
inReconfirmNotificationPeriod(userEmailData)
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
{makePrimaryAsync.isLoading ? (
|
||||
<PrimaryButton disabled isLoading={state.isLoading}>
|
||||
{t('processing_uppercase')}…
|
||||
</PrimaryButton>
|
||||
) : (
|
||||
<OLTooltip
|
||||
id={`make-primary-${userEmailData.email}`}
|
||||
description={getDescription(t, state, userEmailData)}
|
||||
>
|
||||
{/*
|
||||
Disabled buttons don't work with tooltips, due to pointer-events: none,
|
||||
so create a wrapper for the tooltip
|
||||
*/}
|
||||
<span>
|
||||
<PrimaryButton
|
||||
disabled={isConfirmDisabled}
|
||||
onClick={handleShowModal}
|
||||
>
|
||||
{t('make_primary')}
|
||||
</PrimaryButton>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
)}
|
||||
<ConfirmationModal
|
||||
email={userEmailData.email}
|
||||
isConfirmDisabled={isConfirmDisabled}
|
||||
primary={primary}
|
||||
show={show}
|
||||
onHide={handleHideModal}
|
||||
onConfirm={handleSetDefaultUserEmail}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default MakePrimary
|
@@ -0,0 +1,22 @@
|
||||
import OLButton, { OLButtonProps } from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
function PrimaryButton({
|
||||
children,
|
||||
disabled,
|
||||
isLoading,
|
||||
onClick,
|
||||
}: OLButtonProps) {
|
||||
return (
|
||||
<OLButton
|
||||
size="sm"
|
||||
disabled={disabled && !isLoading}
|
||||
isLoading={isLoading}
|
||||
onClick={onClick}
|
||||
variant="secondary"
|
||||
>
|
||||
{children}
|
||||
</OLButton>
|
||||
)
|
||||
}
|
||||
|
||||
export default PrimaryButton
|
@@ -0,0 +1,89 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { UserEmailData } from '../../../../../../../types/user-email'
|
||||
import { useUserEmailsContext } from '../../../context/user-email-context'
|
||||
import { postJSON } from '../../../../../infrastructure/fetch-json'
|
||||
import { UseAsyncReturnType } from '../../../../../shared/hooks/use-async'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import OLIconButton, {
|
||||
OLIconButtonProps,
|
||||
} from '@/features/ui/components/ol/ol-icon-button'
|
||||
import getMeta from '@/utils/meta'
|
||||
|
||||
type DeleteButtonProps = Pick<
|
||||
OLIconButtonProps,
|
||||
'disabled' | 'isLoading' | 'onClick'
|
||||
>
|
||||
|
||||
function DeleteButton({ disabled, isLoading, onClick }: DeleteButtonProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<OLIconButton
|
||||
variant="danger"
|
||||
disabled={disabled}
|
||||
isLoading={isLoading}
|
||||
size="sm"
|
||||
onClick={onClick}
|
||||
accessibilityLabel={t('remove') || ''}
|
||||
icon="delete"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type RemoveProps = {
|
||||
userEmailData: UserEmailData
|
||||
deleteEmailAsync: UseAsyncReturnType
|
||||
}
|
||||
|
||||
function Remove({ userEmailData, deleteEmailAsync }: RemoveProps) {
|
||||
const { t } = useTranslation()
|
||||
const { state, deleteEmail, resetLeaversSurveyExpiration } =
|
||||
useUserEmailsContext()
|
||||
const isManaged = getMeta('ol-isManagedAccount')
|
||||
|
||||
const getTooltipText = () => {
|
||||
if (isManaged) {
|
||||
return t('your_account_is_managed_by_your_group_admin')
|
||||
}
|
||||
return userEmailData.default
|
||||
? t('please_change_primary_to_remove')
|
||||
: t('remove')
|
||||
}
|
||||
|
||||
const handleRemoveUserEmail = () => {
|
||||
deleteEmailAsync
|
||||
.runAsync(
|
||||
postJSON('/user/emails/delete', {
|
||||
body: {
|
||||
email: userEmailData.email,
|
||||
},
|
||||
})
|
||||
)
|
||||
.then(() => {
|
||||
deleteEmail(userEmailData.email)
|
||||
resetLeaversSurveyExpiration(userEmailData)
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
if (deleteEmailAsync.isLoading) {
|
||||
return <DeleteButton isLoading />
|
||||
}
|
||||
|
||||
return (
|
||||
<OLTooltip
|
||||
id={userEmailData.email}
|
||||
description={getTooltipText()}
|
||||
overlayProps={{ placement: userEmailData.default ? 'left' : 'top' }}
|
||||
>
|
||||
<span>
|
||||
<DeleteButton
|
||||
disabled={state.isLoading || userEmailData.default}
|
||||
onClick={handleRemoveUserEmail}
|
||||
/>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default Remove
|
Reference in New Issue
Block a user