accountDetails.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import {Fragment} from 'react';
  2. import {updateUser} from 'sentry/actionCreators/account';
  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 LoadingError from 'sentry/components/loadingError';
  7. import LoadingIndicator from 'sentry/components/loadingIndicator';
  8. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  9. import accountDetailsFields from 'sentry/data/forms/accountDetails';
  10. import accountPreferencesFields from 'sentry/data/forms/accountPreferences';
  11. import {t} from 'sentry/locale';
  12. import type {User} from 'sentry/types';
  13. import {
  14. ApiQueryKey,
  15. setApiQueryData,
  16. useApiQuery,
  17. useQueryClient,
  18. } from 'sentry/utils/queryClient';
  19. import useOrganization from 'sentry/utils/useOrganization';
  20. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  21. // The avatar endpoint ("/users/me/avatar/") returns a User-like type without `options` and other properties that are present in User
  22. export type ChangeAvatarUser = Omit<
  23. User,
  24. 'canReset2fa' | 'flags' | 'identities' | 'isAuthenticated' | 'options' | 'permissions'
  25. > &
  26. Partial<
  27. Pick<
  28. User,
  29. | 'canReset2fa'
  30. | 'flags'
  31. | 'identities'
  32. | 'isAuthenticated'
  33. | 'options'
  34. | 'permissions'
  35. >
  36. >;
  37. const USER_ENDPOINT = '/users/me/';
  38. const USER_ENDPOINT_QUERY_KEY: ApiQueryKey = [USER_ENDPOINT];
  39. function AccountDetails() {
  40. const organization = useOrganization({allowNull: true});
  41. const queryClient = useQueryClient();
  42. const {
  43. data: user,
  44. isLoading,
  45. isError,
  46. refetch,
  47. } = useApiQuery<User>(USER_ENDPOINT_QUERY_KEY, {staleTime: 0});
  48. if (isLoading) {
  49. return (
  50. <Fragment>
  51. <SettingsPageHeader title={t('Account Details')} />
  52. <LoadingIndicator />
  53. </Fragment>
  54. );
  55. }
  56. if (isError) {
  57. return <LoadingError onRetry={refetch} />;
  58. }
  59. const handleSubmitSuccess = (userData: User | ChangeAvatarUser) => {
  60. // the updateUser method updates our Config Store
  61. // No components listen to the ConfigStore, they just access it directly
  62. updateUser(userData);
  63. // We need to update the state, because AvatarChooser is using it,
  64. // otherwise it will flick
  65. setApiQueryData(queryClient, USER_ENDPOINT_QUERY_KEY, userData);
  66. };
  67. const formCommonProps: Partial<FormProps> = {
  68. apiEndpoint: USER_ENDPOINT,
  69. apiMethod: 'PUT',
  70. allowUndo: true,
  71. saveOnBlur: true,
  72. onSubmitSuccess: handleSubmitSuccess,
  73. };
  74. return (
  75. <Fragment>
  76. <SentryDocumentTitle title={t('Account Details')} />
  77. <SettingsPageHeader title={t('Account Details')} />
  78. <Form initialData={user} {...formCommonProps}>
  79. <JsonForm forms={accountDetailsFields} additionalFieldProps={{user}} />
  80. </Form>
  81. <Form initialData={user.options} {...formCommonProps}>
  82. <JsonForm
  83. forms={accountPreferencesFields}
  84. additionalFieldProps={{
  85. user,
  86. organization,
  87. }}
  88. />
  89. </Form>
  90. <AvatarChooser
  91. endpoint="/users/me/avatar/"
  92. model={user}
  93. onSave={resp => {
  94. handleSubmitSuccess(resp as ChangeAvatarUser);
  95. }}
  96. isUser
  97. />
  98. </Fragment>
  99. );
  100. }
  101. export default AccountDetails;