account-info-section.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. import { useState } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import {
  4. getUserFacingMessage,
  5. postJSON,
  6. } from '../../../infrastructure/fetch-json'
  7. import getMeta from '../../../utils/meta'
  8. import useAsync from '../../../shared/hooks/use-async'
  9. import { useUserContext } from '../../../shared/context/user-context'
  10. import OLButton from '@/features/ui/components/ol/ol-button'
  11. import OLNotification from '@/features/ui/components/ol/ol-notification'
  12. import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
  13. import OLFormLabel from '@/features/ui/components/ol/ol-form-label'
  14. import OLFormControl from '@/features/ui/components/ol/ol-form-control'
  15. import OLFormText from '@/features/ui/components/ol/ol-form-text'
  16. function AccountInfoSection() {
  17. const { t } = useTranslation()
  18. const { hasAffiliationsFeature } = getMeta('ol-ExposedSettings')
  19. const isExternalAuthenticationSystemUsed = getMeta(
  20. 'ol-isExternalAuthenticationSystemUsed'
  21. )
  22. const shouldAllowEditingDetails = getMeta('ol-shouldAllowEditingDetails')
  23. const {
  24. first_name: initialFirstName,
  25. last_name: initialLastName,
  26. email: initialEmail,
  27. } = useUserContext()
  28. const [email, setEmail] = useState(initialEmail)
  29. const [firstName, setFirstName] = useState(initialFirstName)
  30. const [lastName, setLastName] = useState(initialLastName)
  31. const { isLoading, isSuccess, isError, error, runAsync } = useAsync()
  32. const [isFormValid, setIsFormValid] = useState(true)
  33. const handleEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  34. setEmail(event.target.value)
  35. setIsFormValid(event.target.validity.valid)
  36. }
  37. const handleFirstNameChange = (
  38. event: React.ChangeEvent<HTMLInputElement>
  39. ) => {
  40. setFirstName(event.target.value)
  41. }
  42. const handleLastNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  43. setLastName(event.target.value)
  44. }
  45. const canUpdateEmail =
  46. !hasAffiliationsFeature && !isExternalAuthenticationSystemUsed
  47. const canUpdateNames = shouldAllowEditingDetails
  48. const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
  49. event.preventDefault()
  50. if (!isFormValid) {
  51. return
  52. }
  53. runAsync(
  54. postJSON('/user/settings', {
  55. body: {
  56. email: canUpdateEmail ? email : undefined,
  57. first_name: canUpdateNames ? firstName : undefined,
  58. last_name: canUpdateNames ? lastName : undefined,
  59. },
  60. })
  61. ).catch(() => {})
  62. }
  63. return (
  64. <>
  65. <h3 id="update-account-info">{t('update_account_info')}</h3>
  66. <form id="account-info-form" onSubmit={handleSubmit}>
  67. {hasAffiliationsFeature ? null : (
  68. <ReadOrWriteFormGroup
  69. id="email-input"
  70. type="email"
  71. label={t('email')}
  72. value={email}
  73. handleChange={handleEmailChange}
  74. canEdit={canUpdateEmail}
  75. required
  76. />
  77. )}
  78. <ReadOrWriteFormGroup
  79. id="first-name-input"
  80. type="text"
  81. label={t('first_name')}
  82. value={firstName}
  83. maxLength={255}
  84. handleChange={handleFirstNameChange}
  85. canEdit={canUpdateNames}
  86. required={false}
  87. />
  88. <ReadOrWriteFormGroup
  89. id="last-name-input"
  90. type="text"
  91. label={t('last_name')}
  92. maxLength={255}
  93. value={lastName}
  94. handleChange={handleLastNameChange}
  95. canEdit={canUpdateNames}
  96. required={false}
  97. />
  98. {isSuccess ? (
  99. <OLFormGroup>
  100. <OLNotification
  101. type="success"
  102. content={t('thanks_settings_updated')}
  103. />
  104. </OLFormGroup>
  105. ) : null}
  106. {isError ? (
  107. <OLFormGroup>
  108. <OLNotification
  109. type="error"
  110. content={getUserFacingMessage(error) ?? ''}
  111. />
  112. </OLFormGroup>
  113. ) : null}
  114. {canUpdateEmail || canUpdateNames ? (
  115. <OLFormGroup>
  116. <OLButton
  117. type="submit"
  118. variant="primary"
  119. form="account-info-form"
  120. disabled={!isFormValid}
  121. isLoading={isLoading}
  122. loadingLabel={t('saving') + '…'}
  123. aria-labelledby={isLoading ? undefined : 'update-account-info'}
  124. >
  125. {t('update')}
  126. </OLButton>
  127. </OLFormGroup>
  128. ) : null}
  129. </form>
  130. </>
  131. )
  132. }
  133. type ReadOrWriteFormGroupProps = {
  134. id: string
  135. type: string
  136. label: string
  137. value?: string
  138. handleChange: (event: any) => void
  139. canEdit: boolean
  140. maxLength?: number
  141. required: boolean
  142. }
  143. function ReadOrWriteFormGroup({
  144. id,
  145. type,
  146. label,
  147. value,
  148. handleChange,
  149. canEdit,
  150. maxLength,
  151. required,
  152. }: ReadOrWriteFormGroupProps) {
  153. const [validationMessage, setValidationMessage] = useState('')
  154. const handleInvalid = (event: React.InvalidEvent<HTMLInputElement>) => {
  155. event.preventDefault()
  156. }
  157. const handleChangeAndValidity = (
  158. event: React.ChangeEvent<HTMLInputElement>
  159. ) => {
  160. handleChange(event)
  161. setValidationMessage(event.target.validationMessage)
  162. }
  163. if (!canEdit) {
  164. return (
  165. <OLFormGroup controlId={id}>
  166. <OLFormLabel>{label}</OLFormLabel>
  167. <OLFormControl type="text" readOnly value={value} />
  168. </OLFormGroup>
  169. )
  170. }
  171. return (
  172. <OLFormGroup controlId={id}>
  173. <OLFormLabel>{label}</OLFormLabel>
  174. <OLFormControl
  175. type={type}
  176. required={required}
  177. value={value}
  178. maxLength={maxLength}
  179. data-ol-dirty={!!validationMessage}
  180. onChange={handleChangeAndValidity}
  181. onInvalid={handleInvalid}
  182. />
  183. {validationMessage && (
  184. <OLFormText type="error">{validationMessage}</OLFormText>
  185. )}
  186. </OLFormGroup>
  187. )
  188. }
  189. export default AccountInfoSection