account-info-section.tsx 5.6 KB

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