add-email.tsx 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import { useState, useEffect } from 'react'
  2. import { useTranslation, Trans } from 'react-i18next'
  3. import Cell from './cell'
  4. import Layout from './add-email/layout'
  5. import Input, { DomainInfo } from './add-email/input'
  6. import AddAnotherEmailBtn from './add-email/add-another-email-btn'
  7. import InstitutionFields from './add-email/institution-fields'
  8. import SsoLinkingInfo from './add-email/sso-linking-info'
  9. import AddNewEmailBtn from './add-email/add-new-email-btn'
  10. import useAsync from '../../../../shared/hooks/use-async'
  11. import { useUserEmailsContext } from '../../context/user-email-context'
  12. import { ssoAvailableForDomain } from '../../utils/sso'
  13. import { postJSON } from '../../../../infrastructure/fetch-json'
  14. import { University } from '../../../../../../types/university'
  15. import { CountryCode } from '../../data/countries-list'
  16. import { isValidEmail } from '../../../../shared/utils/email'
  17. import getMeta from '../../../../utils/meta'
  18. import { ReCaptcha2 } from '../../../../shared/components/recaptcha-2'
  19. import { useRecaptcha } from '../../../../shared/hooks/use-recaptcha'
  20. import OLCol from '@/features/ui/components/ol/ol-col'
  21. import { bsVersion } from '@/features/utils/bootstrap-5'
  22. import { ConfirmEmailForm } from '@/features/settings/components/emails/confirm-email-form'
  23. function AddEmail() {
  24. const { t } = useTranslation()
  25. const [isFormVisible, setIsFormVisible] = useState(
  26. () => window.location.hash === '#add-email'
  27. )
  28. const [newEmail, setNewEmail] = useState('')
  29. const [confirmationStep, setConfirmationStep] = useState(false)
  30. const [newEmailMatchedDomain, setNewEmailMatchedDomain] =
  31. useState<DomainInfo | null>(null)
  32. const [countryCode, setCountryCode] = useState<CountryCode | null>(null)
  33. const [universities, setUniversities] = useState<
  34. Partial<Record<CountryCode, University[]>>
  35. >({})
  36. const [universityName, setUniversityName] = useState('')
  37. const [role, setRole] = useState('')
  38. const [department, setDepartment] = useState('')
  39. const { isLoading, isError, error, runAsync } = useAsync()
  40. const {
  41. state,
  42. setLoading: setUserEmailsContextLoading,
  43. getEmails,
  44. } = useUserEmailsContext()
  45. const emailAddressLimit = getMeta('ol-emailAddressLimit') || 10
  46. const { ref: recaptchaRef, getReCaptchaToken } = useRecaptcha()
  47. useEffect(() => {
  48. setUserEmailsContextLoading(isLoading)
  49. }, [setUserEmailsContextLoading, isLoading])
  50. const handleShowAddEmailForm = () => {
  51. setIsFormVisible(true)
  52. }
  53. const handleEmailChange = (value: string, domain?: DomainInfo) => {
  54. setNewEmail(value)
  55. setNewEmailMatchedDomain(domain || null)
  56. }
  57. const getSelectedKnownUniversityId = (): number | undefined => {
  58. if (countryCode) {
  59. return universities[countryCode]?.find(
  60. ({ name }) => name === universityName
  61. )?.id
  62. }
  63. return newEmailMatchedDomain?.university.id
  64. }
  65. const handleAddNewEmail = () => {
  66. const selectedKnownUniversityId = getSelectedKnownUniversityId()
  67. const knownUniversityData = selectedKnownUniversityId && {
  68. university: {
  69. id: selectedKnownUniversityId,
  70. },
  71. role,
  72. department,
  73. }
  74. const unknownUniversityData = universityName &&
  75. !selectedKnownUniversityId && {
  76. university: {
  77. name: universityName,
  78. country_code: countryCode,
  79. },
  80. role,
  81. department,
  82. }
  83. runAsync(
  84. (async () => {
  85. const token = await getReCaptchaToken()
  86. await postJSON('/user/emails/secondary', {
  87. body: {
  88. email: newEmail,
  89. ...knownUniversityData,
  90. ...unknownUniversityData,
  91. 'g-recaptcha-response': token,
  92. },
  93. })
  94. })()
  95. )
  96. .then(() => {
  97. setConfirmationStep(true)
  98. })
  99. .catch(() => {})
  100. }
  101. if (confirmationStep) {
  102. return (
  103. <ConfirmEmailForm
  104. confirmationEndpoint="/user/emails/confirm-secondary"
  105. resendEndpoint="/user/emails/resend-secondary-confirmation"
  106. flow="secondary"
  107. email={newEmail}
  108. onSuccessfulConfirmation={getEmails}
  109. interstitial={false}
  110. onCancel={() => {
  111. setConfirmationStep(false)
  112. setIsFormVisible(false)
  113. }}
  114. />
  115. )
  116. }
  117. if (!isFormVisible) {
  118. return (
  119. <Layout isError={isError} error={error}>
  120. <OLCol lg={12}>
  121. <Cell>
  122. {state.data.emailCount >= emailAddressLimit ? (
  123. <span className="small">
  124. <Trans
  125. i18nKey="email_limit_reached"
  126. values={{
  127. emailAddressLimit,
  128. }}
  129. shouldUnescape
  130. tOptions={{ interpolation: { escapeValue: true } }}
  131. components={[<strong />]} // eslint-disable-line react/jsx-key
  132. />
  133. </span>
  134. ) : (
  135. <AddAnotherEmailBtn onClick={handleShowAddEmailForm} />
  136. )}
  137. </Cell>
  138. </OLCol>
  139. </Layout>
  140. )
  141. }
  142. const InputComponent = (
  143. <>
  144. <label
  145. htmlFor="affiliations-email"
  146. className={bsVersion({ bs5: 'visually-hidden', bs3: 'sr-only' })}
  147. >
  148. {t('email')}
  149. </label>
  150. <Input
  151. onChange={handleEmailChange}
  152. handleAddNewEmail={handleAddNewEmail}
  153. />
  154. </>
  155. )
  156. if (!isValidEmail(newEmail)) {
  157. return (
  158. <form>
  159. <Layout isError={isError} error={error}>
  160. <ReCaptcha2 page="addEmail" recaptchaRef={recaptchaRef} />
  161. <OLCol lg={8}>
  162. <Cell>
  163. {InputComponent}
  164. <div className="affiliations-table-cell-tabbed">
  165. <div>{t('start_by_adding_your_email')}</div>
  166. </div>
  167. </Cell>
  168. </OLCol>
  169. <OLCol lg={4}>
  170. <Cell className="text-lg-end">
  171. <AddNewEmailBtn email={newEmail} disabled />
  172. </Cell>
  173. </OLCol>
  174. </Layout>
  175. </form>
  176. )
  177. }
  178. const isSsoAvailableForDomain =
  179. newEmailMatchedDomain && ssoAvailableForDomain(newEmailMatchedDomain)
  180. return (
  181. <form>
  182. <Layout isError={isError} error={error}>
  183. <ReCaptcha2 page="addEmail" recaptchaRef={recaptchaRef} />
  184. <OLCol lg={8}>
  185. <Cell>
  186. {InputComponent}
  187. {!isSsoAvailableForDomain ? (
  188. <div className="affiliations-table-cell-tabbed">
  189. <InstitutionFields
  190. countryCode={countryCode}
  191. setCountryCode={setCountryCode}
  192. universities={universities}
  193. setUniversities={setUniversities}
  194. universityName={universityName}
  195. setUniversityName={setUniversityName}
  196. role={role}
  197. setRole={setRole}
  198. department={department}
  199. setDepartment={setDepartment}
  200. newEmailMatchedDomain={newEmailMatchedDomain}
  201. />
  202. </div>
  203. ) : null}
  204. </Cell>
  205. </OLCol>
  206. {!isSsoAvailableForDomain ? (
  207. <OLCol lg={4}>
  208. <Cell className="text-lg-end">
  209. <AddNewEmailBtn
  210. email={newEmail}
  211. disabled={state.isLoading}
  212. isLoading={isLoading}
  213. onClick={handleAddNewEmail}
  214. />
  215. </Cell>
  216. </OLCol>
  217. ) : (
  218. <OLCol lg={12}>
  219. <Cell>
  220. <div className="affiliations-table-cell-tabbed">
  221. <SsoLinkingInfo
  222. email={newEmail}
  223. domainInfo={newEmailMatchedDomain as DomainInfo}
  224. />
  225. </div>
  226. </Cell>
  227. </OLCol>
  228. )}
  229. </Layout>
  230. </form>
  231. )
  232. }
  233. export default AddEmail