@oxy.so/services 12.10.1 → 12.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/lib/commonjs/ui/components/accountSettings/AccountCategoriesSection.js +142 -0
  2. package/lib/commonjs/ui/components/accountSettings/AccountCategoriesSection.js.map +1 -0
  3. package/lib/commonjs/ui/components/accountSettings/AccountSettings.js +361 -0
  4. package/lib/commonjs/ui/components/accountSettings/AccountSettings.js.map +1 -0
  5. package/lib/commonjs/ui/hooks/useAvatarPicker.js +54 -48
  6. package/lib/commonjs/ui/hooks/useAvatarPicker.js.map +1 -1
  7. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +16 -237
  8. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  9. package/lib/commonjs/ui/screens/ChangeAvatarScreen.js +23 -4
  10. package/lib/commonjs/ui/screens/ChangeAvatarScreen.js.map +1 -1
  11. package/lib/commonjs/ui/screens/index.js +7 -0
  12. package/lib/commonjs/ui/screens/index.js.map +1 -1
  13. package/lib/module/ui/components/accountSettings/AccountCategoriesSection.js +134 -0
  14. package/lib/module/ui/components/accountSettings/AccountCategoriesSection.js.map +1 -0
  15. package/lib/module/ui/components/accountSettings/AccountSettings.js +355 -0
  16. package/lib/module/ui/components/accountSettings/AccountSettings.js.map +1 -0
  17. package/lib/module/ui/hooks/useAvatarPicker.js +53 -48
  18. package/lib/module/ui/hooks/useAvatarPicker.js.map +1 -1
  19. package/lib/module/ui/screens/AccountSettingsScreen.js +16 -235
  20. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  21. package/lib/module/ui/screens/ChangeAvatarScreen.js +23 -4
  22. package/lib/module/ui/screens/ChangeAvatarScreen.js.map +1 -1
  23. package/lib/module/ui/screens/index.js +4 -0
  24. package/lib/module/ui/screens/index.js.map +1 -1
  25. package/lib/typescript/commonjs/ui/components/accountSettings/AccountCategoriesSection.d.ts +26 -0
  26. package/lib/typescript/commonjs/ui/components/accountSettings/AccountCategoriesSection.d.ts.map +1 -0
  27. package/lib/typescript/commonjs/ui/components/accountSettings/AccountSettings.d.ts +80 -0
  28. package/lib/typescript/commonjs/ui/components/accountSettings/AccountSettings.d.ts.map +1 -0
  29. package/lib/typescript/commonjs/ui/hooks/useAvatarPicker.d.ts +22 -1
  30. package/lib/typescript/commonjs/ui/hooks/useAvatarPicker.d.ts.map +1 -1
  31. package/lib/typescript/commonjs/ui/screens/AccountSettingsScreen.d.ts +4 -5
  32. package/lib/typescript/commonjs/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  33. package/lib/typescript/commonjs/ui/screens/ChangeAvatarScreen.d.ts.map +1 -1
  34. package/lib/typescript/commonjs/ui/screens/index.d.ts +1 -0
  35. package/lib/typescript/commonjs/ui/screens/index.d.ts.map +1 -1
  36. package/lib/typescript/module/ui/components/accountSettings/AccountCategoriesSection.d.ts +26 -0
  37. package/lib/typescript/module/ui/components/accountSettings/AccountCategoriesSection.d.ts.map +1 -0
  38. package/lib/typescript/module/ui/components/accountSettings/AccountSettings.d.ts +80 -0
  39. package/lib/typescript/module/ui/components/accountSettings/AccountSettings.d.ts.map +1 -0
  40. package/lib/typescript/module/ui/hooks/useAvatarPicker.d.ts +22 -1
  41. package/lib/typescript/module/ui/hooks/useAvatarPicker.d.ts.map +1 -1
  42. package/lib/typescript/module/ui/screens/AccountSettingsScreen.d.ts +4 -5
  43. package/lib/typescript/module/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  44. package/lib/typescript/module/ui/screens/ChangeAvatarScreen.d.ts.map +1 -1
  45. package/lib/typescript/module/ui/screens/index.d.ts +1 -0
  46. package/lib/typescript/module/ui/screens/index.d.ts.map +1 -1
  47. package/package.json +4 -4
  48. package/src/ui/components/accountSettings/AccountCategoriesSection.tsx +180 -0
  49. package/src/ui/components/accountSettings/AccountSettings.tsx +421 -0
  50. package/src/ui/components/accountSettings/__tests__/AccountSettings.test.tsx +101 -0
  51. package/src/ui/hooks/useAvatarPicker.ts +65 -69
  52. package/src/ui/screens/AccountSettingsScreen.tsx +13 -277
  53. package/src/ui/screens/ChangeAvatarScreen.tsx +25 -6
  54. package/src/ui/screens/index.ts +8 -0
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+
3
+ import { useCallback, useState } from 'react';
4
+ import { View } from 'react-native';
5
+ import { Button, CloseButton } from '@oxy.so/bloom/button';
6
+ import { GroupedSection, GroupedSectionItem } from '@oxy.so/bloom/grouped-section';
7
+ import { useTheme } from '@oxy.so/bloom/theme';
8
+ import { ACCOUNT_CATEGORY_IDS, MAX_ACCOUNT_CATEGORIES, SELECTABLE_ACCOUNT_CATEGORY_IDS, accountCategoryLabel } from '@oxy.so/core';
9
+ import MaterialCommunityIcons from "../../icons/MaterialCommunityIcons.js";
10
+ import { SettingsIcon } from "../SettingsIcon.js";
11
+ import { useI18n } from "../../hooks/useI18n.js";
12
+
13
+ /**
14
+ * An account's categories are an ORDERED list whose element 0 is the primary —
15
+ * nothing else records which one is. So nothing here sorts: a choice joins at
16
+ * the end (choosing a second category must not demote the first), "Make
17
+ * primary" moves one to the front, and two lists are equal only in the same
18
+ * order (a promotion changes nothing BUT the order).
19
+ */
20
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
21
+ export function toggleAccountCategory(current, id) {
22
+ if (current.includes(id)) return current.filter(entry => entry !== id);
23
+ if (current.length >= MAX_ACCOUNT_CATEGORIES) return [...current];
24
+ return [...current, id];
25
+ }
26
+ export function promoteAccountCategory(current, id) {
27
+ if (!current.includes(id)) return [...current];
28
+ return [id, ...current.filter(entry => entry !== id)];
29
+ }
30
+ export function accountCategoriesEqual(a, b) {
31
+ return a.length === b.length && a.every((entry, index) => entry === b[index]);
32
+ }
33
+
34
+ /**
35
+ * A stored id can be one this build has never heard of — the vocabulary grows
36
+ * at Oxy, and an older client meets the newer id. It keeps its row (it counts
37
+ * against the cap and survives a save) but is never printed raw.
38
+ */
39
+ const KNOWN_IDS = new Set(ACCOUNT_CATEGORY_IDS);
40
+ /**
41
+ * The categories editor of `AccountSettings`: the chosen categories in their
42
+ * stored order (primary first), and a collapsible list of the vocabulary to add
43
+ * from. Only SELECTABLE ids are offered — a withdrawn one stays on an account
44
+ * that carries it, but Oxy refuses newly adding it.
45
+ */
46
+ export const AccountCategoriesSection = ({
47
+ value,
48
+ onChange,
49
+ disabled = false
50
+ }) => {
51
+ const {
52
+ colors
53
+ } = useTheme();
54
+ const {
55
+ t,
56
+ locale
57
+ } = useI18n();
58
+ const [pickerOpen, setPickerOpen] = useState(false);
59
+ const atCap = value.length >= MAX_ACCOUNT_CATEGORIES;
60
+ const labelOf = useCallback(id => KNOWN_IDS.has(id) ? accountCategoryLabel(locale, id) : t('accounts.settings.categories.unavailable'), [locale, t]);
61
+ const toggle = useCallback(id => onChange(toggleAccountCategory(value, id)), [onChange, value]);
62
+ return /*#__PURE__*/_jsxs(View, {
63
+ className: "gap-space-8",
64
+ children: [/*#__PURE__*/_jsxs(GroupedSection, {
65
+ title: t('accounts.settings.categories.title'),
66
+ footer: t('accounts.settings.categories.footer', {
67
+ max: MAX_ACCOUNT_CATEGORIES
68
+ }),
69
+ children: [value.map((id, index) => {
70
+ const label = labelOf(id);
71
+ return /*#__PURE__*/_jsx(GroupedSectionItem, {
72
+ icon: /*#__PURE__*/_jsx(SettingsIcon, {
73
+ name: index === 0 ? 'star' : 'tag-outline',
74
+ color: index === 0 ? colors.primary : colors.textSecondary
75
+ }),
76
+ title: label,
77
+ description: index === 0 ? t('accounts.settings.categories.primary') : undefined,
78
+ showChevron: false,
79
+ disabled: disabled,
80
+ rightElement: /*#__PURE__*/_jsxs(View, {
81
+ className: "flex-row items-center gap-space-8",
82
+ children: [index > 0 ? /*#__PURE__*/_jsx(Button, {
83
+ appearance: "subtle",
84
+ size: "xs",
85
+ disabled: disabled,
86
+ onPress: () => onChange(promoteAccountCategory(value, id)),
87
+ children: t('accounts.settings.categories.makePrimary')
88
+ }) : null, /*#__PURE__*/_jsx(CloseButton, {
89
+ size: "xs",
90
+ disabled: disabled,
91
+ onPress: () => toggle(id),
92
+ accessibilityLabel: t('accounts.settings.categories.remove', {
93
+ category: label
94
+ })
95
+ })]
96
+ })
97
+ }, id);
98
+ }), /*#__PURE__*/_jsx(GroupedSectionItem, {
99
+ icon: /*#__PURE__*/_jsx(SettingsIcon, {
100
+ name: "plus",
101
+ color: colors.textSecondary
102
+ }),
103
+ title: t('accounts.settings.categories.add'),
104
+ description: atCap ? t('accounts.settings.categories.atCap', {
105
+ max: MAX_ACCOUNT_CATEGORIES
106
+ }) : undefined,
107
+ disabled: disabled || atCap,
108
+ showChevron: false,
109
+ rightElement: /*#__PURE__*/_jsx(MaterialCommunityIcons, {
110
+ name: pickerOpen ? 'chevron-up' : 'chevron-down',
111
+ size: 20,
112
+ color: colors.textSecondary
113
+ }),
114
+ onPress: () => setPickerOpen(open => !open)
115
+ })]
116
+ }), pickerOpen && !atCap ? /*#__PURE__*/_jsx(GroupedSection, {
117
+ children: SELECTABLE_ACCOUNT_CATEGORY_IDS.map(id => {
118
+ const selected = value.includes(id);
119
+ return /*#__PURE__*/_jsx(GroupedSectionItem, {
120
+ title: accountCategoryLabel(locale, id),
121
+ showChevron: false,
122
+ disabled: disabled,
123
+ rightElement: selected ? /*#__PURE__*/_jsx(MaterialCommunityIcons, {
124
+ name: "check-circle",
125
+ size: 20,
126
+ color: colors.primary
127
+ }) : undefined,
128
+ onPress: () => toggle(id)
129
+ }, id);
130
+ })
131
+ }) : null]
132
+ });
133
+ };
134
+ //# sourceMappingURL=AccountCategoriesSection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["useCallback","useState","View","Button","CloseButton","GroupedSection","GroupedSectionItem","useTheme","ACCOUNT_CATEGORY_IDS","MAX_ACCOUNT_CATEGORIES","SELECTABLE_ACCOUNT_CATEGORY_IDS","accountCategoryLabel","MaterialCommunityIcons","SettingsIcon","useI18n","jsx","_jsx","jsxs","_jsxs","toggleAccountCategory","current","id","includes","filter","entry","length","promoteAccountCategory","accountCategoriesEqual","a","b","every","index","KNOWN_IDS","Set","AccountCategoriesSection","value","onChange","disabled","colors","t","locale","pickerOpen","setPickerOpen","atCap","labelOf","has","toggle","className","children","title","footer","max","map","label","icon","name","color","primary","textSecondary","description","undefined","showChevron","rightElement","appearance","size","onPress","accessibilityLabel","category","open","selected"],"sourceRoot":"../../../../../src","sources":["ui/components/accountSettings/AccountCategoriesSection.tsx"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,355 @@
1
+ "use strict";
2
+
3
+ import { useCallback, useMemo, useState } from 'react';
4
+ import { View } from 'react-native';
5
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
6
+ import { Avatar } from '@oxy.so/bloom/avatar';
7
+ import { Button } from '@oxy.so/bloom/button';
8
+ import { GroupedSection, GroupedSectionItem } from '@oxy.so/bloom/grouped-section';
9
+ import { surfaces } from '@oxy.so/bloom/surfaces';
10
+ import { TextField, TextFieldInput } from '@oxy.so/bloom/text-field';
11
+ import { useTheme } from '@oxy.so/bloom/theme';
12
+ import { toast } from '@oxy.so/bloom/toast';
13
+ import { Text } from '@oxy.so/bloom/typography';
14
+ import { accountNameInvalidKey, accountNameInvalidMessage, accountNameMaxLength, getNormalizedUserHandle, isValidAccountName, kindAcceptsAccountCategories } from '@oxy.so/core';
15
+ import { useOxy } from "../../context/oxyRuntimeContext.js";
16
+ import { useI18n } from "../../hooks/useI18n.js";
17
+ import { pickAvatarFile } from "../../hooks/useAvatarPicker.js";
18
+ import { clearedFieldsFromAccountUpdate, upsertCachedUser } from "../../hooks/queries/userCache.js";
19
+ import { OrganizationSettings } from "../organizations/OrganizationSettings.js";
20
+ import { SettingsIcon } from "../SettingsIcon.js";
21
+ import { AccountCategoriesSection, accountCategoriesEqual } from "./AccountCategoriesSection.js";
22
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
23
+ const BIO_MAX = 160;
24
+ const AVATAR_SIZE = 64;
25
+ const errorMessage = (error, fallback) => error instanceof Error && error.message ? error.message : fallback;
26
+
27
+ /**
28
+ * How archiving the account behaves, for an app whose ending of an account
29
+ * means more than Oxy's archive.
30
+ */
31
+
32
+ /**
33
+ * The account-graph node {@link AccountSettings} edits — seeded from the
34
+ * session's account list, then read fresh. `node` is `null` until it resolves.
35
+ */
36
+ export function useAccountSettingsNode(accountId) {
37
+ const {
38
+ oxyServices,
39
+ canUsePrivateApi,
40
+ user,
41
+ accounts
42
+ } = useOxy();
43
+ const query = useQuery({
44
+ queryKey: ['accounts', 'detail', accountId, user?.id, canUsePrivateApi],
45
+ initialData: canUsePrivateApi ? accounts?.find(account => account.accountId === accountId) : undefined,
46
+ initialDataUpdatedAt: 0,
47
+ queryFn: () => oxyServices.accounts.get(accountId),
48
+ enabled: canUsePrivateApi && accountId.length > 0
49
+ });
50
+ return {
51
+ query,
52
+ node: canUsePrivateApi && query.isSuccess ? query.data : null
53
+ };
54
+ }
55
+
56
+ /**
57
+ * A non-personal account's settings: its picture, name, bio and categories,
58
+ * its members, and archiving it. The one editor for every member-operated
59
+ * account — the `AccountSettings` surface renders it, and an app embeds it in
60
+ * its own page (`@oxy.so/services/screens`) with its own sections and archive
61
+ * steps around it. Oxy decides what the caller may do from the membership it
62
+ * resolved, so every section is shown only to a member who holds its right.
63
+ */
64
+ export const AccountSettings = ({
65
+ accountId,
66
+ extraSections,
67
+ onUpdated,
68
+ archive,
69
+ navigate,
70
+ onClose
71
+ }) => {
72
+ const {
73
+ colors
74
+ } = useTheme();
75
+ const {
76
+ t
77
+ } = useI18n();
78
+ const {
79
+ oxyServices,
80
+ user,
81
+ accounts,
82
+ switchToAccount,
83
+ showBottomSheet
84
+ } = useOxy();
85
+ const queryClient = useQueryClient();
86
+ const id = accountId;
87
+ const {
88
+ query: accountQuery,
89
+ node
90
+ } = useAccountSettingsNode(id);
91
+ const account = node?.account;
92
+ const permissions = node?.callerMembership?.permissions ?? [];
93
+ const isImplicitOwner = node ? node.relationship !== 'member' : false;
94
+ const can = useCallback(permission => isImplicitOwner || permissions.includes(permission), [isImplicitOwner, permissions]);
95
+ const canUpdate = can('account:update');
96
+ const canViewMembers = can('members:read');
97
+ const canArchive = can('account:delete');
98
+ const hasCategories = kindAcceptsAccountCategories(node?.kind);
99
+
100
+ // Editable fields, seeded once from the loaded account and NOT re-synced: while
101
+ // the form is open it is the only writer, so a sync could only overwrite what
102
+ // is being typed. The stored values are read on every render for the dirty
103
+ // check, so a save settles the button without anything mirroring state.
104
+ const [seeded, setSeeded] = useState(false);
105
+ const [displayName, setDisplayName] = useState('');
106
+ const [displayNameError, setDisplayNameError] = useState('');
107
+ const [bio, setBio] = useState('');
108
+ // Verbatim, including an id this build cannot name: dropping it on load would
109
+ // delete it on the next save.
110
+ const [categories, setCategories] = useState([]);
111
+ const storedName = account?.name?.displayName ?? getNormalizedUserHandle(account) ?? '';
112
+ const storedCategories = account?.accountCategories ?? [];
113
+ if (node && !seeded) {
114
+ setDisplayName(storedName);
115
+ setBio(account?.bio ?? '');
116
+ setCategories([...storedCategories]);
117
+ setSeeded(true);
118
+ }
119
+ const updateMutation = useMutation({
120
+ mutationKey: ['accounts', 'update', id],
121
+ mutationFn: input => oxyServices.accounts.update(id, input),
122
+ onSuccess: (updatedNode, input) => {
123
+ const cleared = clearedFieldsFromAccountUpdate(input);
124
+ upsertCachedUser(queryClient, updatedNode.account, user?.id, {
125
+ cleared: cleared.length > 0 ? cleared : undefined
126
+ });
127
+ onUpdated?.(updatedNode.account, {
128
+ cleared
129
+ });
130
+ queryClient.invalidateQueries({
131
+ queryKey: ['accounts', 'detail', id]
132
+ });
133
+ queryClient.invalidateQueries({
134
+ queryKey: ['accounts']
135
+ });
136
+ toast.success(t('accounts.settings.toasts.saved'));
137
+ },
138
+ onError: error => {
139
+ toast.error(errorMessage(error, t('accounts.settings.toasts.saveFailed')));
140
+ }
141
+ });
142
+ const archiveMutation = useMutation({
143
+ mutationKey: ['accounts', 'archive', id],
144
+ mutationFn: async target => {
145
+ await archive?.beforeArchive?.(target);
146
+ await oxyServices.accounts.archive(id);
147
+ return target;
148
+ },
149
+ onSuccess: async target => {
150
+ // If we archived the account we're currently signed in AS, switch back to
151
+ // the personal account so the app isn't left as an archived identity.
152
+ if (user?.id === id) {
153
+ const personal = accounts.find(entry => entry.relationship === 'self');
154
+ if (personal) {
155
+ await switchToAccount(personal.accountId).catch(() => undefined);
156
+ }
157
+ }
158
+ queryClient.invalidateQueries({
159
+ queryKey: ['accounts']
160
+ });
161
+ toast.success(archive?.successMessage ?? t('accounts.settings.toasts.archived'));
162
+ if (archive?.onArchived) archive.onArchived(target);else onClose?.();
163
+ },
164
+ onError: error => {
165
+ toast.error(errorMessage(error, t('accounts.settings.toasts.archiveFailed')));
166
+ }
167
+ });
168
+ const handleArchive = useCallback(async () => {
169
+ if (!node) return;
170
+ let confirmed;
171
+ try {
172
+ confirmed = archive?.confirm ? await archive.confirm(node) : await surfaces.confirm({
173
+ title: t('accounts.settings.archive.confirmTitle'),
174
+ description: t('accounts.settings.archive.confirmDescription'),
175
+ confirmLabel: t('accounts.settings.archive.title'),
176
+ cancelLabel: t('common.cancel'),
177
+ destructive: true
178
+ });
179
+ } catch (error) {
180
+ toast.error(errorMessage(error, t('accounts.settings.toasts.archiveFailed')));
181
+ return;
182
+ }
183
+ if (confirmed) archiveMutation.mutate(node);
184
+ }, [archive, archiveMutation, node, t]);
185
+ const handleDisplayNameChange = useCallback(value => {
186
+ setDisplayName(value);
187
+ const trimmed = value.trim();
188
+ if (!trimmed) {
189
+ setDisplayNameError('');
190
+ return;
191
+ }
192
+ const kind = node?.account.kind;
193
+ setDisplayNameError(isValidAccountName(trimmed, kind) ? '' : t(`accounts.settings.displayName.${accountNameInvalidKey(kind)}`) || accountNameInvalidMessage(kind));
194
+ }, [node?.account.kind, t]);
195
+ const trimmedName = displayName.trim();
196
+ const trimmedBio = bio.trim();
197
+ const categoriesChanged = hasCategories && !accountCategoriesEqual(categories, storedCategories);
198
+ const profileChanged = trimmedName !== storedName.trim() || trimmedBio !== (account?.bio ?? '').trim() || categoriesChanged;
199
+ // An empty display name is refused rather than sent: at Oxy an empty
200
+ // `displayName` clears the explicit name, which a managed account (a title,
201
+ // not a given-and-family name) has nothing to fall back from.
202
+ const canSave = profileChanged && trimmedName.length > 0 && !displayNameError;
203
+ const handleSave = useCallback(() => {
204
+ if (!canSave) return;
205
+ updateMutation.mutate({
206
+ name: {
207
+ displayName: trimmedName
208
+ },
209
+ // `null` is Oxy's clear; an empty string would store one.
210
+ bio: trimmedBio ? trimmedBio : null,
211
+ // Whole and IN ORDER — element 0 is the primary, and `[]` is the clear.
212
+ ...(categoriesChanged ? {
213
+ accountCategories: categories
214
+ } : null)
215
+ });
216
+ }, [canSave, categories, categoriesChanged, trimmedBio, trimmedName, updateMutation]);
217
+
218
+ // The picture is written on its own, as the personal profile's is: the shared
219
+ // picker uploads the crop, and the account takes it (or `null`, its clear).
220
+ const handleChangePicture = useCallback(async () => {
221
+ try {
222
+ const avatar = await pickAvatarFile(oxyServices, {
223
+ avatar: account?.avatar,
224
+ displayName: storedName
225
+ });
226
+ if (avatar !== undefined) updateMutation.mutate({
227
+ avatar
228
+ });
229
+ } catch (error) {
230
+ toast.error(errorMessage(error, t('accounts.settings.toasts.saveFailed')));
231
+ }
232
+ }, [account?.avatar, oxyServices, storedName, t, updateMutation]);
233
+ const avatarUri = useMemo(() => account?.avatar ? oxyServices.assets.publicUrl(account.avatar, 'thumb') : undefined, [account?.avatar, oxyServices]);
234
+ const openMembers = useCallback(() => {
235
+ if (navigate) navigate('AccountMembers', {
236
+ accountId: id
237
+ });else showBottomSheet?.({
238
+ screen: 'AccountMembers',
239
+ props: {
240
+ accountId: id
241
+ }
242
+ });
243
+ }, [id, navigate, showBottomSheet]);
244
+ if (!id) {
245
+ return /*#__PURE__*/_jsx(View, {
246
+ className: "items-center justify-center px-screen-margin py-space-40",
247
+ children: /*#__PURE__*/_jsx(Text, {
248
+ className: "text-body font-body text-text-secondary text-center",
249
+ children: t('accounts.settings.errors.missingAccount')
250
+ })
251
+ });
252
+ }
253
+ if (accountQuery.isLoading || !node) {
254
+ return /*#__PURE__*/_jsx(View, {
255
+ className: "items-center py-space-32"
256
+ });
257
+ }
258
+ const busy = updateMutation.isPending;
259
+ return /*#__PURE__*/_jsxs(View, {
260
+ className: "px-screen-margin pt-space-24 pb-space-32 gap-space-24",
261
+ children: [canUpdate ? /*#__PURE__*/_jsxs(View, {
262
+ className: "gap-space-16 p-space-16 rounded-radius-20 bg-fill",
263
+ children: [/*#__PURE__*/_jsxs(View, {
264
+ className: "flex-row items-center gap-space-16",
265
+ children: [/*#__PURE__*/_jsx(Avatar, {
266
+ source: avatarUri,
267
+ name: storedName,
268
+ size: AVATAR_SIZE
269
+ }), /*#__PURE__*/_jsx(Button, {
270
+ appearance: "subtle",
271
+ size: "sm",
272
+ onPress: () => void handleChangePicture(),
273
+ disabled: busy,
274
+ children: t('accounts.settings.picture.change')
275
+ })]
276
+ }), /*#__PURE__*/_jsxs(View, {
277
+ className: "gap-space-4",
278
+ children: [/*#__PURE__*/_jsx(TextField, {
279
+ isInvalid: Boolean(displayNameError),
280
+ children: /*#__PURE__*/_jsx(TextFieldInput, {
281
+ floatingLabel: true,
282
+ label: t('accounts.settings.displayName.label'),
283
+ value: displayName,
284
+ onChangeText: handleDisplayNameChange,
285
+ isInvalid: Boolean(displayNameError),
286
+ maxLength: accountNameMaxLength(node.account.kind)
287
+ })
288
+ }), displayNameError ? /*#__PURE__*/_jsx(Text, {
289
+ className: "text-caption font-caption text-negative px-space-4",
290
+ children: displayNameError
291
+ }) : null]
292
+ }), /*#__PURE__*/_jsxs(View, {
293
+ className: "gap-space-4",
294
+ children: [/*#__PURE__*/_jsx(TextField, {
295
+ children: /*#__PURE__*/_jsx(TextFieldInput, {
296
+ floatingLabel: true,
297
+ label: t('accounts.settings.bio.label'),
298
+ value: bio,
299
+ onChangeText: setBio,
300
+ maxLength: BIO_MAX,
301
+ multiline: true,
302
+ numberOfLines: 3,
303
+ textAlignVertical: "top"
304
+ })
305
+ }), /*#__PURE__*/_jsxs(Text, {
306
+ className: "text-caption font-caption text-text-tertiary px-space-4 text-right",
307
+ children: [bio.length, "/", BIO_MAX]
308
+ })]
309
+ }), hasCategories ? /*#__PURE__*/_jsx(AccountCategoriesSection, {
310
+ value: categories,
311
+ onChange: setCategories,
312
+ disabled: busy
313
+ }) : null, /*#__PURE__*/_jsx(Button, {
314
+ appearance: "solid",
315
+ tone: "accent",
316
+ onPress: handleSave,
317
+ disabled: busy || !canSave,
318
+ loading: busy,
319
+ accessibilityLabel: t('accounts.settings.save'),
320
+ className: "w-full",
321
+ children: t('accounts.settings.save')
322
+ })]
323
+ }) : null, node.account.kind === 'organization' ? /*#__PURE__*/_jsx(OrganizationSettings, {
324
+ accountId: id,
325
+ canUpdate: canUpdate
326
+ }, id) : null, canViewMembers ? /*#__PURE__*/_jsx(GroupedSection, {
327
+ title: t('accounts.settings.sections.access'),
328
+ children: /*#__PURE__*/_jsx(GroupedSectionItem, {
329
+ icon: /*#__PURE__*/_jsx(SettingsIcon, {
330
+ name: "account-multiple",
331
+ color: colors.info
332
+ }),
333
+ title: t('accounts.members.title'),
334
+ description: t('accounts.settings.members.subtitle'),
335
+ onPress: openMembers
336
+ })
337
+ }) : null, extraSections, canArchive ? /*#__PURE__*/_jsx(GroupedSection, {
338
+ title: t('accounts.settings.sections.dangerZone'),
339
+ footer: archive?.footer,
340
+ children: /*#__PURE__*/_jsx(GroupedSectionItem, {
341
+ icon: /*#__PURE__*/_jsx(SettingsIcon, {
342
+ name: "archive-arrow-down",
343
+ color: colors.error
344
+ }),
345
+ title: archive?.title ?? t('accounts.settings.archive.title'),
346
+ description: archive?.title ? archive.description : t('accounts.settings.archive.subtitle'),
347
+ destructive: true,
348
+ disabled: archiveMutation.isPending,
349
+ onPress: () => void handleArchive()
350
+ })
351
+ }) : null]
352
+ });
353
+ };
354
+ export default AccountSettings;
355
+ //# sourceMappingURL=AccountSettings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["useCallback","useMemo","useState","View","useMutation","useQuery","useQueryClient","Avatar","Button","GroupedSection","GroupedSectionItem","surfaces","TextField","TextFieldInput","useTheme","toast","Text","accountNameInvalidKey","accountNameInvalidMessage","accountNameMaxLength","getNormalizedUserHandle","isValidAccountName","kindAcceptsAccountCategories","useOxy","useI18n","pickAvatarFile","clearedFieldsFromAccountUpdate","upsertCachedUser","OrganizationSettings","SettingsIcon","AccountCategoriesSection","accountCategoriesEqual","jsx","_jsx","jsxs","_jsxs","BIO_MAX","AVATAR_SIZE","errorMessage","error","fallback","Error","message","useAccountSettingsNode","accountId","oxyServices","canUsePrivateApi","user","accounts","query","queryKey","id","initialData","find","account","undefined","initialDataUpdatedAt","queryFn","get","enabled","length","node","isSuccess","data","AccountSettings","extraSections","onUpdated","archive","navigate","onClose","colors","t","switchToAccount","showBottomSheet","queryClient","accountQuery","permissions","callerMembership","isImplicitOwner","relationship","can","permission","includes","canUpdate","canViewMembers","canArchive","hasCategories","kind","seeded","setSeeded","displayName","setDisplayName","displayNameError","setDisplayNameError","bio","setBio","categories","setCategories","storedName","name","storedCategories","accountCategories","updateMutation","mutationKey","mutationFn","input","update","onSuccess","updatedNode","cleared","invalidateQueries","success","onError","archiveMutation","target","beforeArchive","personal","entry","catch","successMessage","onArchived","handleArchive","confirmed","confirm","title","description","confirmLabel","cancelLabel","destructive","mutate","handleDisplayNameChange","value","trimmed","trim","trimmedName","trimmedBio","categoriesChanged","profileChanged","canSave","handleSave","handleChangePicture","avatar","avatarUri","assets","publicUrl","openMembers","screen","props","className","children","isLoading","busy","isPending","source","size","appearance","onPress","disabled","isInvalid","Boolean","floatingLabel","label","onChangeText","maxLength","multiline","numberOfLines","textAlignVertical","onChange","tone","loading","accessibilityLabel","icon","color","info","footer"],"sourceRoot":"../../../../../src","sources":["ui/components/accountSettings/AccountSettings.tsx"],"mappings":"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","ignoreList":[]}
@@ -4,7 +4,10 @@
4
4
  * Avatar Picker Hook
