| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- import { useState, useEffect } from 'react'
- import { useTranslation, Trans } from 'react-i18next'
- import Cell from './cell'
- import Layout from './add-email/layout'
- import Input, { DomainInfo } from './add-email/input'
- import AddAnotherEmailBtn from './add-email/add-another-email-btn'
- import InstitutionFields from './add-email/institution-fields'
- import SsoLinkingInfo from './add-email/sso-linking-info'
- import AddNewEmailBtn from './add-email/add-new-email-btn'
- import useAsync from '../../../../shared/hooks/use-async'
- import { useUserEmailsContext } from '../../context/user-email-context'
- import { ssoAvailableForDomain } from '../../utils/sso'
- import { postJSON } from '../../../../infrastructure/fetch-json'
- import { University } from '../../../../../../types/university'
- import { CountryCode } from '../../data/countries-list'
- import { isValidEmail } from '../../../../shared/utils/email'
- import getMeta from '../../../../utils/meta'
- import { ReCaptcha2 } from '../../../../shared/components/recaptcha-2'
- import { useRecaptcha } from '../../../../shared/hooks/use-recaptcha'
- import OLCol from '@/shared/components/ol/ol-col'
- import { ConfirmEmailForm } from '@/features/settings/components/emails/confirm-email-form'
- import RecaptchaConditions from '@/shared/components/recaptcha-conditions'
- import SsoLinkingInfoGroup from './add-email/sso-linking-info-group'
- import Notification from '@/shared/components/notification'
- import { useFeatureFlag } from '@/shared/context/split-test-context'
- function isDomainCapturedByGroup(
- domainInfo: DomainInfo,
- domainCapturedByGroupRolloutFlagEnabled: boolean
- ): boolean {
- return domainCapturedByGroupRolloutFlagEnabled
- ? (domainInfo.capturedByGroup && domainInfo.group?.domainCaptureEnabled) ||
- false
- : domainInfo.group?.domainCaptureEnabled || false
- }
- function AddEmail() {
- const { t } = useTranslation()
- const [isFormVisible, setIsFormVisible] = useState(
- () => window.location.hash === '#add-email'
- )
- const [newEmail, setNewEmail] = useState('')
- const [confirmationStep, setConfirmationStep] = useState(false)
- const [newEmailMatchedDomain, setNewEmailMatchedDomain] =
- useState<DomainInfo | null>(null)
- const [countryCode, setCountryCode] = useState<CountryCode | null>(null)
- const [universities, setUniversities] = useState<
- Partial<Record<CountryCode, University[]>>
- >({})
- const [universityName, setUniversityName] = useState('')
- const [role, setRole] = useState('')
- const [department, setDepartment] = useState('')
- const { isLoading, isError, error, runAsync } = useAsync()
- const {
- state,
- setLoading: setUserEmailsContextLoading,
- getEmails,
- } = useUserEmailsContext()
- const emailAddressLimit = getMeta('ol-emailAddressLimit') || 10
- const { ref: recaptchaRef, getReCaptchaToken } = useRecaptcha()
- const domainCapturedByGroupRolloutFlagEnabled = useFeatureFlag(
- 'domain-captured-by-group'
- )
- useEffect(() => {
- setUserEmailsContextLoading(isLoading)
- }, [setUserEmailsContextLoading, isLoading])
- const handleShowAddEmailForm = () => {
- setIsFormVisible(true)
- }
- const handleEmailChange = (value: string, domain?: DomainInfo) => {
- setNewEmail(value)
- setNewEmailMatchedDomain(domain || null)
- }
- const getSelectedKnownUniversityId = (): number | undefined => {
- if (countryCode) {
- return universities[countryCode]?.find(
- ({ name }) => name === universityName
- )?.id
- }
- return newEmailMatchedDomain?.university.id
- }
- const handleAddNewEmail = () => {
- const selectedKnownUniversityId = getSelectedKnownUniversityId()
- const knownUniversityData = selectedKnownUniversityId && {
- university: {
- id: selectedKnownUniversityId,
- },
- role,
- department,
- }
- const unknownUniversityData = universityName &&
- !selectedKnownUniversityId && {
- university: {
- name: universityName,
- country_code: countryCode,
- },
- role,
- department,
- }
- runAsync(
- (async () => {
- const token = await getReCaptchaToken()
- await postJSON('/user/emails/secondary', {
- body: {
- email: newEmail,
- ...knownUniversityData,
- ...unknownUniversityData,
- 'g-recaptcha-response': token,
- },
- })
- })()
- )
- .then(() => {
- setConfirmationStep(true)
- })
- .catch(() => {})
- }
- if (confirmationStep) {
- return (
- <ConfirmEmailForm
- confirmationEndpoint="/user/emails/confirm-secondary"
- resendEndpoint="/user/emails/resend-secondary-confirmation"
- flow="secondary"
- email={newEmail}
- onSuccessfulConfirmation={getEmails}
- interstitial={false}
- onCancel={() => {
- setConfirmationStep(false)
- setIsFormVisible(false)
- }}
- />
- )
- }
- if (!isFormVisible) {
- return (
- <Layout isError={isError} error={error}>
- <OLCol lg={12}>
- <Cell>
- {state.data.emailCount >= emailAddressLimit ? (
- <span className="small">
- <Trans
- i18nKey="email_limit_reached"
- values={{
- emailAddressLimit,
- }}
- shouldUnescape
- tOptions={{ interpolation: { escapeValue: true } }}
- components={[<strong />]} // eslint-disable-line react/jsx-key
- />
- </span>
- ) : (
- <AddAnotherEmailBtn onClick={handleShowAddEmailForm} />
- )}
- </Cell>
- </OLCol>
- </Layout>
- )
- }
- const InputComponent = (
- <>
- <label htmlFor="affiliations-email">{t('email')}</label>
- <Input
- onChange={handleEmailChange}
- handleAddNewEmail={handleAddNewEmail}
- />
- </>
- )
- const recaptchaConditions = (
- <OLCol>
- <Cell>
- <div className="affiliations-table-cell-tabbed">
- <RecaptchaConditions />
- </div>
- </Cell>
- </OLCol>
- )
- if (!isValidEmail(newEmail)) {
- return (
- <form>
- <Layout isError={isError} error={error}>
- <ReCaptcha2 page="addEmail" recaptchaRef={recaptchaRef} />
- <OLCol lg={8}>
- <Cell>
- {InputComponent}
- <div className="affiliations-table-cell-tabbed">
- <div>{t('start_by_adding_your_email')}</div>
- </div>
- </Cell>
- </OLCol>
- <OLCol lg={4}>
- <Cell className="text-lg-end">
- <AddNewEmailBtn email={newEmail} disabled />
- </Cell>
- </OLCol>
- {recaptchaConditions}
- </Layout>
- </form>
- )
- }
- const isDomainCaptured = newEmailMatchedDomain
- ? isDomainCapturedByGroup(
- newEmailMatchedDomain,
- domainCapturedByGroupRolloutFlagEnabled
- )
- : false
- const isSsoAvailableForDomain =
- newEmailMatchedDomain &&
- ssoAvailableForDomain(newEmailMatchedDomain, isDomainCaptured)
- return (
- <form>
- <Layout isError={isError} error={error}>
- <ReCaptcha2 page="addEmail" recaptchaRef={recaptchaRef} />
- <OLCol lg={8}>
- <Cell>
- {InputComponent}
- {!isSsoAvailableForDomain ? (
- <div className="affiliations-table-cell-tabbed">
- <InstitutionFields
- countryCode={countryCode}
- setCountryCode={setCountryCode}
- universities={universities}
- setUniversities={setUniversities}
- universityName={universityName}
- setUniversityName={setUniversityName}
- role={role}
- setRole={setRole}
- department={department}
- setDepartment={setDepartment}
- newEmailMatchedDomain={newEmailMatchedDomain}
- />
- </div>
- ) : null}
- </Cell>
- </OLCol>
- {!isSsoAvailableForDomain ? (
- <OLCol lg={4}>
- <Cell className="text-lg-end">
- <AddNewEmailBtn
- email={newEmail}
- disabled={state.isLoading}
- isLoading={isLoading}
- onClick={handleAddNewEmail}
- />
- </Cell>
- </OLCol>
- ) : (
- <OLCol lg={12}>
- <Cell>
- <div className="affiliations-table-cell-tabbed">
- <AddEmailViaSSO
- email={newEmail}
- domainInfo={newEmailMatchedDomain}
- isDomainCaptured={isDomainCaptured}
- userInstitutions={state.data.linkedInstitutionIds}
- />
- </div>
- </Cell>
- </OLCol>
- )}
- {recaptchaConditions}
- </Layout>
- </form>
- )
- }
- function AddEmailViaSSO({
- email,
- domainInfo,
- userInstitutions,
- isDomainCaptured,
- }: {
- email: string
- domainInfo: DomainInfo
- userInstitutions: string[]
- isDomainCaptured: boolean
- }) {
- if (isDomainCaptured && domainInfo.group?.managedUsersEnabled) {
- return (
- <Notification
- type="error"
- ariaLive="polite"
- content={
- <>
- Your company email address has been registered under a verified
- domain, and cannot be added as a secondary email. Please create a
- new <strong>Overleaf</strong> account linked to this email address.
- </>
- }
- />
- )
- } else if (domainInfo.university.ssoEnabled) {
- // Check if the user has already linked this institution
- if (userInstitutions.includes(domainInfo.university.id.toString())) {
- return (
- <Notification
- type="error"
- ariaLive="polite"
- content={
- <>
- This institution is already linked with your account via another
- email address.
- </>
- }
- />
- )
- }
- return <SsoLinkingInfo email={email} domainInfo={domainInfo} />
- } else if (isDomainCaptured && domainInfo.group?.ssoConfig?.enabled) {
- return <SsoLinkingInfoGroup domainInfo={domainInfo} />
- }
- }
- export default AddEmail
|