accountDetails.tsx 3.8 KB

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