@oxy.so/services 11.2.0-oxy-one.7 → 11.3.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 (77) hide show
  1. package/lib/commonjs/ui/components/organizations/OrganizationClaimReview.js +208 -0
  2. package/lib/commonjs/ui/components/organizations/OrganizationClaimReview.js.map +1 -0
  3. package/lib/commonjs/ui/components/organizations/OrganizationClaims.js +77 -0
  4. package/lib/commonjs/ui/components/organizations/OrganizationClaims.js.map +1 -0
  5. package/lib/commonjs/ui/components/organizations/OrganizationDirectorySearch.js +218 -0
  6. package/lib/commonjs/ui/components/organizations/OrganizationDirectorySearch.js.map +1 -0
  7. package/lib/commonjs/ui/components/organizations/OrganizationSettings.js +147 -0
  8. package/lib/commonjs/ui/components/organizations/OrganizationSettings.js.map +1 -0
  9. package/lib/commonjs/ui/components/organizations/OrganizationTypePicker.js +34 -0
  10. package/lib/commonjs/ui/components/organizations/OrganizationTypePicker.js.map +1 -0
  11. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +14 -5
  12. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  13. package/lib/commonjs/ui/screens/CreateAccountScreen.js +148 -115
  14. package/lib/commonjs/ui/screens/CreateAccountScreen.js.map +1 -1
  15. package/lib/commonjs/ui/screens/ManageAccountScreen.js +13 -1
  16. package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
  17. package/lib/module/ui/components/organizations/OrganizationClaimReview.js +205 -0
  18. package/lib/module/ui/components/organizations/OrganizationClaimReview.js.map +1 -0
  19. package/lib/module/ui/components/organizations/OrganizationClaims.js +73 -0
  20. package/lib/module/ui/components/organizations/OrganizationClaims.js.map +1 -0
  21. package/lib/module/ui/components/organizations/OrganizationDirectorySearch.js +215 -0
  22. package/lib/module/ui/components/organizations/OrganizationDirectorySearch.js.map +1 -0
  23. package/lib/module/ui/components/organizations/OrganizationSettings.js +143 -0
  24. package/lib/module/ui/components/organizations/OrganizationSettings.js.map +1 -0
  25. package/lib/module/ui/components/organizations/OrganizationTypePicker.js +30 -0
  26. package/lib/module/ui/components/organizations/OrganizationTypePicker.js.map +1 -0
  27. package/lib/module/ui/screens/AccountSettingsScreen.js +14 -5
  28. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  29. package/lib/module/ui/screens/CreateAccountScreen.js +149 -115
  30. package/lib/module/ui/screens/CreateAccountScreen.js.map +1 -1
  31. package/lib/module/ui/screens/ManageAccountScreen.js +13 -1
  32. package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
  33. package/lib/typescript/commonjs/ui/components/organizations/OrganizationClaimReview.d.ts +3 -0
  34. package/lib/typescript/commonjs/ui/components/organizations/OrganizationClaimReview.d.ts.map +1 -0
  35. package/lib/typescript/commonjs/ui/components/organizations/OrganizationClaims.d.ts +2 -0
  36. package/lib/typescript/commonjs/ui/components/organizations/OrganizationClaims.d.ts.map +1 -0
  37. package/lib/typescript/commonjs/ui/components/organizations/OrganizationDirectorySearch.d.ts +6 -0
  38. package/lib/typescript/commonjs/ui/components/organizations/OrganizationDirectorySearch.d.ts.map +1 -0
  39. package/lib/typescript/commonjs/ui/components/organizations/OrganizationSettings.d.ts +5 -0
  40. package/lib/typescript/commonjs/ui/components/organizations/OrganizationSettings.d.ts.map +1 -0
  41. package/lib/typescript/commonjs/ui/components/organizations/OrganizationTypePicker.d.ts +7 -0
  42. package/lib/typescript/commonjs/ui/components/organizations/OrganizationTypePicker.d.ts.map +1 -0
  43. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +3 -2
  44. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  45. package/lib/typescript/commonjs/ui/hooks/queries/usePersonalPlans.d.ts +2 -2
  46. package/lib/typescript/commonjs/ui/icons/Ionicons.d.ts +1 -1
  47. package/lib/typescript/commonjs/ui/icons/MaterialCommunityIcons.d.ts +1 -1
  48. package/lib/typescript/commonjs/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  49. package/lib/typescript/commonjs/ui/screens/CreateAccountScreen.d.ts.map +1 -1
  50. package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts.map +1 -1
  51. package/lib/typescript/module/ui/components/organizations/OrganizationClaimReview.d.ts +3 -0
  52. package/lib/typescript/module/ui/components/organizations/OrganizationClaimReview.d.ts.map +1 -0
  53. package/lib/typescript/module/ui/components/organizations/OrganizationClaims.d.ts +2 -0
  54. package/lib/typescript/module/ui/components/organizations/OrganizationClaims.d.ts.map +1 -0
  55. package/lib/typescript/module/ui/components/organizations/OrganizationDirectorySearch.d.ts +6 -0
  56. package/lib/typescript/module/ui/components/organizations/OrganizationDirectorySearch.d.ts.map +1 -0
  57. package/lib/typescript/module/ui/components/organizations/OrganizationSettings.d.ts +5 -0
  58. package/lib/typescript/module/ui/components/organizations/OrganizationSettings.d.ts.map +1 -0
  59. package/lib/typescript/module/ui/components/organizations/OrganizationTypePicker.d.ts +7 -0
  60. package/lib/typescript/module/ui/components/organizations/OrganizationTypePicker.d.ts.map +1 -0
  61. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +3 -2
  62. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  63. package/lib/typescript/module/ui/hooks/queries/usePersonalPlans.d.ts +2 -2
  64. package/lib/typescript/module/ui/icons/Ionicons.d.ts +1 -1
  65. package/lib/typescript/module/ui/icons/MaterialCommunityIcons.d.ts +1 -1
  66. package/lib/typescript/module/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  67. package/lib/typescript/module/ui/screens/CreateAccountScreen.d.ts.map +1 -1
  68. package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts.map +1 -1
  69. package/package.json +8 -8
  70. package/src/ui/components/organizations/OrganizationClaimReview.tsx +218 -0
  71. package/src/ui/components/organizations/OrganizationClaims.tsx +67 -0
  72. package/src/ui/components/organizations/OrganizationDirectorySearch.tsx +228 -0
  73. package/src/ui/components/organizations/OrganizationSettings.tsx +131 -0
  74. package/src/ui/components/organizations/OrganizationTypePicker.tsx +31 -0
  75. package/src/ui/screens/AccountSettingsScreen.tsx +22 -4
  76. package/src/ui/screens/CreateAccountScreen.tsx +177 -124
  77. package/src/ui/screens/ManageAccountScreen.tsx +18 -0
