account-info-section.tsx 5.2 KB

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