reconfirmation-info.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import { useState, useEffect, useLayoutEffect } from 'react'
  2. import { UserEmailData } from '../../../../../../types/user-email'
  3. import getMeta from '../../../../utils/meta'
  4. import ReconfirmationInfoSuccess from './reconfirmation-info/reconfirmation-info-success'
  5. import ReconfirmationInfoPromptText from './reconfirmation-info/reconfirmation-info-prompt-text'
  6. import OLRow from '@/features/ui/components/ol/ol-row'
  7. import OLCol from '@/features/ui/components/ol/ol-col'
  8. import OLNotification from '@/features/ui/components/ol/ol-notification'
  9. import { useUserEmailsContext } from '@/features/settings/context/user-email-context'
  10. import { FetchError, postJSON } from '@/infrastructure/fetch-json'
  11. import { debugConsole } from '@/utils/debugging'
  12. import { ssoAvailableForInstitution } from '@/features/settings/utils/sso'
  13. import { Trans, useTranslation } from 'react-i18next'
  14. import useAsync from '@/shared/hooks/use-async'
  15. import { useLocation } from '@/shared/hooks/use-location'
  16. import OLButton from '@/features/ui/components/ol/ol-button'
  17. import LoadingSpinner from '@/shared/components/loading-spinner'
  18. type ReconfirmationInfoProps = {
  19. userEmailData: UserEmailData
  20. }
  21. function ReconfirmationInfo({ userEmailData }: ReconfirmationInfoProps) {
  22. const reconfirmedViaSAML = getMeta('ol-reconfirmedViaSAML')
  23. const { t } = useTranslation()
  24. const { samlInitPath } = getMeta('ol-ExposedSettings')
  25. const { error, isLoading, isError, isSuccess, runAsync } = useAsync()
  26. const { state, setLoading: setUserEmailsContextLoading } =
  27. useUserEmailsContext()
  28. const [hasSent, setHasSent] = useState(false)
  29. const [isPending, setIsPending] = useState(false)
  30. const location = useLocation()
  31. const ssoAvailable = Boolean(
  32. ssoAvailableForInstitution(userEmailData.affiliation?.institution ?? null)
  33. )
  34. const handleRequestReconfirmation = () => {
  35. if (userEmailData.affiliation?.institution && ssoAvailable) {
  36. setIsPending(true)
  37. location.assign(
  38. `${samlInitPath}?university_id=${userEmailData.affiliation.institution.id}&reconfirm=/user/settings`
  39. )
  40. } else {
  41. runAsync(
  42. postJSON('/user/emails/send-reconfirmation', {
  43. body: {
  44. email: userEmailData.email,
  45. },
  46. })
  47. ).catch(debugConsole.error)
  48. }
  49. }
  50. useEffect(() => {
  51. setUserEmailsContextLoading(isLoading)
  52. }, [setUserEmailsContextLoading, isLoading])
  53. useLayoutEffect(() => {
  54. if (isSuccess) {
  55. setHasSent(true)
  56. }
  57. }, [isSuccess])
  58. const rateLimited =
  59. isError && error instanceof FetchError && error.response?.status === 429
  60. if (!userEmailData.affiliation) {
  61. return null
  62. }
  63. if (
  64. userEmailData.samlProviderId &&
  65. userEmailData.samlProviderId === reconfirmedViaSAML
  66. ) {
  67. return (
  68. <OLRow>
  69. <OLCol lg={12}>
  70. <OLNotification
  71. type="info"
  72. content={
  73. <ReconfirmationInfoSuccess
  74. institution={userEmailData.affiliation.institution}
  75. />
  76. }
  77. />
  78. </OLCol>
  79. </OLRow>
  80. )
  81. }
  82. if (userEmailData.affiliation.inReconfirmNotificationPeriod) {
  83. return (
  84. <OLRow>
  85. <OLCol lg={12}>
  86. <OLNotification
  87. type="info"
  88. content={
  89. <>
  90. {hasSent ? (
  91. <Trans
  92. i18nKey="please_check_your_inbox_to_confirm"
  93. values={{
  94. institutionName:
  95. userEmailData.affiliation.institution.name,
  96. }}
  97. shouldUnescape
  98. tOptions={{ interpolation: { escapeValue: true } }}
  99. components={
  100. /* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
  101. [<strong />]
  102. }
  103. />
  104. ) : (
  105. <ReconfirmationInfoPromptText
  106. institutionName={userEmailData.affiliation.institution.name}
  107. primary={userEmailData.default}
  108. />
  109. )}
  110. <br />
  111. {isError && (
  112. <div className="text-danger">
  113. {rateLimited
  114. ? t('too_many_requests')
  115. : t('generic_something_went_wrong')}
  116. </div>
  117. )}
  118. </>
  119. }
  120. action={
  121. hasSent ? (
  122. <>
  123. {isLoading ? (
  124. <>
  125. <LoadingSpinner loadingText={`${t('sending')}…`} />
  126. </>
  127. ) : (
  128. <OLButton
  129. variant="link"
  130. disabled={state.isLoading}
  131. onClick={handleRequestReconfirmation}
  132. className="btn-inline-link"
  133. >
  134. {t('resend_confirmation_email')}
  135. </OLButton>
  136. )}
  137. </>
  138. ) : (
  139. <OLButton
  140. variant="secondary"
  141. disabled={isPending}
  142. isLoading={isLoading}
  143. onClick={handleRequestReconfirmation}
  144. >
  145. {isLoading ? (
  146. <>
  147. <LoadingSpinner loadingText={`${t('sending')}…`} />
  148. </>
  149. ) : (
  150. t('confirm_affiliation')
  151. )}
  152. </OLButton>
  153. )
  154. }
  155. />
  156. </OLCol>
  157. </OLRow>
  158. )
  159. }
  160. return null
  161. }
  162. export default ReconfirmationInfo