5
5
  *
6
6
  * The ONE write path for a user's profile picture. It owns no UI: the whole
7
- * choose-and-crop experience is a single surface.
7
+ * choose-and-crop experience is a single surface. The choosing half —
8
+ * {@link pickAvatarFile} — is account-agnostic and also serves a managed
9
+ * account's picture (`AccountSettings`), so there is one picker, one cropper and
10
+ * one upload for every account an app can edit.
8
11
  *
9
12
  * Flow (typed promise over the shared surface stack — no callback props):
10
13
  * 1. `openWithinOrPresent('ChangeAvatar')` opens the source list (device gallery,
@@ -38,66 +41,68 @@ function extractUploadedFileId(result) {
38
41
  if (first?.id) return first.id;
39
42
  return undefined;
40
43
  }
44
+
45
+ /**
46
+ * Whose picture the picker is changing, when it is not the signed-in user's —
47
+ * the source list previews it and offers "Remove" only when there is one.
48
+ */
49
+
50
+ /**
51
+ * The shared choose-and-crop flow, then the crop uploaded as a NEW public file.
52
+ *
53
+ * Resolves the new file's id, `null` when the person chose to remove the
54
+ * picture, or `undefined` when they backed out. It writes no account: the
55
+ * caller decides whose picture the file becomes, which is what lets the
56
+ * signed-in user's profile and a managed account share every step before that.
57
+ * An upload failure rejects.
58
+ */
59
+ export async function pickAvatarFile(oxyServices, subject) {
60
+ // The source list + crop editor are ONE flow. When triggered from a screen
61
+ // already inside a surface (EditProfile, ManageAccount, WelcomeNewUser,
62
+ // AccountSettings), it MORPHS into that surface and pops back on finish;
63
+ // triggered with no surface open it presents cold. Either way it resolves with
64
+ // the cropped JPEG, a removal, or `undefined` when the user backs out.
65
+ const result = await openWithinOrPresent('ChangeAvatar', subject ? {
66
+ subject
67
+ } : undefined);
68
+ if (!result) return undefined;
69
+ if ('removed' in result) return null;
70
+ const uploadResult = await oxyServices.assets.upload({
71
+ uri: result.uri,
72
+ type: result.mime,
73
+ name: `avatar-${Date.now()}.jpg`
74
+ }, {
75
+ visibility: 'public'
76
+ });
77
+ const fileId = extractUploadedFileId(uploadResult);
78
+ if (!fileId) {
79
+ throw new Error('Avatar upload succeeded but no file ID was returned');
80
+ }
81
+ await updateAvatarVisibility(fileId, oxyServices, 'pickAvatarFile');
82
+ return fileId;
83
+ }
41
84
  export function useAvatarPicker({
42
85
  oxyServices,
43
86
  currentLanguage,
44
87
  activeSessionId,
45
88
  queryClient
46
89
  }) {
47
- /**
48
- * Final step: take the cropped JPEG, upload it as a new public file, then
49
- * set it as the user's avatar. Fires success/error toasts.
50
- */
51
- const finalizeCroppedAvatar = useCallback(async cropped => {
52
- try {
53
- const uploadResult = await oxyServices.assets.upload({
54
- uri: cropped.uri,
55
- type: cropped.mime,
56
- name: `avatar-${Date.now()}.jpg`
57
- }, {
58
- visibility: 'public'
59
- });
60
- const newFileId = extractUploadedFileId(uploadResult);
61
- if (!newFileId) {
62
- throw new Error('Avatar upload succeeded but no file ID was returned');
63
- }
64
- await updateAvatarVisibility(newFileId, oxyServices, 'useAvatarPicker');
65
- await updateProfileWithAvatar({
66
- avatar: newFileId
67
- }, oxyServices, activeSessionId, queryClient);
68
- toast.success(translate(currentLanguage ?? undefined, 'editProfile.toasts.avatarUpdated') || 'Avatar updated');
69
- } catch (e) {
70
- const message = e instanceof Error ? e.message : undefined;
71
- toast.error(message || translate(currentLanguage ?? undefined, 'editProfile.toasts.updateAvatarFailed') || 'Failed to update avatar');
72
- }
73
- }, [activeSessionId, currentLanguage, oxyServices, queryClient]);
74
-
75
- /** Clear the avatar so the profile falls back to the user's initials. */
76
- const removeAvatar = useCallback(async () => {
90
+ const openAvatarPicker = useCallback(async () => {
91
+ const locale = currentLanguage ?? undefined;
77
92
  try {
93
+ const fileId = await pickAvatarFile(oxyServices);
94
+ if (fileId === undefined) return;
95
+ // `''` clears the field on `updateMe`, so the profile falls back to the
96
+ // user's initials.
78
97
  await updateProfileWithAvatar({
79
- avatar: ''
98
+ avatar: fileId ?? ''
80
99
  }, oxyServices, activeSessionId, queryClient);
81
- toast.success(translate(currentLanguage ?? undefined, 'editProfile.toasts.avatarRemoved'));
100
+ toast.success(translate(locale, fileId === null ? 'editProfile.toasts.avatarRemoved' : 'editProfile.toasts.avatarUpdated'));
82
101
  } catch (e) {
83
102
  const message = e instanceof Error ? e.message : undefined;
84
- toast.error(message || translate(currentLanguage ?? undefined, 'editProfile.toasts.updateAvatarFailed'));
103
+ toast.error(message || translate(locale, 'editProfile.toasts.updateAvatarFailed'));
85
104
  }
86
105
  }, [activeSessionId, currentLanguage, oxyServices, queryClient]);
87
- const openAvatarPicker = useCallback(async () => {
88
- // The source list + crop editor are ONE flow. When triggered from a screen
89
- // already inside a surface (EditProfile, ManageAccount, WelcomeNewUser — every
90
- // current entry point), it MORPHS into that surface and pops back on finish;
91
- // triggered with no surface open it presents cold. Either way it resolves with
92
- // the cropped JPEG, a removal, or `undefined` when the user backs out.
93
- const result = await openWithinOrPresent('ChangeAvatar');
94
- if (!result) return;
95
- if ('removed' in result) {
96
- await removeAvatar();
97
- return;
98
- }
99
- await finalizeCroppedAvatar(result);
100
- }, [finalizeCroppedAvatar, removeAvatar]);
101
106
  return {
102
107
  openAvatarPicker
103
108
  };