Files
overleaf-cep/services/web/frontend/js/features/settings/components/emails/row.tsx
Jessica Lawshe ba13ccdb11 Merge pull request #33202 from overleaf/jel-domain-captured-by-group-settings-page
[Domain capture] Check `domainCapturedByGroup` for existing emails on user settings

GitOrigin-RevId: 5ac86b89969b186cce0cac410c2957e5aa1b9703
2026-05-12 08:06:33 +00:00

215 lines
7.1 KiB
TypeScript

import { useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { UserEmailData } from '../../../../../../types/user-email'
import Email from './email'
import InstitutionAndRole from './institution-and-role'
import EmailCell from './cell'
import Actions from './actions'
import { institutionAlreadyLinked } from '../../utils/selectors'
import { useUserEmailsContext } from '../../context/user-email-context'
import getMeta from '../../../../utils/meta'
import { useFeatureFlag } from '@/shared/context/split-test-context'
import { ssoAvailableForInstitution } from '../../utils/sso'
import ReconfirmationInfo from './reconfirmation-info'
import { useLocation } from '../../../../shared/hooks/use-location'
import OLRow from '@/shared/components/ol/ol-row'
import OLCol from '@/shared/components/ol/ol-col'
import OLButton from '@/shared/components/ol/ol-button'
import UnlinkCommonsSSOModal from './unlink-commons-sso-modal'
type EmailsRowProps = {
userEmailData: UserEmailData
primary?: UserEmailData
}
function EmailsRow({ userEmailData, primary }: EmailsRowProps) {
const hasSSOAffiliation = Boolean(
userEmailData.affiliation &&
ssoAvailableForInstitution(userEmailData.affiliation.institution)
)
return (
<>
<OLRow data-testid="email-row">
<OLCol lg={4}>
<EmailCell>
<Email userEmailData={userEmailData} />
</EmailCell>
</OLCol>
<OLCol lg={5}>
{userEmailData.affiliation?.institution && (
<EmailCell>
<InstitutionAndRole userEmailData={userEmailData} />
</EmailCell>
)}
</OLCol>
<OLCol lg={3}>
<EmailCell className="text-lg-end">
<Actions userEmailData={userEmailData} primary={primary} />
</EmailCell>
</OLCol>
</OLRow>
{hasSSOAffiliation && (
<SSOAffiliationInfo userEmailData={userEmailData} />
)}
<ReconfirmationInfo userEmailData={userEmailData} />
</>
)
}
type SSOAffiliationInfoProps = {
userEmailData: UserEmailData
}
function SSOAffiliationInfo({ userEmailData }: SSOAffiliationInfoProps) {
const { samlInitPath } = getMeta('ol-ExposedSettings')
const { t } = useTranslation()
const { state } = useUserEmailsContext()
const location = useLocation()
const [showUnlinkSSOModal, setShowUnlinkSSOModal] = useState(false)
const [linkAccountsButtonDisabled, setLinkAccountsButtonDisabled] =
useState(false)
const domainCapturedByGroupRolloutFlagEnabled = useFeatureFlag(
'domain-captured-by-group'
)
function handleLinkAccountsButtonClick() {
setLinkAccountsButtonDisabled(true)
location.assign(
`${samlInitPath}?university_id=${userEmailData.affiliation?.institution?.id}&auto=/user/settings&email=${userEmailData.email}`
)
}
if (
!userEmailData.samlProviderId &&
institutionAlreadyLinked(state, userEmailData)
) {
// if the email is not linked to the institution, but there's another email already linked to that institution
// no SSO affiliation is displayed, since cannot have multiple emails linked to the same institution
return null
}
if (userEmailData.samlProviderId) {
return (
<OLRow>
<OLCol lg={{ span: 8, offset: 4 }}>
<div className="horizontal-divider" />
<OLRow>
<OLCol lg={9}>
<EmailCell>
<p>
<Trans
i18nKey="acct_linked_to_institution_acct_2"
components={
/* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
[<strong />]
}
values={{
institutionName:
userEmailData.affiliation?.institution.name,
}}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>
</p>
</EmailCell>
</OLCol>
<OLCol lg={3} className="text-lg-end">
<EmailCell>
<OLButton
variant="secondary"
size="sm"
onClick={() => setShowUnlinkSSOModal(true)}
>
{t('unlink_sso')}
</OLButton>
<UnlinkCommonsSSOModal
show={showUnlinkSSOModal}
onClose={() => setShowUnlinkSSOModal(false)}
institutionName={
userEmailData.affiliation?.institution.name || ''
}
institutionEmail={userEmailData.email}
hasLicence={userEmailData.emailHasInstitutionLicence || false}
/>
</EmailCell>
</OLCol>
</OLRow>
</OLCol>
</OLRow>
)
}
const domainAlsoForGroupWithDomainCapture =
domainCapturedByGroupRolloutFlagEnabled
? userEmailData?.affiliation?.domainCapturedByGroup &&
userEmailData?.affiliation?.group?.domainCaptureEnabled
: userEmailData?.affiliation?.group?.domainCaptureEnabled
if (domainAlsoForGroupWithDomainCapture) {
// user is not linked via Commons and should link via groups
// do not show UI to link to Commons
return null
}
return (
<OLRow>
<OLCol lg={{ span: 8, offset: 4 }}>
<div className="horizontal-divider" />
<OLRow>
<OLCol lg={9}>
<EmailCell>
<p className="small">
<Trans
i18nKey="can_link_your_institution_acct_2"
values={{
institutionName:
userEmailData.affiliation?.institution.name,
}}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
components={
/* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
[<strong />]
}
/>
</p>
<p className="small">
<Trans
i18nKey="doing_this_allow_log_in_through_institution_2"
components={
/* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
[<strong />]
}
/>{' '}
<a href="/learn/how-to/Institutional_Login" target="_blank">
{t('find_out_more_about_institution_login')}
</a>
</p>
</EmailCell>
</OLCol>
<OLCol lg={3} className="text-lg-end">
<EmailCell>
<OLButton
variant="primary"
className="btn-link-accounts"
disabled={linkAccountsButtonDisabled}
onClick={handleLinkAccountsButtonClick}
size="sm"
>
{t('link_accounts')}
</OLButton>
</EmailCell>
</OLCol>
</OLRow>
</OLCol>
</OLRow>
)
}
export default EmailsRow