@agent-native/core 0.92.4 → 0.92.6
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.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +19 -0
- package/corpus/core/docs/content/template-assets.mdx +139 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +96 -6
- package/corpus/core/src/cli/design-connect.ts +43 -16
- package/corpus/core/src/cli/skills.ts +52 -16
- package/corpus/core/src/client/AssistantChat.tsx +127 -15
- package/corpus/core/src/client/chat/tool-call-display.tsx +9 -3
- package/corpus/core/src/client/org/TeamPage.tsx +274 -177
- package/corpus/core/src/client/settings/DemoModeSection.tsx +6 -6
- package/corpus/core/src/client/settings/SettingsPanel.tsx +1 -1
- package/corpus/core/src/client/settings/SettingsTabsPage.tsx +49 -1
- package/corpus/core/src/demo/actions/toggle-demo-mode.ts +2 -2
- package/corpus/core/src/demo/config.ts +3 -3
- package/corpus/core/src/demo/redact.ts +720 -0
- package/corpus/core/src/extensions/url-safety.ts +13 -1
- package/corpus/core/src/localization/default-messages.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/ar-SA.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/de-DE.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/en-US.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/es-ES.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/fr-FR.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/hi-IN.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/ja-JP.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/ko-KR.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/pt-BR.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/zh-CN.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/zh-TW.ts +7 -0
- package/corpus/templates/analytics/AGENTS.md +6 -3
- package/corpus/templates/analytics/changelog/2026-07-09-uptime-monitors-use-scheduled-workers-in-production-serverless.md +6 -0
- package/corpus/templates/analytics/docs/uptime-monitoring.md +5 -0
- package/corpus/templates/analytics/server/plugins/uptime-monitor-jobs.ts +14 -2
- package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +35 -0
- package/corpus/templates/assets/.agents/skills/library-management/SKILL.md +19 -0
- package/corpus/templates/assets/AGENTS.md +13 -0
- package/corpus/templates/assets/actions/_generation-preset-settings.ts +73 -0
- package/corpus/templates/assets/actions/_preset-skeleton-validation.ts +63 -0
- package/corpus/templates/assets/actions/create-generation-preset.ts +22 -8
- package/corpus/templates/assets/actions/duplicate-library.ts +49 -1
- package/corpus/templates/assets/actions/generate-image-batch.ts +14 -1
- package/corpus/templates/assets/actions/generate-image.ts +195 -26
- package/corpus/templates/assets/actions/import-asset-from-url.ts +323 -0
- package/corpus/templates/assets/actions/list-generation-presets.ts +1 -1
- package/corpus/templates/assets/actions/rerun-generation-run.ts +37 -0
- package/corpus/templates/assets/actions/update-generation-preset.ts +19 -1
- package/corpus/templates/assets/app/i18n/ar-SA.ts +1 -0
- package/corpus/templates/assets/app/i18n/de-DE.ts +1 -0
- package/corpus/templates/assets/app/i18n/es-ES.ts +1 -0
- package/corpus/templates/assets/app/i18n/fr-FR.ts +1 -0
- package/corpus/templates/assets/app/i18n/hi-IN.ts +1 -0
- package/corpus/templates/assets/app/i18n/ja-JP.ts +1 -0
- package/corpus/templates/assets/app/i18n/ko-KR.ts +1 -0
- package/corpus/templates/assets/app/i18n/pt-BR.ts +1 -0
- package/corpus/templates/assets/app/i18n/zh-CN.ts +1 -0
- package/corpus/templates/assets/app/i18n/zh-TW.ts +41 -0
- package/corpus/templates/assets/app/i18n-data.ts +93 -0
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +66 -11
- package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +959 -24
- package/corpus/templates/assets/changelog/2026-07-07-preset-skeleton-controls-fit-correctly-on-narrower-edit-page.md +6 -0
- package/corpus/templates/assets/changelog/2026-07-09-import-reference-images-from-url.md +6 -0
- package/corpus/templates/assets/changelog/2026-07-09-preset-reference-board.md +6 -0
- package/corpus/templates/assets/server/handlers/assets.ts +7 -27
- package/corpus/templates/assets/server/lib/generation.ts +83 -2
- package/corpus/templates/assets/server/lib/preset-chat-context.ts +17 -4
- package/corpus/templates/assets/server/lib/preset-references.ts +182 -0
- package/corpus/templates/assets/server/lib/upload-validation.ts +38 -0
- package/corpus/templates/assets/shared/api.ts +20 -0
- package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +6 -0
- package/corpus/templates/calendar/actions/create-event.ts +8 -2
- package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +22 -7
- package/corpus/templates/calendar/changelog/2026-07-09-calendar-events-with-guests-now-automatically-get-a-google-m.md +6 -0
- package/corpus/templates/calendar/changelog/2026-07-09-new-events-now-mark-the-creator-rsvp-as-yes.md +6 -0
- package/corpus/templates/calendar/server/handlers/events.ts +9 -2
- package/corpus/templates/calendar/server/lib/event-video-conferencing.ts +58 -0
- package/corpus/templates/calendar/server/lib/google-calendar.ts +1 -0
- package/corpus/templates/clips/changelog/2026-07-09-meeting-reminder-popovers-can-now-be-dismissed-with-a-top-le.md +6 -0
- package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +3 -1
- package/corpus/templates/clips/desktop/src/styles.css +13 -9
- package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +5 -2
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +27 -15
- package/corpus/templates/design/actions/connect-localhost.ts +40 -11
- package/corpus/templates/design/actions/open-visual-edit.ts +7 -1
- package/dist/agent/production-agent.d.ts +34 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +58 -5
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/design-connect.d.ts +5 -1
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +37 -16
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +52 -16
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +3 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +96 -15
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +5 -1
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/org/TeamPage.d.ts.map +1 -1
- package/dist/client/org/TeamPage.js +32 -13
- package/dist/client/org/TeamPage.js.map +1 -1
- package/dist/client/settings/DemoModeSection.d.ts +3 -3
- package/dist/client/settings/DemoModeSection.js +4 -4
- package/dist/client/settings/DemoModeSection.js.map +1 -1
- package/dist/client/settings/SettingsPanel.js +1 -1
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -1
- package/dist/client/settings/SettingsTabsPage.js +38 -2
- package/dist/client/settings/SettingsTabsPage.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/demo/actions/toggle-demo-mode.js +2 -2
- package/dist/demo/actions/toggle-demo-mode.js.map +1 -1
- package/dist/demo/config.js +3 -3
- package/dist/demo/config.js.map +1 -1
- package/dist/demo/redact.d.ts +1 -1
- package/dist/demo/redact.d.ts.map +1 -1
- package/dist/demo/redact.js +0 -0
- package/dist/demo/redact.js.map +1 -1
- package/dist/extensions/url-safety.d.ts +5 -0
- package/dist/extensions/url-safety.d.ts.map +1 -1
- package/dist/extensions/url-safety.js +10 -0
- package/dist/extensions/url-safety.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/localization/default-messages.d.ts +7 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +7 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +3 -3
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/templates/default/app/i18n/ar-SA.ts +7 -0
- package/dist/templates/default/app/i18n/de-DE.ts +7 -0
- package/dist/templates/default/app/i18n/en-US.ts +7 -0
- package/dist/templates/default/app/i18n/es-ES.ts +7 -0
- package/dist/templates/default/app/i18n/fr-FR.ts +7 -0
- package/dist/templates/default/app/i18n/hi-IN.ts +7 -0
- package/dist/templates/default/app/i18n/ja-JP.ts +7 -0
- package/dist/templates/default/app/i18n/ko-KR.ts +7 -0
- package/dist/templates/default/app/i18n/pt-BR.ts +7 -0
- package/dist/templates/default/app/i18n/zh-CN.ts +7 -0
- package/dist/templates/default/app/i18n/zh-TW.ts +7 -0
- package/docs/content/template-assets.mdx +139 -0
- package/package.json +1 -1
- package/src/templates/default/app/i18n/ar-SA.ts +7 -0
- package/src/templates/default/app/i18n/de-DE.ts +7 -0
- package/src/templates/default/app/i18n/en-US.ts +7 -0
- package/src/templates/default/app/i18n/es-ES.ts +7 -0
- package/src/templates/default/app/i18n/fr-FR.ts +7 -0
- package/src/templates/default/app/i18n/hi-IN.ts +7 -0
- package/src/templates/default/app/i18n/ja-JP.ts +7 -0
- package/src/templates/default/app/i18n/ko-KR.ts +7 -0
- package/src/templates/default/app/i18n/pt-BR.ts +7 -0
- package/src/templates/default/app/i18n/zh-CN.ts +7 -0
- package/src/templates/default/app/i18n/zh-TW.ts +7 -0
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Table,
|
|
3
|
+
TableBody,
|
|
4
|
+
TableCell,
|
|
5
|
+
TableHead,
|
|
6
|
+
TableHeader,
|
|
7
|
+
TableRow,
|
|
8
|
+
} from "@agent-native/toolkit/ui/table";
|
|
1
9
|
import {
|
|
2
10
|
IconUserPlus,
|
|
3
11
|
IconTrash,
|
|
@@ -301,12 +309,22 @@ function OrgNameDisplay({ name, canEdit }: { name: string; canEdit: boolean }) {
|
|
|
301
309
|
);
|
|
302
310
|
}
|
|
303
311
|
|
|
312
|
+
interface MemberListItem {
|
|
313
|
+
email: string;
|
|
314
|
+
role: string;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
interface PendingInviteListItem {
|
|
318
|
+
id: string;
|
|
319
|
+
email: string;
|
|
320
|
+
role: string;
|
|
321
|
+
}
|
|
322
|
+
|
|
304
323
|
function MembersCard() {
|
|
305
324
|
const t = useT();
|
|
306
325
|
const { data: org } = useOrg();
|
|
307
326
|
const { data: membersData, isLoading: isLoadingMembers } = useOrgMembers();
|
|
308
327
|
const { data: invitationsData } = useOrgInvitations();
|
|
309
|
-
const removeMember = useRemoveMember();
|
|
310
328
|
const switchOrg = useSwitchOrg();
|
|
311
329
|
|
|
312
330
|
const [showInviteForm, setShowInviteForm] = useState(false);
|
|
@@ -320,108 +338,62 @@ function MembersCard() {
|
|
|
320
338
|
const hasMultipleOrgs = (org.orgs?.length ?? 0) > 1;
|
|
321
339
|
|
|
322
340
|
return (
|
|
323
|
-
<
|
|
324
|
-
<
|
|
325
|
-
<div className="flex
|
|
326
|
-
<div className="flex
|
|
327
|
-
<
|
|
328
|
-
|
|
329
|
-
<div>
|
|
330
|
-
<OrgNameDisplay name={org.orgName ?? ""} canEdit={isOwnerOrAdmin} />
|
|
331
|
-
<div className="text-xs text-muted-foreground">
|
|
332
|
-
{t("org.memberCount", { count: members.length })} ·{" "}
|
|
333
|
-
{t("org.youAreRole", { role: org.role })}
|
|
334
|
-
</div>
|
|
335
|
-
</div>
|
|
336
|
-
</div>
|
|
337
|
-
{hasMultipleOrgs && (
|
|
338
|
-
<select
|
|
339
|
-
value={org.orgId ?? ""}
|
|
340
|
-
onChange={(e) => switchOrg.mutate(e.target.value || null)}
|
|
341
|
-
disabled={switchOrg.isPending}
|
|
342
|
-
className="rounded-md border border-border bg-background px-2.5 py-1.5 text-xs"
|
|
343
|
-
>
|
|
344
|
-
{org.orgs.map((o) => (
|
|
345
|
-
<option key={o.orgId} value={o.orgId}>
|
|
346
|
-
{o.orgName}
|
|
347
|
-
</option>
|
|
348
|
-
))}
|
|
349
|
-
</select>
|
|
350
|
-
)}
|
|
351
|
-
</div>
|
|
352
|
-
|
|
353
|
-
<div className="grid gap-5 border-t border-border pt-3 lg:grid-cols-[minmax(0,1fr)_minmax(18rem,22rem)]">
|
|
354
|
-
<div className="min-w-0 space-y-4">
|
|
355
|
-
<div className="space-y-1">
|
|
356
|
-
<div className="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2">
|
|
357
|
-
{t("org.members")}
|
|
341
|
+
<div className="space-y-4">
|
|
342
|
+
<section className="rounded-lg border border-border bg-card p-4 space-y-4">
|
|
343
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
|
344
|
+
<div className="flex min-w-0 items-center gap-3">
|
|
345
|
+
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-blue-600/10">
|
|
346
|
+
<IconUsersGroup className="h-5 w-5 text-blue-600" />
|
|
358
347
|
</div>
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
{
|
|
362
|
-
|
|
363
|
-
key={i}
|
|
364
|
-
className="flex items-center justify-between py-1.5 px-2"
|
|
365
|
-
>
|
|
366
|
-
<div className="flex items-center gap-2">
|
|
367
|
-
<div
|
|
368
|
-
className="h-3.5 rounded bg-muted animate-pulse"
|
|
369
|
-
style={{ width: `${140 + i * 30}px` }}
|
|
370
|
-
/>
|
|
371
|
-
<div className="h-3.5 w-3.5 rounded bg-muted animate-pulse" />
|
|
372
|
-
</div>
|
|
373
|
-
</div>
|
|
374
|
-
))}
|
|
375
|
-
</>
|
|
376
|
-
)}
|
|
377
|
-
{members.map((m) => (
|
|
378
|
-
<MemberRow
|
|
379
|
-
key={m.email}
|
|
380
|
-
email={m.email}
|
|
381
|
-
role={m.role}
|
|
382
|
-
isCurrentUser={m.email === org.email}
|
|
383
|
-
currentUserRole={org.role ?? null}
|
|
348
|
+
<div className="min-w-0">
|
|
349
|
+
<OrgNameDisplay
|
|
350
|
+
name={org.orgName ?? ""}
|
|
351
|
+
canEdit={isOwnerOrAdmin}
|
|
384
352
|
/>
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
key={inv.id}
|
|
389
|
-
className="flex items-center justify-between gap-2 py-1.5 px-2 opacity-60"
|
|
390
|
-
>
|
|
391
|
-
<div className="flex min-w-0 items-center gap-2">
|
|
392
|
-
<span className="truncate text-sm">{inv.email}</span>
|
|
393
|
-
<RoleIcon role={inv.role} />
|
|
394
|
-
<span className="rounded border border-border px-1.5 py-0.5 text-[10px] text-muted-foreground">
|
|
395
|
-
Invited{inv.role === "admin" ? " · admin" : ""}
|
|
396
|
-
</span>
|
|
397
|
-
</div>
|
|
353
|
+
<div className="text-xs text-muted-foreground">
|
|
354
|
+
{t("org.memberCount", { count: members.length })} ·{" "}
|
|
355
|
+
{t("org.youAreRole", { role: org.role })}
|
|
398
356
|
</div>
|
|
399
|
-
))}
|
|
400
|
-
</div>
|
|
401
|
-
|
|
402
|
-
{isOwnerOrAdmin && (
|
|
403
|
-
<div className="border-t border-border pt-3">
|
|
404
|
-
{!showInviteForm ? (
|
|
405
|
-
<button
|
|
406
|
-
type="button"
|
|
407
|
-
onClick={() => setShowInviteForm(true)}
|
|
408
|
-
className="flex items-center gap-1.5 rounded-md border border-border px-2.5 py-1.5 text-xs font-medium hover:bg-accent/50"
|
|
409
|
-
>
|
|
410
|
-
<IconUserPlus className="h-3.5 w-3.5" />
|
|
411
|
-
Invite members
|
|
412
|
-
</button>
|
|
413
|
-
) : (
|
|
414
|
-
<BulkInviteForm
|
|
415
|
-
currentUserRole={org.role}
|
|
416
|
-
onClose={() => setShowInviteForm(false)}
|
|
417
|
-
/>
|
|
418
|
-
)}
|
|
419
357
|
</div>
|
|
358
|
+
</div>
|
|
359
|
+
{hasMultipleOrgs && (
|
|
360
|
+
<select
|
|
361
|
+
value={org.orgId ?? ""}
|
|
362
|
+
onChange={(e) => switchOrg.mutate(e.target.value || null)}
|
|
363
|
+
disabled={switchOrg.isPending}
|
|
364
|
+
className="w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-xs sm:w-auto"
|
|
365
|
+
>
|
|
366
|
+
{org.orgs.map((o) => (
|
|
367
|
+
<option key={o.orgId} value={o.orgId}>
|
|
368
|
+
{o.orgName}
|
|
369
|
+
</option>
|
|
370
|
+
))}
|
|
371
|
+
</select>
|
|
420
372
|
)}
|
|
421
373
|
</div>
|
|
422
374
|
|
|
423
375
|
{isOwnerOrAdmin && (
|
|
424
|
-
<div className="
|
|
376
|
+
<div className="border-t border-border pt-4">
|
|
377
|
+
{!showInviteForm ? (
|
|
378
|
+
<button
|
|
379
|
+
type="button"
|
|
380
|
+
onClick={() => setShowInviteForm(true)}
|
|
381
|
+
className="inline-flex items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs font-medium text-background hover:opacity-90"
|
|
382
|
+
>
|
|
383
|
+
<IconUserPlus className="h-3.5 w-3.5" />
|
|
384
|
+
{t("org.inviteMembers")}
|
|
385
|
+
</button>
|
|
386
|
+
) : (
|
|
387
|
+
<BulkInviteForm
|
|
388
|
+
currentUserRole={org.role}
|
|
389
|
+
onClose={() => setShowInviteForm(false)}
|
|
390
|
+
/>
|
|
391
|
+
)}
|
|
392
|
+
</div>
|
|
393
|
+
)}
|
|
394
|
+
|
|
395
|
+
{isOwnerOrAdmin && (
|
|
396
|
+
<div className="grid gap-5 border-t border-border pt-4 lg:grid-cols-2">
|
|
425
397
|
<DomainSettingsSection
|
|
426
398
|
domain={org.allowedDomain}
|
|
427
399
|
ownerEmail={org.email}
|
|
@@ -430,14 +402,134 @@ function MembersCard() {
|
|
|
430
402
|
{isOwner && <A2ASecretSection secret={org.a2aSecret} />}
|
|
431
403
|
</div>
|
|
432
404
|
)}
|
|
433
|
-
</div>
|
|
434
405
|
|
|
435
|
-
|
|
436
|
-
|
|
406
|
+
<ErrorText error={switchOrg.error} />
|
|
407
|
+
</section>
|
|
408
|
+
|
|
409
|
+
<MembersTableCard
|
|
410
|
+
members={members}
|
|
411
|
+
pendingInvites={pendingInvites}
|
|
412
|
+
isLoadingMembers={isLoadingMembers}
|
|
413
|
+
currentUserEmail={org.email}
|
|
414
|
+
currentUserRole={org.role ?? null}
|
|
415
|
+
/>
|
|
416
|
+
</div>
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function MembersTableCard({
|
|
421
|
+
members,
|
|
422
|
+
pendingInvites,
|
|
423
|
+
isLoadingMembers,
|
|
424
|
+
currentUserEmail,
|
|
425
|
+
currentUserRole,
|
|
426
|
+
}: {
|
|
427
|
+
members: MemberListItem[];
|
|
428
|
+
pendingInvites: PendingInviteListItem[];
|
|
429
|
+
isLoadingMembers: boolean;
|
|
430
|
+
currentUserEmail: string;
|
|
431
|
+
currentUserRole: string | null;
|
|
432
|
+
}) {
|
|
433
|
+
const t = useT();
|
|
434
|
+
|
|
435
|
+
return (
|
|
436
|
+
<section className="rounded-lg border border-border bg-card">
|
|
437
|
+
<div className="flex flex-col gap-1 border-b border-border px-4 py-3 sm:flex-row sm:items-center sm:justify-between">
|
|
438
|
+
<div>
|
|
439
|
+
<h3 className="text-sm font-medium">{t("org.members")}</h3>
|
|
440
|
+
<p className="text-xs text-muted-foreground">
|
|
441
|
+
{t("org.memberCount", { count: members.length })}
|
|
442
|
+
</p>
|
|
443
|
+
</div>
|
|
444
|
+
</div>
|
|
445
|
+
<Table>
|
|
446
|
+
<TableHeader>
|
|
447
|
+
<TableRow>
|
|
448
|
+
<TableHead>{t("org.member")}</TableHead>
|
|
449
|
+
<TableHead>{t("org.role")}</TableHead>
|
|
450
|
+
<TableHead>{t("org.status")}</TableHead>
|
|
451
|
+
<TableHead className="text-end">{t("org.actions")}</TableHead>
|
|
452
|
+
</TableRow>
|
|
453
|
+
</TableHeader>
|
|
454
|
+
<TableBody>
|
|
455
|
+
{isLoadingMembers && members.length === 0 ? (
|
|
456
|
+
[0, 1, 2].map((i) => (
|
|
457
|
+
<TableRow key={i}>
|
|
458
|
+
<TableCell colSpan={4}>
|
|
459
|
+
<div
|
|
460
|
+
className="h-3.5 rounded bg-muted animate-pulse"
|
|
461
|
+
style={{ width: `${180 + i * 48}px` }}
|
|
462
|
+
/>
|
|
463
|
+
</TableCell>
|
|
464
|
+
</TableRow>
|
|
465
|
+
))
|
|
466
|
+
) : members.length === 0 && pendingInvites.length === 0 ? (
|
|
467
|
+
<TableRow>
|
|
468
|
+
<TableCell
|
|
469
|
+
colSpan={4}
|
|
470
|
+
className="py-8 text-center text-sm text-muted-foreground"
|
|
471
|
+
>
|
|
472
|
+
{t("org.noMembers")}
|
|
473
|
+
</TableCell>
|
|
474
|
+
</TableRow>
|
|
475
|
+
) : (
|
|
476
|
+
<>
|
|
477
|
+
{members.map((m) => (
|
|
478
|
+
<MemberRow
|
|
479
|
+
key={m.email}
|
|
480
|
+
email={m.email}
|
|
481
|
+
role={m.role}
|
|
482
|
+
isCurrentUser={m.email === currentUserEmail}
|
|
483
|
+
currentUserRole={currentUserRole}
|
|
484
|
+
/>
|
|
485
|
+
))}
|
|
486
|
+
{pendingInvites.map((inv) => (
|
|
487
|
+
<PendingInviteRow key={inv.id} invite={inv} />
|
|
488
|
+
))}
|
|
489
|
+
</>
|
|
490
|
+
)}
|
|
491
|
+
</TableBody>
|
|
492
|
+
</Table>
|
|
437
493
|
</section>
|
|
438
494
|
);
|
|
439
495
|
}
|
|
440
496
|
|
|
497
|
+
function roleLabel(role: string, t: ReturnType<typeof useT>) {
|
|
498
|
+
if (role === "owner") return t("org.owner");
|
|
499
|
+
if (role === "admin") return t("org.admin");
|
|
500
|
+
return t("org.member");
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function RoleBadge({ role }: { role: string }) {
|
|
504
|
+
const t = useT();
|
|
505
|
+
return (
|
|
506
|
+
<span className="inline-flex items-center gap-1.5 rounded border border-border px-2 py-1 text-xs text-muted-foreground">
|
|
507
|
+
<RoleIcon role={role} />
|
|
508
|
+
{roleLabel(role, t)}
|
|
509
|
+
</span>
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function PendingInviteRow({ invite }: { invite: PendingInviteListItem }) {
|
|
514
|
+
const t = useT();
|
|
515
|
+
return (
|
|
516
|
+
<TableRow className="opacity-70">
|
|
517
|
+
<TableCell className="min-w-56">
|
|
518
|
+
<span className="truncate text-sm">{invite.email}</span>
|
|
519
|
+
</TableCell>
|
|
520
|
+
<TableCell>
|
|
521
|
+
<RoleBadge role={invite.role} />
|
|
522
|
+
</TableCell>
|
|
523
|
+
<TableCell>
|
|
524
|
+
<span className="rounded border border-border px-1.5 py-0.5 text-[10px] text-muted-foreground">
|
|
525
|
+
{t("org.invited")}
|
|
526
|
+
</span>
|
|
527
|
+
</TableCell>
|
|
528
|
+
<TableCell className="text-end text-muted-foreground">-</TableCell>
|
|
529
|
+
</TableRow>
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
441
533
|
function MemberRow({
|
|
442
534
|
email,
|
|
443
535
|
role,
|
|
@@ -465,99 +557,104 @@ function MemberRow({
|
|
|
465
557
|
(currentUserRole === "admin" && role === "member"));
|
|
466
558
|
|
|
467
559
|
return (
|
|
468
|
-
<
|
|
469
|
-
<
|
|
560
|
+
<TableRow>
|
|
561
|
+
<TableCell className="min-w-56">
|
|
470
562
|
<span className="truncate text-sm">{email}</span>
|
|
471
|
-
|
|
472
|
-
|
|
563
|
+
</TableCell>
|
|
564
|
+
<TableCell>
|
|
565
|
+
<RoleBadge role={role} />
|
|
566
|
+
</TableCell>
|
|
567
|
+
<TableCell>
|
|
568
|
+
{isCurrentUser ? (
|
|
473
569
|
<span className="rounded border border-border px-1.5 py-0.5 text-[10px] text-muted-foreground">
|
|
474
570
|
{t("org.you")}
|
|
475
571
|
</span>
|
|
572
|
+
) : (
|
|
573
|
+
<span className="text-muted-foreground">-</span>
|
|
476
574
|
)}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
<
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
575
|
+
</TableCell>
|
|
576
|
+
<TableCell>
|
|
577
|
+
{canManage ? (
|
|
578
|
+
<div className="flex shrink-0 items-center justify-end gap-1">
|
|
579
|
+
{editing ? (
|
|
580
|
+
<select
|
|
581
|
+
autoFocus
|
|
582
|
+
value={role}
|
|
583
|
+
onChange={(e) => {
|
|
584
|
+
const next = e.target.value === "admin" ? "admin" : "member";
|
|
585
|
+
if (next !== role) {
|
|
586
|
+
changeRole.mutate(
|
|
587
|
+
{ email, role: next },
|
|
588
|
+
{ onSuccess: () => setEditing(false) },
|
|
589
|
+
);
|
|
590
|
+
} else {
|
|
591
|
+
setEditing(false);
|
|
592
|
+
}
|
|
593
|
+
}}
|
|
594
|
+
onBlur={() => setEditing(false)}
|
|
595
|
+
disabled={changeRole.isPending}
|
|
596
|
+
className="rounded-md border border-border bg-background px-1.5 py-0.5 text-[11px]"
|
|
597
|
+
>
|
|
598
|
+
<option value="member">{t("org.member")}</option>
|
|
599
|
+
<option value="admin">{t("org.admin")}</option>
|
|
600
|
+
</select>
|
|
601
|
+
) : (
|
|
602
|
+
<Tooltip>
|
|
603
|
+
<TooltipTrigger asChild>
|
|
604
|
+
<button
|
|
605
|
+
type="button"
|
|
606
|
+
onClick={() => setEditing(true)}
|
|
607
|
+
className="text-muted-foreground hover:text-foreground"
|
|
608
|
+
>
|
|
609
|
+
<IconPencil className="h-3.5 w-3.5" />
|
|
610
|
+
</button>
|
|
611
|
+
</TooltipTrigger>
|
|
612
|
+
<TooltipContent>{t("org.changeRole")}</TooltipContent>
|
|
613
|
+
</Tooltip>
|
|
614
|
+
)}
|
|
615
|
+
{confirmingRemove ? (
|
|
616
|
+
<div className="flex items-center gap-1">
|
|
510
617
|
<button
|
|
511
618
|
type="button"
|
|
512
|
-
onClick={() =>
|
|
513
|
-
className="text-muted-foreground hover:text-foreground"
|
|
619
|
+
onClick={() => setConfirmingRemove(false)}
|
|
620
|
+
className="rounded px-1.5 py-0.5 text-[11px] text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
514
621
|
>
|
|
515
|
-
|
|
622
|
+
{t("org.cancel")}
|
|
516
623
|
</button>
|
|
517
|
-
</TooltipTrigger>
|
|
518
|
-
<TooltipContent>{t("org.changeRole")}</TooltipContent>
|
|
519
|
-
</Tooltip>
|
|
520
|
-
)}
|
|
521
|
-
{confirmingRemove ? (
|
|
522
|
-
<div className="flex items-center gap-1">
|
|
523
|
-
<button
|
|
524
|
-
type="button"
|
|
525
|
-
onClick={() => setConfirmingRemove(false)}
|
|
526
|
-
className="rounded px-1.5 py-0.5 text-[11px] text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
527
|
-
>
|
|
528
|
-
{t("org.cancel")}
|
|
529
|
-
</button>
|
|
530
|
-
<button
|
|
531
|
-
type="button"
|
|
532
|
-
disabled={removeMember.isPending}
|
|
533
|
-
onClick={() =>
|
|
534
|
-
removeMember.mutate(email, {
|
|
535
|
-
onSettled: () => setConfirmingRemove(false),
|
|
536
|
-
})
|
|
537
|
-
}
|
|
538
|
-
className="rounded bg-red-500 px-1.5 py-0.5 text-[11px] text-white hover:bg-red-600 disabled:opacity-50"
|
|
539
|
-
>
|
|
540
|
-
{t("org.remove")}
|
|
541
|
-
</button>
|
|
542
|
-
</div>
|
|
543
|
-
) : (
|
|
544
|
-
<Tooltip>
|
|
545
|
-
<TooltipTrigger asChild>
|
|
546
624
|
<button
|
|
547
625
|
type="button"
|
|
548
626
|
disabled={removeMember.isPending}
|
|
549
|
-
onClick={() =>
|
|
550
|
-
|
|
627
|
+
onClick={() =>
|
|
628
|
+
removeMember.mutate(email, {
|
|
629
|
+
onSettled: () => setConfirmingRemove(false),
|
|
630
|
+
})
|
|
631
|
+
}
|
|
632
|
+
className="rounded bg-red-500 px-1.5 py-0.5 text-[11px] text-white hover:bg-red-600 disabled:opacity-50"
|
|
551
633
|
>
|
|
552
|
-
|
|
634
|
+
{t("org.remove")}
|
|
553
635
|
</button>
|
|
554
|
-
</
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
636
|
+
</div>
|
|
637
|
+
) : (
|
|
638
|
+
<Tooltip>
|
|
639
|
+
<TooltipTrigger asChild>
|
|
640
|
+
<button
|
|
641
|
+
type="button"
|
|
642
|
+
disabled={removeMember.isPending}
|
|
643
|
+
onClick={() => setConfirmingRemove(true)}
|
|
644
|
+
className="text-muted-foreground hover:text-red-500 disabled:opacity-50"
|
|
645
|
+
>
|
|
646
|
+
<IconTrash className="h-3.5 w-3.5" />
|
|
647
|
+
</button>
|
|
648
|
+
</TooltipTrigger>
|
|
649
|
+
<TooltipContent>{t("org.removeMember")}</TooltipContent>
|
|
650
|
+
</Tooltip>
|
|
651
|
+
)}
|
|
652
|
+
</div>
|
|
653
|
+
) : (
|
|
654
|
+
<div className="text-end text-muted-foreground">-</div>
|
|
655
|
+
)}
|
|
656
|
+
</TableCell>
|
|
657
|
+
</TableRow>
|
|
561
658
|
);
|
|
562
659
|
}
|
|
563
660
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* <DemoModeSection /> — toggle that replaces names, emails,
|
|
3
|
-
* realistic fake data everywhere (UI + what the agent sees)
|
|
4
|
-
* IDs and structure so the app keeps working.
|
|
2
|
+
* <DemoModeSection /> — toggle that replaces contact/free-text names, emails,
|
|
3
|
+
* and numbers with realistic fake data everywhere (UI + what the agent sees)
|
|
4
|
+
* while preserving labels, IDs, and structure so the app keeps working.
|
|
5
5
|
*
|
|
6
6
|
* The toggle is stored in application_state under `demo-mode`
|
|
7
7
|
* (`{ enabled: boolean }`) and written via `PUT
|
|
@@ -87,9 +87,9 @@ export function DemoModeSection() {
|
|
|
87
87
|
Enable demo mode
|
|
88
88
|
</div>
|
|
89
89
|
<p className="text-[10px] text-muted-foreground mt-0.5">
|
|
90
|
-
Replace names, emails, and numbers with realistic
|
|
91
|
-
— in the UI and what the agent sees. IDs
|
|
92
|
-
so the app keeps working.
|
|
90
|
+
Replace contact/free-text names, emails, and numbers with realistic
|
|
91
|
+
fake data everywhere — in the UI and what the agent sees. Labels, IDs,
|
|
92
|
+
and structure are preserved so the app keeps working.
|
|
93
93
|
</p>
|
|
94
94
|
{forced && (
|
|
95
95
|
<p className="text-[10px] text-muted-foreground mt-0.5">
|
|
@@ -2891,7 +2891,7 @@ function SettingsPanelContent({
|
|
|
2891
2891
|
id={settingsSectionDomId("demo-mode")}
|
|
2892
2892
|
icon={<IconEyeOff size={14} />}
|
|
2893
2893
|
title="Demo mode"
|
|
2894
|
-
subtitle="Replace names, emails, and numbers with realistic fake data everywhere — in the UI and what the agent sees. IDs and structure are preserved so the app keeps working."
|
|
2894
|
+
subtitle="Replace contact/free-text names, emails, and numbers with realistic fake data everywhere — in the UI and what the agent sees. Labels, IDs, and structure are preserved so the app keeps working."
|
|
2895
2895
|
open={openSection === "demo-mode"}
|
|
2896
2896
|
onToggle={() => toggle("demo-mode")}
|
|
2897
2897
|
>
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
useCallback,
|
|
10
10
|
useEffect,
|
|
11
11
|
useMemo,
|
|
12
|
+
useRef,
|
|
12
13
|
useState,
|
|
13
14
|
type ComponentType,
|
|
14
15
|
type ReactNode,
|
|
@@ -151,6 +152,17 @@ function updateHashForTab(tabId: string) {
|
|
|
151
152
|
window.history.pushState(null, "", `${pathname}${search}${hash}`);
|
|
152
153
|
}
|
|
153
154
|
|
|
155
|
+
function isEditableElement(element: Element | null): boolean {
|
|
156
|
+
if (!(element instanceof HTMLElement)) return false;
|
|
157
|
+
const tagName = element.tagName.toLowerCase();
|
|
158
|
+
return (
|
|
159
|
+
tagName === "input" ||
|
|
160
|
+
tagName === "textarea" ||
|
|
161
|
+
tagName === "select" ||
|
|
162
|
+
element.isContentEditable
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
154
166
|
export function SettingsTabsPage({
|
|
155
167
|
general,
|
|
156
168
|
team,
|
|
@@ -171,6 +183,9 @@ export function SettingsTabsPage({
|
|
|
171
183
|
value,
|
|
172
184
|
onValueChange,
|
|
173
185
|
}: SettingsTabsPageProps) {
|
|
186
|
+
const rootRef = useRef<HTMLDivElement | null>(null);
|
|
187
|
+
const searchInputRef = useRef<HTMLInputElement | null>(null);
|
|
188
|
+
const autoFocusedSearchRef = useRef(false);
|
|
174
189
|
const tabs = useMemo<SettingsTabItem[]>(() => {
|
|
175
190
|
const hasOrganizationTab = extraTabs.some(
|
|
176
191
|
(tab) => tab.id === "organization",
|
|
@@ -254,6 +269,37 @@ export function SettingsTabsPage({
|
|
|
254
269
|
return () => window.removeEventListener("hashchange", handleHashChange);
|
|
255
270
|
}, [isControlled, tabs]);
|
|
256
271
|
|
|
272
|
+
useEffect(() => {
|
|
273
|
+
if (!enableSearch || autoFocusedSearchRef.current) return;
|
|
274
|
+
if (
|
|
275
|
+
typeof window === "undefined" ||
|
|
276
|
+
typeof window.matchMedia !== "function"
|
|
277
|
+
) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (window.matchMedia("(max-width: 767px)").matches) return;
|
|
281
|
+
|
|
282
|
+
const frame = window.requestAnimationFrame(() => {
|
|
283
|
+
autoFocusedSearchRef.current = true;
|
|
284
|
+
const input = searchInputRef.current;
|
|
285
|
+
if (!input) return;
|
|
286
|
+
|
|
287
|
+
const activeElement = document.activeElement;
|
|
288
|
+
const activeInsideSettings =
|
|
289
|
+
!!activeElement && rootRef.current?.contains(activeElement);
|
|
290
|
+
if (
|
|
291
|
+
activeElement !== input &&
|
|
292
|
+
(isEditableElement(activeElement) || activeInsideSettings)
|
|
293
|
+
) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
input.focus({ preventScroll: true });
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
return () => window.cancelAnimationFrame(frame);
|
|
301
|
+
}, [enableSearch]);
|
|
302
|
+
|
|
257
303
|
const selectedTab = tabs.find((tab) => tab.id === activeTab) ?? tabs[0];
|
|
258
304
|
|
|
259
305
|
// Flatten tab + deep-link entries into one searchable index.
|
|
@@ -338,6 +384,7 @@ export function SettingsTabsPage({
|
|
|
338
384
|
|
|
339
385
|
return (
|
|
340
386
|
<div
|
|
387
|
+
ref={rootRef}
|
|
341
388
|
className={cn(
|
|
342
389
|
"flex h-full min-h-0 w-full flex-col overflow-hidden bg-background sm:flex-row",
|
|
343
390
|
className,
|
|
@@ -345,7 +392,7 @@ export function SettingsTabsPage({
|
|
|
345
392
|
>
|
|
346
393
|
<div
|
|
347
394
|
className={cn(
|
|
348
|
-
"flex shrink-0 flex-col gap-2
|
|
395
|
+
"flex shrink-0 flex-col gap-2 bg-background p-2 sm:min-h-0 sm:w-56 sm:overflow-y-auto sm:p-3",
|
|
349
396
|
navClassName,
|
|
350
397
|
)}
|
|
351
398
|
>
|
|
@@ -353,6 +400,7 @@ export function SettingsTabsPage({
|
|
|
353
400
|
<div className="relative sm:mb-1">
|
|
354
401
|
<IconSearch className="pointer-events-none absolute start-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" />
|
|
355
402
|
<input
|
|
403
|
+
ref={searchInputRef}
|
|
356
404
|
type="search"
|
|
357
405
|
value={query}
|
|
358
406
|
onChange={(event) => setQuery(event.target.value)}
|
|
@@ -5,7 +5,7 @@ import { writeAppState } from "../../application-state/script-helpers.js";
|
|
|
5
5
|
|
|
6
6
|
export default defineAction({
|
|
7
7
|
description:
|
|
8
|
-
"Turn demo mode on or off. When demo mode is on, the app replaces
|
|
8
|
+
"Turn demo mode on or off. When demo mode is on, the app replaces contact/free-text names, email addresses, and numbers with realistic fake data everywhere — in the UI and in what you (the agent) see — while keeping labels, IDs, dates, and structure intact so everything still works. Use when the user asks to 'hide my data', 'turn on demo mode', 'anonymize this for a screen share / recording', or similar. This is the same toggle as the Demo mode switch in settings.",
|
|
9
9
|
schema: z.object({
|
|
10
10
|
enabled: z
|
|
11
11
|
.boolean()
|
|
@@ -16,7 +16,7 @@ export default defineAction({
|
|
|
16
16
|
return {
|
|
17
17
|
enabled,
|
|
18
18
|
message: enabled
|
|
19
|
-
? "Demo mode is ON —
|
|
19
|
+
? "Demo mode is ON — contact/free-text names, emails, and numbers are now replaced with deterministic fake data everywhere (UI and agent). Labels, IDs, and structure are preserved so everything keeps working."
|
|
20
20
|
: "Demo mode is OFF — real data is shown again.",
|
|
21
21
|
};
|
|
22
22
|
},
|