account-info-section.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  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 FormText from '@/features/ui/components/bootstrap-5/form/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>{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. bs3Props={{
  123. loading: isLoading ? `${t('saving')}…` : t('update'),
  124. }}
  125. >
  126. {t('update')}
  127. </OLButton>
  128. </OLFormGroup>
  129. ) : null}
  130. </form>
  131. </>
  132. )
  133. }
  134. type ReadOrWriteFormGroupProps = {
  135. id: string
  136. type: string
  137. label: string
  138. value?: string
  139. handleChange: (event: any) => void
  140. canEdit: boolean
  141. maxLength?: number
  142. required: boolean
  143. }
  144. function ReadOrWriteFormGroup({
  145. id,
  146. type,
  147. label,
  148. value,
  149. handleChange,
  150. canEdit,
  151. maxLength,
  152. required,
  153. }: ReadOrWriteFormGroupProps) {
  154. const [validationMessage, setValidationMessage] = useState('')
  155. const handleInvalid = (event: React.InvalidEvent<HTMLInputElement>) => {
  156. event.preventDefault()
  157. }
  158. const handleChangeAndValidity = (
  159. event: React.ChangeEvent<HTMLInputElement>
  160. ) => {
  161. handleChange(event)
  162. setValidationMessage(event.target.validationMessage)
  163. }
  164. if (!canEdit) {
  165. return (
  166. <OLFormGroup controlId={id}>
  167. <OLFormLabel>{label}</OLFormLabel>
  168. <OLFormControl type="text" readOnly value={value} />
  169. </OLFormGroup>
  170. )
  171. }
  172. return (
  173. <OLFormGroup controlId={id}>
  174. <OLFormLabel>{label}</OLFormLabel>
  175. <OLFormControl
  176. type={type}
  177. required={required}
  178. value={value}
  179. maxLength={maxLength}
  180. data-ol-dirty={!!validationMessage}
  181. onChange={handleChangeAndValidity}
  182. onInvalid={handleInvalid}
  183. />
  184. {validationMessage && (
  185. <FormText type="error">{validationMessage}</FormText>
  186. )}
  187. </OLFormGroup>
  188. )
  189. }
  190. export default AccountInfoSection