@@ -1,4 +1,6 @@
1
1
  import type React from 'react';
2
+ import { ORGANIZATION_NAME_MAX_LENGTH, organizationNameSchema } from '@oxy.so/contracts';
3
+ import { OrganizationSettings } from '../components/organizations/OrganizationSettings';
2
4
  import { useCallback, useMemo, useState } from 'react';
3
5
  import { View, ActivityIndicator } from 'react-native';
4
6
  import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
@@ -52,12 +54,12 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
52
54
  const id = typeof accountId === 'string' ? accountId : '';
53
55
 
54
56
  const accountQuery = useQuery({
55
- queryKey: ['accounts', 'detail', id],
57
+ queryKey: ['accounts', 'detail', id, user?.id, canUsePrivateApi],
56
58
  queryFn: () => oxyServices.accounts.get(id),
57
59
  enabled: canUsePrivateApi && id.length > 0,
58
60
  });
59
61
 
60
- const node = accountQuery.data ?? null;
62
+ const node = canUsePrivateApi && accountQuery.isSuccess ? accountQuery.data : null;
61
63
  const permissions = node?.callerMembership?.permissions ?? [];
62
64
  const isImplicitOwner = node ? node.relationship !== 'member' : false;
63
65
  const can = useCallback(
@@ -148,6 +150,14 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
148
150
  setDisplayNameError('');
149
151
  return;
150
152
  }
