accountDetails.tsx 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import {updateUser} from 'sentry/actionCreators/account';
  2. import {APIRequestMethod} from 'sentry/api';
  3. import AvatarChooser from 'sentry/components/avatarChooser';
  4. import Form, {FormProps} from 'sentry/components/forms/form';
  5. import JsonForm from 'sentry/components/forms/jsonForm';
  6. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  7. import accountDetailsFields from 'sentry/data/forms/accountDetails';
  8. import accountPreferencesFields from 'sentry/data/forms/accountPreferences';
  9. import {t} from 'sentry/locale';
  10. import {Organization, User} from 'sentry/types';
  11. import withOrganization from 'sentry/utils/withOrganization';
  12. import DeprecatedAsyncView, {AsyncViewProps} from 'sentry/views/deprecatedAsyncView';
  13. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  14. // The avatar endpoint ("/users/me/avatar/") returns a User-like type without `options` and other properties that are present in User
  15. export type ChangeAvatarUser = Omit<
  16. User,
  17. 'canReset2fa' | 'flags' | 'identities' | 'isAuthenticated' | 'options' | 'permissions'
  18. > &
  19. Partial<
  20. Pick<
  21. User,
  22. | 'canReset2fa'
  23. | 'flags'
  24. | 'identities'
  25. | 'isAuthenticated'
  26. | 'options'
  27. | 'permissions'
  28. >
  29. >;
  30. const ENDPOINT = '/users/me/';
  31. interface Props extends AsyncViewProps {
  32. organization: Organization;
  33. }
  34. class AccountDetails extends DeprecatedAsyncView<Props> {
  35. getEndpoints(): ReturnType<DeprecatedAsyncView['getEndpoints']> {
  36. // local state is NOT updated when the form saves
  37. return [['user', ENDPOINT]];
  38. }
  39. handleSubmitSuccess = (user: User | ChangeAvatarUser) => {
  40. // the updateUser method updates our Config Store
  41. // No components listen to the ConfigStore, they just access it directly
  42. updateUser(user);
  43. // We need to update the state, because AvatarChooser is using it,
  44. // otherwise it will flick
  45. this.setState({
  46. user,
  47. });
  48. };
  49. renderBody() {
  50. const user = this.state.user as User;
  51. const formCommonProps: Partial<FormProps> = {
  52. apiEndpoint: ENDPOINT,
  53. apiMethod: 'PUT' as APIRequestMethod,
  54. allowUndo: true,
  55. saveOnBlur: true,
  56. onSubmitSuccess: this.handleSubmitSuccess,
  57. };
  58. return (
  59. <div>
  60. <SentryDocumentTitle title={t('Account Details')} />
  61. <SettingsPageHeader title={t('Account Details')} />
  62. <Form initialData={user} {...formCommonProps}>
  63. <JsonForm forms={accountDetailsFields} additionalFieldProps={{user}} />
  64. </Form>
  65. <Form initialData={user.options} {...formCommonProps}>
  66. <JsonForm
  67. forms={accountPreferencesFields}
  68. additionalFieldProps={{
  69. user,
  70. organization: this.props.organization,
  71. }}
  72. />
  73. </Form>
  74. <AvatarChooser
  75. endpoint="/users/me/avatar/"
  76. model={user}
  77. onSave={resp => {
  78. this.handleSubmitSuccess(resp as ChangeAvatarUser);
  79. }}
  80. isUser
  81. />
  82. </div>
  83. );
  84. }
  85. }
  86. export default withOrganization(AccountDetails);