add-email.tsx 7.2 KB

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