153
+ if (node?.account.kind === 'organization') {
154
+ setDisplayNameError(
155
+ organizationNameSchema.safeParse(trimmed).success
156
+ ? ''
157
+ : t('organizationDirectory.invalidName'),
158
+ );
159
+ return;
160
+ }
151
161
  const nameParts = trimmed.split(/\s+/).filter(Boolean);
152
162
  const first = nameParts[0] || '';
153
163
  const last = nameParts.length > 1 ? nameParts.slice(1).join(' ') : '';
@@ -158,7 +168,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
158
168
  : '',
159
169
  );
160
170
  },
161
- [t],
171
+ [node?.account.kind, t],
162
172
  );
163
173
 
164
174
  const handleSave = useCallback(() => {
@@ -219,7 +229,11 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
219
229
  value={displayName}
220
230
  onChangeText={handleDisplayNameChange}
221
231
  isInvalid={Boolean(displayNameError)}
222
- maxLength={DISPLAY_NAME_MAX}
232
+ maxLength={
233
+ node?.account.kind === 'organization'
234
+ ? ORGANIZATION_NAME_MAX_LENGTH
235
+ : DISPLAY_NAME_MAX
236
+ }
223
237
  />
224
238
  </TextField>
225
239
  {displayNameError ? (
@@ -261,6 +275,10 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
261
275
  </View>
262
276
  ) : null}
263
277
 
278
+ {node?.account.kind === 'organization' ? (
279
+ <OrganizationSettings key={id} accountId={id} canUpdate={canUpdate} />
280
+ ) : null}
281
+
264
282
  {/* Members entry */}
265
283
  {canViewMembers ? (
266
284
  <SettingsListGroup title={t('accounts.settings.sections.access') || 'Access'}>
@@ -1,4 +1,11 @@
1
1
  import type React from 'react';
2
+ import {
3
+ ORGANIZATION_NAME_MAX_LENGTH,
4
+ organizationNameSchema,
5
+ type OrganizationType,
6
+ } from '@oxy.so/contracts';
7
+ import { OrganizationDirectorySearch } from '../components/organizations/OrganizationDirectorySearch';
8
+ import { OrganizationTypePicker } from '../components/organizations/OrganizationTypePicker';
2
9
  import { useState, useCallback, useRef, useEffect } from 'react';
3
10
  import { View, ActivityIndicator } from 'react-native';
4
11
  import Ionicons from '../icons/Ionicons';
@@ -114,19 +121,21 @@ const ACCOUNT_CATEGORY_OPTIONS: readonly AccountCategoryId[] = SELECTABLE_ACCOUN
114
121
  */
115
122
  const CreateAccountScreen: React.FC<BaseScreenProps> = ({ onClose, goBack, parentAccountId }) => {
116
123
  const bloomTheme = useTheme();
117
- const { oxyServices, createAccount, switchToAccount } = useOxy();
124
+ const { oxyServices, createAccount, switchToAccount, user, canUsePrivateApi } = useOxy();
118
125
  const { t, locale } = useI18n();
119
126
 
120
- useSurfaceHeader({
121
- title: t('accounts.create.title') || 'Create account',
122
- subtitle:
123
- t('accounts.create.subtitle') ||
124
- 'Create an account you control. It will have its own profile, members, and apps.',
125
- });
126
-
127
127
  const parentId = typeof parentAccountId === 'string' ? parentAccountId : undefined;
128
128
 
129
129
  const [kind, setKind] = useState<CreatableAccountKind>('project');
130
+ const [organizationType, setOrganizationType] = useState<OrganizationType>('business');
131
+ const [newOrganization, setNewOrganization] = useState(false);
132
+ const findingOrganization = kind === 'organization' && !newOrganization;
133
+ useSurfaceHeader({
134
+ title: t(findingOrganization ? 'organizationDirectory.title' : 'accounts.create.title'),
135
+ subtitle: t(
136
+ findingOrganization ? 'organizationDirectory.searchHint' : 'accounts.create.subtitle',
137
+ ),
138
+ });
130
139
  /**
131
140
  * ORDER IS THE DATA: index 0 is the primary category. Selecting appends,
132
141
  * de-selecting removes, and neither sorts — so the list the user assembles is
@@ -254,6 +263,7 @@ const CreateAccountScreen: React.FC<BaseScreenProps> = ({ onClose, goBack, paren
254
263
  const handleKindChange = useCallback(
255
264
  (nextKind: CreatableAccountKind) => {
256
265
  setKind(nextKind);
266
+ setNewOrganization(false);
257
267
  if (!username) return;
258
268
  const proposed = nextKind === 'bot' ? applyBotUsernameSuffix(username) : username;
259
269
  setUsername(proposed);
@@ -270,6 +280,14 @@ const CreateAccountScreen: React.FC<BaseScreenProps> = ({ onClose, goBack, paren
270
280
  setDisplayNameError('');
271
281
  return;
272
282
  }
283
+ if (kind === 'organization') {
284
+ setDisplayNameError(
285
+ organizationNameSchema.safeParse(trimmed).success
286
+ ? ''
287
+ : t('organizationDirectory.invalidName'),
288
+ );
289
+ return;
290
+ }
273
291
  const nameParts = trimmed.split(/\s+/);
274
292
  const firstName = nameParts[0] || '';
275
293
  const lastName = nameParts.length > 1 ? nameParts.slice(1).join(' ') : '';
@@ -280,7 +298,7 @@ const CreateAccountScreen: React.FC<BaseScreenProps> = ({ onClose, goBack, paren
280
298
  : '',
281
299
  );
282
300
  },
283
- [t],
301
+ [kind, t],
284
302
  );
285
303
 
286
304
  /**
@@ -301,10 +319,12 @@ const CreateAccountScreen: React.FC<BaseScreenProps> = ({ onClose, goBack, paren
301
319
  }, []);
302
320
 
303
321
  const canCreate =
322
+ canUsePrivateApi &&
304
323
  usernameStatus === 'available' &&
305
324
  displayName.trim().length > 0 &&
306
325
  !displayNameError &&
307
- !isCreating;
326
+ !isCreating &&
327
+ (kind !== 'organization' || newOrganization);
308
328
 
309
329
  const handleCreate = useCallback(async () => {
310
330
  if (!canCreate) return;
@@ -313,6 +333,7 @@ const CreateAccountScreen: React.FC<BaseScreenProps> = ({ onClose, goBack, paren
313
333
  try {
314
334
  const input: CreateAccountInput = {
315
335
  kind,
336
+ ...(kind === 'organization' ? { organizationType } : {}),
316
337
  username,
317
338
  name: { displayName: displayName.trim() },
318
339
  bio: bio.trim() || undefined,
@@ -352,6 +373,7 @@ const CreateAccountScreen: React.FC<BaseScreenProps> = ({ onClose, goBack, paren
352
373
  }, [
353
374
  canCreate,
354
375
  kind,
376
+ organizationType,
355
377
  accountCategories,
356
378
  username,
357
379
  displayName,
@@ -405,128 +427,159 @@ const CreateAccountScreen: React.FC<BaseScreenProps> = ({ onClose, goBack, paren
405
427
  })}
406
428
  </SettingsListGroup>
407
429
 
408
- {/* Categories — multi-select, shown for every kind this screen can create
430
+ {kind === 'organization' && !newOrganization ? (
431
+ <OrganizationDirectorySearch
432
+ key={user?.id}
433
+ onCreateAccount={(name, type) => {
434
+ handleDisplayNameChange(name);
435
+ setOrganizationType(type);
436
+ setNewOrganization(true);
437
+ }}
438
+ />
439
+ ) : (
440
+ <>
441
+ {kind === 'organization' ? (
442
+ <>
443
+ <Button appearance="outline" onPress={() => setNewOrganization(false)}>
444
+ {t('organizationDirectory.back')}
445
+ </Button>
446
+ <OrganizationTypePicker
447
+ value={organizationType}
448
+ onChange={setOrganizationType}
449
+ disabled={isCreating}
450
+ />
451
+ </>
452
+ ) : null}
453
+ {/* Categories — multi-select, shown for every kind this screen can create
409
454
  (they are all non-personal). The badge on a selected row is its
410
455
  POSITION, so the primary is legible as "1" rather than being a rule the
411
456
  user has to be told. */}
412
- <View className="gap-space-4">
413
- <SettingsListGroup title={t('accounts.create.accountCategory.label')}>
414
- {ACCOUNT_CATEGORY_OPTIONS.map((option) => {
415
- const position = accountCategories.indexOf(option);
416
- const selected = position >= 0;
417
- const label = accountCategoryLabel(locale, option);
418
- return (
419
- <SettingsListItem
420
- key={option}
421
- title={label}
422
- disabled={!selected && accountCategories.length >= MAX_ACCOUNT_CATEGORIES}
423
- onPress={() => toggleAccountCategory(option)}
424
- showChevron={false}
425
- rightElement={
426
- selected ? (
427
- <Text className="text-caption-1 font-semibold text-primary">
428
- {position + 1}
429
- </Text>
430
- ) : undefined
431
- }
432
- accessibilityLabel={label}
433
- />
434
- );
435
- })}
436
- </SettingsListGroup>
437
- <Text className="text-caption font-caption text-text-tertiary px-space-4">
438
- {t('accounts.create.accountCategory.hint', { max: MAX_ACCOUNT_CATEGORIES })}
439
- </Text>
440
- </View>
441
-
442
- {/* Details — a grouped section card hosting the form fields */}
443
- <SettingsListGroup title={t('accounts.create.detailsSection') || 'Details'}>
444
- <View className="p-space-16 gap-space-16">
445
- {/* Username */}
446
- <View className="gap-space-8">
447
- <TextField isInvalid={usernameIsInvalid}>
448
- <TextFieldInput
449
- floatingLabel
450
- label={t('accounts.create.username.label') || 'Username'}
451
- value={username}
452
- onChangeText={handleUsernameChange}
453
- isInvalid={usernameIsInvalid}
454
- autoCapitalize="none"
455
- autoCorrect={false}
456
- autoComplete="off"
457
- maxLength={USERNAME_MAX_LENGTH}
458
- />
459
- </TextField>
460
- {usernameStatus === 'checking' || usernameMessage ? (
461
- <View className="flex-row items-center gap-space-4 px-space-4">
462
- {usernameStatus === 'checking' ? (
463
- <ActivityIndicator size="small" color={bloomTheme.colors.primary} />
464
- ) : usernameStatus === 'available' ? (
465
- <Ionicons name="checkmark-circle" size={16} color={bloomTheme.colors.success} />
466
- ) : usernameIsInvalid ? (
467
- <Ionicons name="alert-circle" size={16} color={bloomTheme.colors.negative} />
457
+ <View className="gap-space-4">
458
+ <SettingsListGroup title={t('accounts.create.accountCategory.label')}>
459
+ {ACCOUNT_CATEGORY_OPTIONS.map((option) => {
460
+ const position = accountCategories.indexOf(option);
461
+ const selected = position >= 0;
462
+ const label = accountCategoryLabel(locale, option);
463
+ return (
464
+ <SettingsListItem
465
+ key={option}
466
+ title={label}
467
+ disabled={!selected && accountCategories.length >= MAX_ACCOUNT_CATEGORIES}
468
+ onPress={() => toggleAccountCategory(option)}
469
+ showChevron={false}
470
+ rightElement={
471
+ selected ? (
472
+ <Text className="text-caption-1 font-semibold text-primary">
473
+ {position + 1}
474
+ </Text>
475
+ ) : undefined
476
+ }
477
+ accessibilityLabel={label}
478
+ />
479
+ );
480
+ })}
481
+ </SettingsListGroup>
482
+ <Text className="text-caption font-caption text-text-tertiary px-space-4">
483
+ {t('accounts.create.accountCategory.hint', { max: MAX_ACCOUNT_CATEGORIES })}
484
+ </Text>
485
+ </View>
486
+
487
+ {/* Details — a grouped section card hosting the form fields */}
488
+ <SettingsListGroup title={t('accounts.create.detailsSection') || 'Details'}>
489
+ <View className="p-space-16 gap-space-16">
490
+ {/* Username */}
491
+ <View className="gap-space-8">
492
+ <TextField isInvalid={usernameIsInvalid}>
493
+ <TextFieldInput
494
+ floatingLabel
495
+ label={t('accounts.create.username.label') || 'Username'}
496
+ value={username}
497
+ onChangeText={handleUsernameChange}
498
+ isInvalid={usernameIsInvalid}
499
+ autoCapitalize="none"
500
+ autoCorrect={false}
501
+ autoComplete="off"
502
+ maxLength={USERNAME_MAX_LENGTH}
503
+ />
504
+ </TextField>
505
+ {usernameStatus === 'checking' || usernameMessage ? (
506
+ <View className="flex-row items-center gap-space-4 px-space-4">
507
+ {usernameStatus === 'checking' ? (
508
+ <ActivityIndicator size="small" color={bloomTheme.colors.primary} />
509
+ ) : usernameStatus === 'available' ? (
510
+ <Ionicons
511
+ name="checkmark-circle"
512
+ size={16}
513
+ color={bloomTheme.colors.success}
514
+ />
515
+ ) : usernameIsInvalid ? (
516
+ <Ionicons name="alert-circle" size={16} color={bloomTheme.colors.negative} />
517
+ ) : null}
518
+ {usernameMessage ? (
519
+ <Text className="text-caption font-caption" style={{ color: statusColor }}>
520
+ {usernameMessage}
521
+ </Text>
522
+ ) : null}
523
+ </View>
468
524
  ) : null}
469
- {usernameMessage ? (
470
- <Text className="text-caption font-caption" style={{ color: statusColor }}>
471
- {usernameMessage}
525
+ </View>
526
+
527
+ {/* Display Name */}
528
+ <View className="gap-space-4">
529
+ <TextField isInvalid={Boolean(displayNameError)}>
530
+ <TextFieldInput
531
+ floatingLabel
532
+ label={t('accounts.create.displayName.label') || 'Display name'}
533
+ value={displayName}
534
+ onChangeText={handleDisplayNameChange}
535
+ isInvalid={Boolean(displayNameError)}
536
+ maxLength={
537
+ kind === 'organization' ? ORGANIZATION_NAME_MAX_LENGTH : DISPLAY_NAME_MAX
538
+ }
539
+ />
540
+ </TextField>
541
+ {displayNameError ? (
542
+ <Text className="text-caption font-caption text-negative px-space-4">
543
+ {displayNameError}
472
544
  </Text>
473
545
  ) : null}
474
546
  </View>
475
- ) : null}
476
- </View>
477
-
478
- {/* Display Name */}
479
- <View className="gap-space-4">
480
- <TextField isInvalid={Boolean(displayNameError)}>
481
- <TextFieldInput
482
- floatingLabel
483
- label={t('accounts.create.displayName.label') || 'Display name'}
484
- value={displayName}
485
- onChangeText={handleDisplayNameChange}
486
- isInvalid={Boolean(displayNameError)}
487
- maxLength={DISPLAY_NAME_MAX}
488
- />
489
- </TextField>
490
- {displayNameError ? (
491
- <Text className="text-caption font-caption text-negative px-space-4">
492
- {displayNameError}
493
- </Text>
494
- ) : null}
495
- </View>
496
547
 
497
- {/* Bio */}
498
- <View className="gap-space-4">
499
- <TextField>
500
- <TextFieldInput
501
- floatingLabel
502
- label={t('accounts.create.bio.label') || 'Bio (optional)'}
503
- value={bio}
504
- onChangeText={setBio}
505
- maxLength={BIO_MAX}
506
- multiline
507
- numberOfLines={3}
508
- textAlignVertical="top"
509
- />
510
- </TextField>
511
- <Text className="text-caption font-caption text-text-tertiary px-space-4 text-right">
512
- {bio.length}/{BIO_MAX}
513
- </Text>
514
- </View>
515
- </View>
516
- </SettingsListGroup>
517
-
518
- {/* Create Button */}
519
- <Button
520
- appearance="solid"
521
- tone="accent"
522
- onPress={handleCreate}
523
- disabled={!canCreate}
524
- loading={isCreating}
525
- accessibilityLabel={title}
526
- className="w-full"
527
- >
528
- {title}
529
- </Button>
548
+ {/* Bio */}
549
+ <View className="gap-space-4">
550
+ <TextField>
551
+ <TextFieldInput
552
+ floatingLabel
553
+ label={t('accounts.create.bio.label') || 'Bio (optional)'}
554
+ value={bio}
555
+ onChangeText={setBio}
556
+ maxLength={BIO_MAX}
557
+ multiline
558
+ numberOfLines={3}
559
+ textAlignVertical="top"
560
+ />
561
+ </TextField>
562
+ <Text className="text-caption font-caption text-text-tertiary px-space-4 text-right">
563
+ {bio.length}/{BIO_MAX}
564
+ </Text>
565
+ </View>
566
+ </View>
567
+ </SettingsListGroup>
568
+
569
+ {/* Create Button */}
570
+ <Button
571
+ appearance="solid"
572
+ tone="accent"
573
+ onPress={handleCreate}
574
+ disabled={!canCreate}
575
+ loading={isCreating}
576
+ accessibilityLabel={title}
577
+ className="w-full"
578
+ >
579
+ {title}
580
+ </Button>
581
+ </>
582
+ )}
530
583
  </View>
531
584
  );
532
585
  };
@@ -1,3 +1,5 @@
1
+ import { OrganizationClaimReview } from '../components/organizations/OrganizationClaimReview';
2
+ import { OrganizationClaims } from '../components/organizations/OrganizationClaims';
1
3
  import type React from 'react';
2
4
  import { useCallback, useMemo, useState } from 'react';
3
5
  import { View, StyleSheet, ActivityIndicator, Linking, Platform } from 'react-native';
@@ -396,6 +398,22 @@ const ManageAccountScreen: React.FC<BaseScreenProps> = ({ onClose, goBack, navig
396
398
  />
397
399
  </SettingsListGroup>
398
400
 
401
+ {user?.kind === 'organization' ? (
402
+ <SettingsListGroup title={t('organizationDirectory.title')}>
403
+ <SettingsListItem
404
+ title={t('organizationDirectory.view')}
405
+ onPress={() => navigate?.('AccountSettings', { accountId: user.id })}
406
+ />
407
+ </SettingsListGroup>
408
+ ) : null}
409
+
410
+ {user?.kind === 'personal' ? (
411
+ <>
412
+ <OrganizationClaims key={user.id} />
413
+ <OrganizationClaimReview key={`review:${user.id}`} />
414
+ </>
415
+ ) : null}
416
+
399
417
  {/* Sessions section */}
400
418
  <SettingsListGroup title={t('manageAccount.sections.sessions') || 'Sessions & devices'}>
401
419
  {deviceSessionsLoading ? (