row.tsx 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. import { useState } from 'react'
  2. import { Trans, useTranslation } from 'react-i18next'
  3. import { UserEmailData } from '../../../../../../types/user-email'
  4. import Email from './email'
  5. import InstitutionAndRole from './institution-and-role'
  6. import EmailCell from './cell'
  7. import Actions from './actions'
  8. import { institutionAlreadyLinked } from '../../utils/selectors'
  9. import { useUserEmailsContext } from '../../context/user-email-context'
  10. import getMeta from '../../../../utils/meta'
  11. import { useFeatureFlag } from '@/shared/context/split-test-context'
  12. import { ssoAvailableForInstitution } from '../../utils/sso'
  13. import ReconfirmationInfo from './reconfirmation-info'
  14. import { useLocation } from '../../../../shared/hooks/use-location'
  15. import OLRow from '@/shared/components/ol/ol-row'
  16. import OLCol from '@/shared/components/ol/ol-col'
  17. import OLButton from '@/shared/components/ol/ol-button'
  18. import UnlinkCommonsSSOModal from './unlink-commons-sso-modal'
  19. type EmailsRowProps = {
  20. userEmailData: UserEmailData
  21. primary?: UserEmailData
  22. }
  23. function EmailsRow({ userEmailData, primary }: EmailsRowProps) {
  24. const hasSSOAffiliation = Boolean(
  25. userEmailData.affiliation &&
  26. ssoAvailableForInstitution(userEmailData.affiliation.institution)
  27. )
  28. return (
  29. <>
  30. <OLRow data-testid="email-row">
  31. <OLCol lg={4}>
  32. <EmailCell>
  33. <Email userEmailData={userEmailData} />
  34. </EmailCell>
  35. </OLCol>
  36. <OLCol lg={5}>
  37. {userEmailData.affiliation?.institution && (
  38. <EmailCell>
  39. <InstitutionAndRole userEmailData={userEmailData} />
  40. </EmailCell>
  41. )}
  42. </OLCol>
  43. <OLCol lg={3}>
  44. <EmailCell className="text-lg-end">
  45. <Actions userEmailData={userEmailData} primary={primary} />
  46. </EmailCell>
  47. </OLCol>
  48. </OLRow>
  49. {hasSSOAffiliation && (
  50. <SSOAffiliationInfo userEmailData={userEmailData} />
  51. )}
  52. <ReconfirmationInfo userEmailData={userEmailData} />
  53. </>
  54. )
  55. }
  56. type SSOAffiliationInfoProps = {
  57. userEmailData: UserEmailData
  58. }
  59. function SSOAffiliationInfo({ userEmailData }: SSOAffiliationInfoProps) {
  60. const { samlInitPath } = getMeta('ol-ExposedSettings')
  61. const { t } = useTranslation()
  62. const { state } = useUserEmailsContext()
  63. const location = useLocation()
  64. const [showUnlinkSSOModal, setShowUnlinkSSOModal] = useState(false)
  65. const [linkAccountsButtonDisabled, setLinkAccountsButtonDisabled] =
  66. useState(false)
  67. const domainCapturedByGroupRolloutFlagEnabled = useFeatureFlag(
  68. 'domain-captured-by-group'
  69. )
  70. function handleLinkAccountsButtonClick() {
  71. setLinkAccountsButtonDisabled(true)
  72. location.assign(
  73. `${samlInitPath}?university_id=${userEmailData.affiliation?.institution?.id}&auto=/user/settings&email=${userEmailData.email}`
  74. )
  75. }
  76. if (
  77. !userEmailData.samlProviderId &&
  78. institutionAlreadyLinked(state, userEmailData)
  79. ) {
  80. // if the email is not linked to the institution, but there's another email already linked to that institution
  81. // no SSO affiliation is displayed, since cannot have multiple emails linked to the same institution
  82. return null
  83. }
  84. if (userEmailData.samlProviderId) {
  85. return (
  86. <OLRow>
  87. <OLCol lg={{ span: 8, offset: 4 }}>
  88. <div className="horizontal-divider" />
  89. <OLRow>
  90. <OLCol lg={9}>
  91. <EmailCell>
  92. <p>
  93. <Trans
  94. i18nKey="acct_linked_to_institution_acct_2"
  95. components={
  96. /* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
  97. [<strong />]
  98. }
  99. values={{
  100. institutionName:
  101. userEmailData.affiliation?.institution.name,
  102. }}
  103. shouldUnescape
  104. tOptions={{ interpolation: { escapeValue: true } }}
  105. />
  106. </p>
  107. </EmailCell>
  108. </OLCol>
  109. <OLCol lg={3} className="text-lg-end">
  110. <EmailCell>
  111. <OLButton
  112. variant="secondary"
  113. size="sm"
  114. onClick={() => setShowUnlinkSSOModal(true)}
  115. >
  116. {t('unlink_sso')}
  117. </OLButton>
  118. <UnlinkCommonsSSOModal
  119. show={showUnlinkSSOModal}
  120. onClose={() => setShowUnlinkSSOModal(false)}
  121. institutionName={
  122. userEmailData.affiliation?.institution.name || ''
  123. }
  124. institutionEmail={userEmailData.email}
  125. hasLicence={userEmailData.emailHasInstitutionLicence || false}
  126. />
  127. </EmailCell>
  128. </OLCol>
  129. </OLRow>
  130. </OLCol>
  131. </OLRow>
  132. )
  133. }
  134. const domainAlsoForGroupWithDomainCapture =
  135. domainCapturedByGroupRolloutFlagEnabled
  136. ? userEmailData?.affiliation?.domainCapturedByGroup &&
  137. userEmailData?.affiliation?.group?.domainCaptureEnabled
  138. : userEmailData?.affiliation?.group?.domainCaptureEnabled
  139. if (domainAlsoForGroupWithDomainCapture) {
  140. // user is not linked via Commons and should link via groups
  141. // do not show UI to link to Commons
  142. return null
  143. }
  144. return (
  145. <OLRow>
  146. <OLCol lg={{ span: 8, offset: 4 }}>
  147. <div className="horizontal-divider" />
  148. <OLRow>
  149. <OLCol lg={9}>
  150. <EmailCell>
  151. <p className="small">
  152. <Trans
  153. i18nKey="can_link_your_institution_acct_2"
  154. values={{
  155. institutionName:
  156. userEmailData.affiliation?.institution.name,
  157. }}
  158. shouldUnescape
  159. tOptions={{ interpolation: { escapeValue: true } }}
  160. components={
  161. /* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
  162. [<strong />]
  163. }
  164. />
  165. </p>
  166. <p className="small">
  167. <Trans
  168. i18nKey="doing_this_allow_log_in_through_institution_2"
  169. components={
  170. /* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
  171. [<strong />]
  172. }
  173. />{' '}
  174. <a href="/learn/how-to/Institutional_Login" target="_blank">
  175. {t('find_out_more_about_institution_login')}
  176. </a>
  177. </p>
  178. </EmailCell>
  179. </OLCol>
  180. <OLCol lg={3} className="text-lg-end">
  181. <EmailCell>
  182. <OLButton
  183. variant="primary"
  184. className="btn-link-accounts"
  185. disabled={linkAccountsButtonDisabled}
  186. onClick={handleLinkAccountsButtonClick}
  187. size="sm"
  188. >
  189. {t('link_accounts')}
  190. </OLButton>
  191. </EmailCell>
  192. </OLCol>
  193. </OLRow>
  194. </OLCol>
  195. </OLRow>
  196. )
  197. }
  198. export default EmailsRow