@dxos/plugin-client 0.9.0 → 0.9.1-staging.ee54ba693a
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/dist/lib/neutral/{AccountContainer-FZ3IC6H4.mjs → AccountContainer-7SF53UON.mjs} +17 -13
- package/dist/lib/neutral/AccountContainer-7SF53UON.mjs.map +7 -0
- package/dist/lib/neutral/ClientPlugin.node.mjs +477 -370
- package/dist/lib/neutral/ClientPlugin.node.mjs.map +4 -4
- package/dist/lib/neutral/{DevicesContainer-6MWNUC3I.mjs → DevicesContainer-PRWIN36N.mjs} +36 -27
- package/dist/lib/neutral/DevicesContainer-PRWIN36N.mjs.map +7 -0
- package/dist/lib/neutral/{InvitationsContainer-ME722VKS.mjs → InvitationsContainer-BI5RAWET.mjs} +43 -32
- package/dist/lib/neutral/InvitationsContainer-BI5RAWET.mjs.map +7 -0
- package/dist/lib/neutral/{JoinDialog-2NXAKPLP.mjs → JoinDialog-4BWV4CYB.mjs} +2 -2
- package/dist/lib/neutral/JoinDialog-4BWV4CYB.mjs.map +7 -0
- package/dist/lib/neutral/{ProfileContainer-VPX3FPWG.mjs → ProfileContainer-YEAYOTX5.mjs} +22 -21
- package/dist/lib/neutral/ProfileContainer-YEAYOTX5.mjs.map +7 -0
- package/dist/lib/neutral/{RecoveryCodeDialog-EWHB6U5T.mjs → RecoveryCodeDialog-7EPNKCLB.mjs} +2 -2
- package/dist/lib/neutral/RecoveryCodeDialog-7EPNKCLB.mjs.map +7 -0
- package/dist/lib/neutral/{RecoveryCredentialsContainer-IPBGDMIA.mjs → RecoveryCredentialsContainer-6BZ2IWX7.mjs} +24 -15
- package/dist/lib/neutral/RecoveryCredentialsContainer-6BZ2IWX7.mjs.map +7 -0
- package/dist/lib/neutral/UsageContainer-2DWCVVE4.mjs +241 -0
- package/dist/lib/neutral/UsageContainer-2DWCVVE4.mjs.map +7 -0
- package/dist/lib/neutral/account-cache-YVQKLNV2.mjs +16 -0
- package/dist/lib/neutral/account-cache-YVQKLNV2.mjs.map +7 -0
- package/dist/lib/neutral/{app-graph-builder-A74W7C36.mjs → app-graph-builder-RVXTD6OE.mjs} +30 -16
- package/dist/lib/neutral/app-graph-builder-RVXTD6OE.mjs.map +7 -0
- package/dist/lib/neutral/capabilities/index.mjs +6 -6
- package/dist/lib/neutral/capabilities/node.mjs +3 -3
- package/dist/lib/neutral/chunk-6G4KZLBG.mjs +28 -0
- package/dist/lib/neutral/chunk-6G4KZLBG.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-D6HPCLTS.mjs → chunk-7DEIKHB7.mjs} +16 -2
- package/dist/lib/neutral/chunk-7DEIKHB7.mjs.map +7 -0
- package/dist/lib/neutral/chunk-A7WDDIWY.mjs +162 -0
- package/dist/lib/neutral/chunk-A7WDDIWY.mjs.map +7 -0
- package/dist/lib/neutral/chunk-GSKMAS3S.mjs +13 -0
- package/dist/lib/neutral/chunk-GSKMAS3S.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-JEF2FQGB.mjs → chunk-JY35S2CN.mjs} +2 -2
- package/dist/lib/neutral/{chunk-JEF2FQGB.mjs.map → chunk-JY35S2CN.mjs.map} +1 -1
- package/dist/lib/neutral/chunk-ZFABGG2E.mjs +8 -0
- package/dist/lib/neutral/{chunk-NTVDGDQ3.mjs.map → chunk-ZFABGG2E.mjs.map} +2 -2
- package/dist/lib/neutral/containers/index.mjs +10 -8
- package/dist/lib/neutral/containers/index.mjs.map +3 -3
- package/dist/lib/neutral/{create-agent-6HLW4D3A.mjs → create-agent-6UKJMZS5.mjs} +3 -3
- package/dist/lib/neutral/{create-identity-JIWAR5GW.mjs → create-identity-ULCRB3YH.mjs} +3 -3
- package/dist/lib/neutral/{create-passkey-FOXEMXGF.mjs → create-passkey-I7VDS3AC.mjs} +7 -7
- package/dist/lib/neutral/create-passkey-I7VDS3AC.mjs.map +7 -0
- package/dist/lib/neutral/{create-recovery-code-ZWDKLPH4.mjs → create-recovery-code-DH2B6EGP.mjs} +4 -4
- package/dist/lib/neutral/index.mjs +6 -4
- package/dist/lib/neutral/{join-identity-QEQFAE2S.mjs → join-identity-PJCHDNLH.mjs} +3 -3
- package/dist/lib/neutral/{layer-specs-CQLXQBQJ.mjs → layer-specs-YI7BVWKZ.mjs} +6 -8
- package/dist/lib/neutral/layer-specs-YI7BVWKZ.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/lib/neutral/meta.mjs +1 -1
- package/dist/lib/neutral/{navigation-handler-LU26CDFR.mjs → navigation-handler-MQ6MZXLX.mjs} +3 -3
- package/dist/lib/neutral/open-usage-OJVTPGOY.mjs +27 -0
- package/dist/lib/neutral/open-usage-OJVTPGOY.mjs.map +7 -0
- package/dist/lib/neutral/operations/index.mjs +2 -2
- package/dist/lib/neutral/plugin.mjs +3 -3
- package/dist/lib/neutral/{react-context-B5LXMHVM.mjs → react-context-V74T6JOJ.mjs} +2 -2
- package/dist/lib/neutral/{react-context-B5LXMHVM.mjs.map → react-context-V74T6JOJ.mjs.map} +3 -3
- package/dist/lib/neutral/{react-surface-DEKGWTCD.mjs → react-surface-HPXRA65U.mjs} +8 -3
- package/dist/lib/neutral/react-surface-HPXRA65U.mjs.map +7 -0
- package/dist/lib/neutral/{recover-identity-JNFKLCUD.mjs → recover-identity-B7YM2BR4.mjs} +3 -3
- package/dist/lib/neutral/{redeem-passkey-4SBAI4AG.mjs → redeem-passkey-TYP4XLHO.mjs} +10 -10
- package/dist/lib/neutral/redeem-passkey-TYP4XLHO.mjs.map +7 -0
- package/dist/lib/neutral/{redeem-token-POBD6TMN.mjs → redeem-token-2J33R3PV.mjs} +3 -3
- package/dist/lib/neutral/{reset-storage-LQKUV5RA.mjs → reset-storage-K47HZTGP.mjs} +3 -3
- package/dist/lib/neutral/{share-identity-KNQ2XNXV.mjs → share-identity-TOROBLWL.mjs} +6 -6
- package/dist/lib/neutral/share-identity-TOROBLWL.mjs.map +7 -0
- package/dist/lib/neutral/testing/index.mjs +2 -2
- package/dist/lib/neutral/testing/index.mjs.map +3 -3
- package/dist/lib/neutral/translations.mjs +24 -1
- package/dist/lib/neutral/translations.mjs.map +3 -3
- package/dist/lib/neutral/types/index.mjs +3 -1
- package/dist/types/dx.config.d.ts +28 -0
- package/dist/types/dx.config.d.ts.map +1 -0
- package/dist/types/src/capabilities/account-cache.d.ts +1 -31
- package/dist/types/src/capabilities/app-graph-builder.d.ts +1 -2
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
- package/dist/types/src/capabilities/client.d.ts +1 -2
- package/dist/types/src/capabilities/client.d.ts.map +1 -1
- package/dist/types/src/capabilities/hub-http-client.d.ts +1 -2
- package/dist/types/src/capabilities/hub-http-client.d.ts.map +1 -1
- package/dist/types/src/capabilities/index.d.ts +9 -43
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/layer-specs.d.ts +1 -2
- package/dist/types/src/capabilities/layer-specs.d.ts.map +1 -1
- package/dist/types/src/capabilities/migrations.d.ts +1 -1
- package/dist/types/src/capabilities/navigation-handler/index.d.ts +1 -1
- package/dist/types/src/capabilities/navigation-handler/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/navigation-handler/navigation-handler.d.ts +1 -2
- package/dist/types/src/capabilities/navigation-handler/navigation-handler.d.ts.map +1 -1
- package/dist/types/src/capabilities/node.d.ts +6 -6
- package/dist/types/src/capabilities/node.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-context.d.ts +1 -6
- package/dist/types/src/capabilities/react-context.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts +2 -2
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
- package/dist/types/src/capabilities/schema-defs.d.ts +1 -1
- package/dist/types/src/commands/account/index.d.ts +3 -0
- package/dist/types/src/commands/account/index.d.ts.map +1 -0
- package/dist/types/src/commands/account/login/index.d.ts +2 -0
- package/dist/types/src/commands/account/login/index.d.ts.map +1 -0
- package/dist/types/src/commands/account/login/login.d.ts +12 -0
- package/dist/types/src/commands/account/login/login.d.ts.map +1 -0
- package/dist/types/src/commands/account/logout/index.d.ts +2 -0
- package/dist/types/src/commands/account/logout/index.d.ts.map +1 -0
- package/dist/types/src/commands/account/logout/logout.d.ts +9 -0
- package/dist/types/src/commands/account/logout/logout.d.ts.map +1 -0
- package/dist/types/src/commands/halo/join/join.d.ts +1 -1
- package/dist/types/src/commands/halo/join/join.d.ts.map +1 -1
- package/dist/types/src/commands/halo/util.d.ts +4 -0
- package/dist/types/src/commands/halo/util.d.ts.map +1 -1
- package/dist/types/src/commands/index.d.ts +1 -0
- package/dist/types/src/commands/index.d.ts.map +1 -1
- package/dist/types/src/constants.d.ts.map +1 -1
- package/dist/types/src/containers/AccountContainer/AccountContainer.d.ts.map +1 -1
- package/dist/types/src/containers/AccountContainer/AccountContainer.stories.d.ts +14 -0
- package/dist/types/src/containers/AccountContainer/AccountContainer.stories.d.ts.map +1 -0
- package/dist/types/src/containers/DevicesContainer/DevicesContainer.d.ts.map +1 -1
- package/dist/types/src/containers/InvitationsContainer/InvitationsContainer.d.ts.map +1 -1
- package/dist/types/src/containers/InvitationsContainer/InvitationsContainer.stories.d.ts +14 -0
- package/dist/types/src/containers/InvitationsContainer/InvitationsContainer.stories.d.ts.map +1 -0
- package/dist/types/src/containers/ProfileContainer/ProfileContainer.d.ts.map +1 -1
- package/dist/types/src/containers/RecoveryCredentialsContainer/RecoveryCredentialsContainer.d.ts.map +1 -1
- package/dist/types/src/containers/UsageContainer/UsageContainer.d.ts +7 -0
- package/dist/types/src/containers/UsageContainer/UsageContainer.d.ts.map +1 -0
- package/dist/types/src/containers/UsageContainer/UsageView.d.ts +28 -0
- package/dist/types/src/containers/UsageContainer/UsageView.d.ts.map +1 -0
- package/dist/types/src/containers/UsageContainer/UsageView.stories.d.ts +21 -0
- package/dist/types/src/containers/UsageContainer/UsageView.stories.d.ts.map +1 -0
- package/dist/types/src/containers/UsageContainer/index.d.ts +2 -0
- package/dist/types/src/containers/UsageContainer/index.d.ts.map +1 -0
- package/dist/types/src/containers/index.d.ts +1 -0
- package/dist/types/src/containers/index.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -0
- package/dist/types/src/{state/use-hub-http.d.ts → hooks/useHubClient.d.ts} +1 -1
- package/dist/types/src/hooks/useHubClient.d.ts.map +1 -0
- package/dist/types/src/meta.d.ts +32 -2
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/operations/create-passkey.d.ts.map +1 -1
- package/dist/types/src/operations/definitions.d.ts +1 -0
- package/dist/types/src/operations/definitions.d.ts.map +1 -1
- package/dist/types/src/operations/index.d.ts.map +1 -1
- package/dist/types/src/operations/open-usage.d.ts +5 -0
- package/dist/types/src/operations/open-usage.d.ts.map +1 -0
- package/dist/types/src/testing/initializeIdentity.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/{state → types}/account-cache.d.ts.map +1 -1
- package/dist/types/src/types/capabilities.d.ts.map +1 -1
- package/dist/types/src/types/events.d.ts.map +1 -1
- package/dist/types/src/types/index.d.ts +1 -0
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/types/schema.d.ts +1 -0
- package/dist/types/src/types/schema.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dx.config.ts +19 -0
- package/package.json +47 -44
- package/src/ClientPlugin.node.ts +2 -2
- package/src/ClientPlugin.test.ts +1 -1
- package/src/capabilities/account-cache.ts +1 -3
- package/src/capabilities/app-graph-builder.ts +81 -71
- package/src/capabilities/layer-specs.ts +4 -6
- package/src/capabilities/react-context.tsx +1 -1
- package/src/capabilities/react-surface.tsx +6 -0
- package/src/commands/account/index.ts +13 -0
- package/src/commands/account/login/index.ts +5 -0
- package/src/commands/account/login/login.ts +147 -0
- package/src/commands/account/logout/index.ts +5 -0
- package/src/commands/account/logout/logout.ts +49 -0
- package/src/commands/halo/create/create.ts +2 -2
- package/src/commands/halo/index.ts +6 -6
- package/src/commands/halo/join/join.ts +2 -13
- package/src/commands/halo/util.ts +24 -0
- package/src/commands/index.ts +1 -0
- package/src/constants.ts +3 -3
- package/src/containers/AccountContainer/AccountContainer.stories.tsx +46 -0
- package/src/containers/AccountContainer/AccountContainer.tsx +67 -63
- package/src/containers/DevicesContainer/DevicesContainer.tsx +72 -54
- package/src/containers/InvitationsContainer/InvitationsContainer.stories.tsx +46 -0
- package/src/containers/InvitationsContainer/InvitationsContainer.tsx +65 -62
- package/src/containers/JoinDialog/JoinDialog.tsx +1 -1
- package/src/containers/ProfileContainer/ProfileContainer.tsx +38 -28
- package/src/containers/RecoveryCodeDialog/RecoveryCodeDialog.tsx +1 -1
- package/src/containers/RecoveryCredentialsContainer/RecoveryCredentialsContainer.tsx +52 -43
- package/src/containers/UsageContainer/UsageContainer.tsx +48 -0
- package/src/containers/UsageContainer/UsageView.stories.tsx +127 -0
- package/src/containers/UsageContainer/UsageView.tsx +270 -0
- package/src/containers/UsageContainer/index.ts +5 -0
- package/src/containers/index.ts +1 -0
- package/src/hooks/index.ts +5 -0
- package/src/meta.ts +3 -12
- package/src/operations/create-passkey.ts +6 -4
- package/src/operations/definitions.ts +12 -1
- package/src/operations/index.ts +1 -0
- package/src/operations/open-usage.ts +24 -0
- package/src/operations/redeem-passkey.ts +7 -7
- package/src/operations/share-identity.ts +3 -3
- package/src/testing/initializeIdentity.ts +5 -2
- package/src/translations.ts +25 -1
- package/src/types/capabilities.ts +8 -6
- package/src/types/events.ts +5 -5
- package/src/types/index.ts +1 -0
- package/src/types/schema.ts +23 -16
- package/dist/lib/neutral/AccountContainer-FZ3IC6H4.mjs.map +0 -7
- package/dist/lib/neutral/DevicesContainer-6MWNUC3I.mjs.map +0 -7
- package/dist/lib/neutral/InvitationsContainer-ME722VKS.mjs.map +0 -7
- package/dist/lib/neutral/JoinDialog-2NXAKPLP.mjs.map +0 -7
- package/dist/lib/neutral/ProfileContainer-VPX3FPWG.mjs.map +0 -7
- package/dist/lib/neutral/RecoveryCodeDialog-EWHB6U5T.mjs.map +0 -7
- package/dist/lib/neutral/RecoveryCredentialsContainer-IPBGDMIA.mjs.map +0 -7
- package/dist/lib/neutral/account-cache-4EKGEUJI.mjs +0 -43
- package/dist/lib/neutral/account-cache-4EKGEUJI.mjs.map +0 -7
- package/dist/lib/neutral/app-graph-builder-A74W7C36.mjs.map +0 -7
- package/dist/lib/neutral/chunk-D6HPCLTS.mjs.map +0 -7
- package/dist/lib/neutral/chunk-JOCCVODY.mjs +0 -21
- package/dist/lib/neutral/chunk-JOCCVODY.mjs.map +0 -7
- package/dist/lib/neutral/chunk-NTVDGDQ3.mjs +0 -8
- package/dist/lib/neutral/chunk-TUK3ELUU.mjs +0 -135
- package/dist/lib/neutral/chunk-TUK3ELUU.mjs.map +0 -7
- package/dist/lib/neutral/chunk-WWCVZG3K.mjs +0 -13
- package/dist/lib/neutral/chunk-WWCVZG3K.mjs.map +0 -7
- package/dist/lib/neutral/create-passkey-FOXEMXGF.mjs.map +0 -7
- package/dist/lib/neutral/layer-specs-CQLXQBQJ.mjs.map +0 -7
- package/dist/lib/neutral/react-surface-DEKGWTCD.mjs.map +0 -7
- package/dist/lib/neutral/redeem-passkey-4SBAI4AG.mjs.map +0 -7
- package/dist/lib/neutral/share-identity-KNQ2XNXV.mjs.map +0 -7
- package/dist/types/src/state/use-hub-http.d.ts.map +0 -1
- /package/dist/lib/neutral/{create-agent-6HLW4D3A.mjs.map → create-agent-6UKJMZS5.mjs.map} +0 -0
- /package/dist/lib/neutral/{create-identity-JIWAR5GW.mjs.map → create-identity-ULCRB3YH.mjs.map} +0 -0
- /package/dist/lib/neutral/{create-recovery-code-ZWDKLPH4.mjs.map → create-recovery-code-DH2B6EGP.mjs.map} +0 -0
- /package/dist/lib/neutral/{join-identity-QEQFAE2S.mjs.map → join-identity-PJCHDNLH.mjs.map} +0 -0
- /package/dist/lib/neutral/{navigation-handler-LU26CDFR.mjs.map → navigation-handler-MQ6MZXLX.mjs.map} +0 -0
- /package/dist/lib/neutral/{recover-identity-JNFKLCUD.mjs.map → recover-identity-B7YM2BR4.mjs.map} +0 -0
- /package/dist/lib/neutral/{redeem-token-POBD6TMN.mjs.map → redeem-token-2J33R3PV.mjs.map} +0 -0
- /package/dist/lib/neutral/{reset-storage-LQKUV5RA.mjs.map → reset-storage-K47HZTGP.mjs.map} +0 -0
- /package/dist/types/src/{state → types}/account-cache.d.ts +0 -0
- /package/src/{state/use-hub-http.ts → hooks/useHubClient.ts} +0 -0
- /package/src/{state → types}/account-cache.ts +0 -0
|
@@ -7,44 +7,45 @@ import React, { useCallback, useState } from 'react';
|
|
|
7
7
|
|
|
8
8
|
import { useCapability } from '@dxos/app-framework/ui';
|
|
9
9
|
import { Context } from '@dxos/context';
|
|
10
|
-
import { Clipboard, Icon, IconButton,
|
|
11
|
-
import {
|
|
10
|
+
import { Clipboard, Icon, IconButton, useAsyncEffect, useTranslation } from '@dxos/react-ui';
|
|
11
|
+
import { Form } from '@dxos/react-ui-form';
|
|
12
|
+
import { Listbox } from '@dxos/react-ui-list';
|
|
12
13
|
|
|
13
14
|
import { meta } from '#meta';
|
|
14
|
-
import { ClientCapabilities } from '#types';
|
|
15
|
+
import { type AccountCacheInvitation, ClientCapabilities } from '#types';
|
|
15
16
|
|
|
16
|
-
import {
|
|
17
|
-
import { useHubHttpClient } from '../../state/use-hub-http';
|
|
17
|
+
import { useHubHttpClient } from '../../hooks';
|
|
18
18
|
|
|
19
19
|
export const InvitationsContainer = () => {
|
|
20
|
-
const { t } = useTranslation(meta.
|
|
20
|
+
const { t } = useTranslation(meta.profile.key);
|
|
21
21
|
const accountCacheAtom = useCapability(ClientCapabilities.AccountCache);
|
|
22
22
|
const [cache] = useAtom(accountCacheAtom);
|
|
23
23
|
const setCache = useAtomSet(accountCacheAtom);
|
|
24
24
|
const [pending, setPending] = useState(false);
|
|
25
25
|
|
|
26
26
|
// Account/invitation routes live on hub-service, not the edge worker.
|
|
27
|
-
const
|
|
27
|
+
const hubClient = useHubHttpClient();
|
|
28
28
|
|
|
29
29
|
useAsyncEffect(async () => {
|
|
30
|
-
if (!
|
|
30
|
+
if (!hubClient) {
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
|
+
|
|
33
34
|
try {
|
|
34
|
-
const result = await
|
|
35
|
+
const result = await hubClient.listAccountInvitations(new Context());
|
|
35
36
|
setCache((prev) => ({ ...prev, invitations: result.invitations, fetchedAt: Date.now() }));
|
|
36
37
|
} catch {
|
|
37
38
|
// Offline: keep cache.
|
|
38
39
|
}
|
|
39
|
-
}, [
|
|
40
|
+
}, [hubClient, setCache]);
|
|
40
41
|
|
|
41
42
|
const handleIssue = useCallback(async () => {
|
|
42
|
-
if (!
|
|
43
|
+
if (!hubClient) {
|
|
43
44
|
return;
|
|
44
45
|
}
|
|
45
46
|
setPending(true);
|
|
46
47
|
try {
|
|
47
|
-
const result = await
|
|
48
|
+
const result = await hubClient.issueAccountInvitation(new Context());
|
|
48
49
|
// Optimistically push the new code and decrement the remaining quota; the
|
|
49
50
|
// server consumes one slot at issue time. Next refresh reconciles.
|
|
50
51
|
setCache((prev) => ({
|
|
@@ -57,7 +58,7 @@ export const InvitationsContainer = () => {
|
|
|
57
58
|
} finally {
|
|
58
59
|
setPending(false);
|
|
59
60
|
}
|
|
60
|
-
}, [
|
|
61
|
+
}, [hubClient, setCache]);
|
|
61
62
|
|
|
62
63
|
const remaining = cache.account?.invitationsRemaining ?? 0;
|
|
63
64
|
const list = cache.invitations ?? [];
|
|
@@ -66,72 +67,74 @@ export const InvitationsContainer = () => {
|
|
|
66
67
|
|
|
67
68
|
return (
|
|
68
69
|
<Clipboard.Provider>
|
|
69
|
-
<
|
|
70
|
-
<
|
|
71
|
-
<
|
|
72
|
-
title={t('
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
70
|
+
<Form.Root variant='settings'>
|
|
71
|
+
<Form.Viewport scroll>
|
|
72
|
+
<Form.Content>
|
|
73
|
+
<Form.Section title={t('invitations-section.title')} description={t('invitations-section.description')}>
|
|
74
|
+
<Form.Row
|
|
75
|
+
label={t('generate-invitation.label')}
|
|
76
|
+
description={t('generate-invitation.description', { count: remaining })}
|
|
77
|
+
>
|
|
78
|
+
<IconButton
|
|
79
|
+
icon='ph--plus--regular'
|
|
80
|
+
label={t('generate-invitation.label')}
|
|
81
|
+
variant='primary'
|
|
82
|
+
onClick={handleIssue}
|
|
83
|
+
disabled={pending || remaining <= 0}
|
|
84
|
+
/>
|
|
85
|
+
</Form.Row>
|
|
86
|
+
</Form.Section>
|
|
84
87
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
{available.length > 0 ? (
|
|
89
|
+
<Form.Section title={t('available-invitations.title')}>
|
|
90
|
+
<Listbox.Root>
|
|
91
|
+
<Listbox.Content classNames='gap-1'>
|
|
92
|
+
{available.map((row) => (
|
|
93
|
+
<AvailableInvitationItem key={row.code} row={row} />
|
|
94
|
+
))}
|
|
95
|
+
</Listbox.Content>
|
|
96
|
+
</Listbox.Root>
|
|
97
|
+
</Form.Section>
|
|
98
|
+
) : null}
|
|
94
99
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
{redeemed.length > 0 ? (
|
|
101
|
+
<Form.Section title={t('redeemed-invitations.title')}>
|
|
102
|
+
<Listbox.Root>
|
|
103
|
+
<Listbox.Content classNames='gap-1'>
|
|
104
|
+
{redeemed.map((row) => (
|
|
105
|
+
<RedeemedInvitationItem key={row.code} row={row} />
|
|
106
|
+
))}
|
|
107
|
+
</Listbox.Content>
|
|
108
|
+
</Listbox.Root>
|
|
109
|
+
</Form.Section>
|
|
110
|
+
) : null}
|
|
111
|
+
</Form.Content>
|
|
112
|
+
</Form.Viewport>
|
|
113
|
+
</Form.Root>
|
|
105
114
|
</Clipboard.Provider>
|
|
106
115
|
);
|
|
107
116
|
};
|
|
108
117
|
|
|
109
118
|
const AvailableInvitationItem = ({ row }: { row: AccountCacheInvitation }) => (
|
|
110
|
-
<
|
|
111
|
-
<
|
|
112
|
-
<Icon icon='ph--paper-plane-tilt--duotone' size={5} classNames='text-description' />
|
|
113
|
-
</ListItem.Endcap>
|
|
119
|
+
<Listbox.Item id={row.code} classNames='grid grid-cols-[min-content_1fr_min-content] items-center gap-2'>
|
|
120
|
+
<Icon icon='ph--paper-plane-tilt--duotone' size={5} classNames='text-description' />
|
|
114
121
|
<div className='flex flex-col min-w-0'>
|
|
115
|
-
<
|
|
122
|
+
<div className='font-mono truncate'>{row.code}</div>
|
|
116
123
|
<p className='text-description text-xs'>{new Date(row.createdAt).toLocaleString()}</p>
|
|
117
124
|
</div>
|
|
118
|
-
<
|
|
119
|
-
|
|
120
|
-
</ListItem.Endcap>
|
|
121
|
-
</ListItem.Root>
|
|
125
|
+
<Clipboard.IconButton value={row.code} />
|
|
126
|
+
</Listbox.Item>
|
|
122
127
|
);
|
|
123
128
|
|
|
124
129
|
const RedeemedInvitationItem = ({ row }: { row: AccountCacheInvitation }) => {
|
|
125
130
|
const date = row.redeemedAt ?? row.createdAt;
|
|
126
131
|
return (
|
|
127
|
-
<
|
|
128
|
-
<
|
|
129
|
-
<Icon icon='ph--check-circle--duotone' size={5} classNames='text-success-text' />
|
|
130
|
-
</ListItem.Endcap>
|
|
132
|
+
<Listbox.Item id={row.code} classNames='grid grid-cols-[min-content_1fr] items-center gap-2'>
|
|
133
|
+
<Icon icon='ph--check-circle--duotone' size={5} classNames='text-success-text' />
|
|
131
134
|
<div className='flex flex-col min-w-0'>
|
|
132
|
-
<
|
|
135
|
+
<div className='font-mono truncate'>{row.code}</div>
|
|
133
136
|
<p className='text-description text-xs'>{new Date(date).toLocaleString()}</p>
|
|
134
137
|
</div>
|
|
135
|
-
</
|
|
138
|
+
</Listbox.Item>
|
|
136
139
|
);
|
|
137
140
|
};
|
|
@@ -17,7 +17,7 @@ import { ClientOperation } from '#operations';
|
|
|
17
17
|
|
|
18
18
|
export const JoinDialog = (props: JoinPanelProps) => {
|
|
19
19
|
const { invokePromise } = useOperationInvoker();
|
|
20
|
-
const { t } = useTranslation(meta.
|
|
20
|
+
const { t } = useTranslation(meta.profile.key);
|
|
21
21
|
|
|
22
22
|
const handleCancelResetStorage = useCallback(() => invokePromise(ClientOperation.ShareIdentity), [invokePromise]);
|
|
23
23
|
|
|
@@ -9,7 +9,7 @@ import { debounce } from '@dxos/async';
|
|
|
9
9
|
import { useClient } from '@dxos/react-client';
|
|
10
10
|
import { type Identity, useIdentity } from '@dxos/react-client/halo';
|
|
11
11
|
import { ButtonGroup, Clipboard, Input, useTranslation } from '@dxos/react-ui';
|
|
12
|
-
import { Form, type FormFieldMap, type FormUpdateMeta
|
|
12
|
+
import { Form, type FormFieldMap, type FormUpdateMeta } from '@dxos/react-ui-form';
|
|
13
13
|
import { EmojiPickerBlock, HuePicker } from '@dxos/react-ui-pickers';
|
|
14
14
|
import { hexToEmoji, hexToHue } from '@dxos/util';
|
|
15
15
|
|
|
@@ -33,7 +33,7 @@ const getDefaultEmojiValue = (identity: Identity | null) => hexToEmoji(identity?
|
|
|
33
33
|
const getEmojiValue = (identity: Identity | null) => identity?.profile?.data?.emoji || getDefaultEmojiValue(identity);
|
|
34
34
|
|
|
35
35
|
export const ProfileContainer = () => {
|
|
36
|
-
const { t } = useTranslation(meta.
|
|
36
|
+
const { t } = useTranslation(meta.profile.key);
|
|
37
37
|
const client = useClient();
|
|
38
38
|
const identity = useIdentity();
|
|
39
39
|
const [displayName, setDisplayNameDirectly] = useState(identity?.profile?.displayName ?? '');
|
|
@@ -101,14 +101,16 @@ export const ProfileContainer = () => {
|
|
|
101
101
|
);
|
|
102
102
|
|
|
103
103
|
return (
|
|
104
|
-
<
|
|
105
|
-
<Input.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
104
|
+
<Form.Row label={label} description={t('display-name.description')}>
|
|
105
|
+
<Input.Root>
|
|
106
|
+
<Input.TextInput
|
|
107
|
+
value={getValue()}
|
|
108
|
+
onChange={handleChange}
|
|
109
|
+
placeholder={t('display-name-input.placeholder')}
|
|
110
|
+
classNames='min-w-64'
|
|
111
|
+
/>
|
|
112
|
+
</Input.Root>
|
|
113
|
+
</Form.Row>
|
|
112
114
|
);
|
|
113
115
|
},
|
|
114
116
|
emoji: ({ type, label, getValue, onValueChange }) => {
|
|
@@ -119,7 +121,7 @@ export const ProfileContainer = () => {
|
|
|
119
121
|
);
|
|
120
122
|
|
|
121
123
|
return (
|
|
122
|
-
<
|
|
124
|
+
<Form.Row label={label} description={t('icon.description')}>
|
|
123
125
|
<EmojiPickerBlock
|
|
124
126
|
triggerVariant='default'
|
|
125
127
|
emoji={getValue()}
|
|
@@ -127,7 +129,7 @@ export const ProfileContainer = () => {
|
|
|
127
129
|
onClickClear={handleEmojiReset}
|
|
128
130
|
classNames='justify-self-end'
|
|
129
131
|
/>
|
|
130
|
-
</
|
|
132
|
+
</Form.Row>
|
|
131
133
|
);
|
|
132
134
|
},
|
|
133
135
|
hue: ({ type, label, getValue, onValueChange }) => {
|
|
@@ -138,22 +140,24 @@ export const ProfileContainer = () => {
|
|
|
138
140
|
);
|
|
139
141
|
|
|
140
142
|
return (
|
|
141
|
-
<
|
|
143
|
+
<Form.Row label={label} description={t('hue.description')}>
|
|
142
144
|
<div className='flex justify-self-end'>
|
|
143
145
|
<HuePicker value={getValue()} onChange={handleChange} onReset={handleHueReset} />
|
|
144
146
|
</div>
|
|
145
|
-
</
|
|
147
|
+
</Form.Row>
|
|
146
148
|
);
|
|
147
149
|
},
|
|
148
150
|
// TODO(wittjosiah): We need text input annotations for disabled and copyable.
|
|
149
151
|
did: ({ label, getValue }) => {
|
|
150
152
|
return (
|
|
151
|
-
<
|
|
152
|
-
<
|
|
153
|
-
<
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
153
|
+
<Form.Row label={label} description={t('did.description')}>
|
|
154
|
+
<Input.Root>
|
|
155
|
+
<ButtonGroup classNames='w-full'>
|
|
156
|
+
<Input.TextInput value={getValue()} disabled classNames='min-w-64' />
|
|
157
|
+
<Clipboard.IconButton value={getValue() ?? ''} />
|
|
158
|
+
</ButtonGroup>
|
|
159
|
+
</Input.Root>
|
|
160
|
+
</Form.Row>
|
|
157
161
|
);
|
|
158
162
|
},
|
|
159
163
|
}),
|
|
@@ -162,15 +166,21 @@ export const ProfileContainer = () => {
|
|
|
162
166
|
|
|
163
167
|
return (
|
|
164
168
|
<Clipboard.Provider>
|
|
165
|
-
<
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
+
<Form.Root
|
|
170
|
+
variant='settings'
|
|
171
|
+
schema={UserProfile}
|
|
172
|
+
values={values}
|
|
173
|
+
fieldMap={fieldMap}
|
|
174
|
+
onValuesChanged={handleChange}
|
|
175
|
+
>
|
|
176
|
+
<Form.Viewport scroll>
|
|
177
|
+
<Form.Content>
|
|
178
|
+
<Form.Section title={t('profile.label')} description={t('profile.description')}>
|
|
169
179
|
<Form.FieldSet />
|
|
170
|
-
</Form.
|
|
171
|
-
</Form.
|
|
172
|
-
</
|
|
173
|
-
</
|
|
180
|
+
</Form.Section>
|
|
181
|
+
</Form.Content>
|
|
182
|
+
</Form.Viewport>
|
|
183
|
+
</Form.Root>
|
|
174
184
|
</Clipboard.Provider>
|
|
175
185
|
);
|
|
176
186
|
};
|
|
@@ -14,7 +14,7 @@ export type RecoveryCodeDialogProps = {
|
|
|
14
14
|
|
|
15
15
|
// TODO(burdon): Should have cancel button.
|
|
16
16
|
export const RecoveryCodeDialog = ({ code }: RecoveryCodeDialogProps) => {
|
|
17
|
-
const { t } = useTranslation(meta.
|
|
17
|
+
const { t } = useTranslation(meta.profile.key);
|
|
18
18
|
const [confirmation, setConfirmation] = useState(false);
|
|
19
19
|
|
|
20
20
|
const handleConfirmation = useCallback((checked: boolean) => setConfirmation(checked), []);
|
|
@@ -6,16 +6,17 @@ import React from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { useOperationInvoker } from '@dxos/app-framework/ui';
|
|
8
8
|
import { useCredentials } from '@dxos/react-client/halo';
|
|
9
|
-
import { Icon, IconButton,
|
|
10
|
-
import {
|
|
9
|
+
import { Icon, IconButton, Message, useTranslation } from '@dxos/react-ui';
|
|
10
|
+
import { Form } from '@dxos/react-ui-form';
|
|
11
|
+
import { Listbox } from '@dxos/react-ui-list';
|
|
11
12
|
|
|
12
13
|
import { meta } from '#meta';
|
|
13
14
|
import { ClientOperation } from '#operations';
|
|
14
15
|
|
|
15
|
-
export const MANAGE_CREDENTIALS_DIALOG = `${meta.
|
|
16
|
+
export const MANAGE_CREDENTIALS_DIALOG = `${meta.profile.key}.ManageCredentialsDialog`;
|
|
16
17
|
|
|
17
18
|
export const RecoveryCredentialsContainer = () => {
|
|
18
|
-
const { t } = useTranslation(meta.
|
|
19
|
+
const { t } = useTranslation(meta.profile.key);
|
|
19
20
|
const { invokePromise } = useOperationInvoker();
|
|
20
21
|
const credentials = useCredentials();
|
|
21
22
|
const recoveryCredentials = credentials.filter(
|
|
@@ -23,44 +24,52 @@ export const RecoveryCredentialsContainer = () => {
|
|
|
23
24
|
);
|
|
24
25
|
|
|
25
26
|
return (
|
|
26
|
-
<
|
|
27
|
-
<
|
|
28
|
-
<
|
|
29
|
-
<
|
|
30
|
-
label={t('create-passkey.label')}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
label={t('create-recovery-code.label')}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
27
|
+
<Form.Root variant='settings'>
|
|
28
|
+
<Form.Viewport scroll>
|
|
29
|
+
<Form.Content>
|
|
30
|
+
<Form.Section title={t('recovery-setup-dialog.title')} description={t('recovery-setup-dialog.description')}>
|
|
31
|
+
<Form.Row label={t('create-passkey.label')} description={t('create-passkey.description')}>
|
|
32
|
+
<IconButton
|
|
33
|
+
label={t('create-passkey.label')}
|
|
34
|
+
icon='ph--key--duotone'
|
|
35
|
+
variant='primary'
|
|
36
|
+
onClick={() => invokePromise(ClientOperation.CreatePasskey)}
|
|
37
|
+
/>
|
|
38
|
+
</Form.Row>
|
|
39
|
+
<Form.Row label={t('create-recovery-code.label')} description={t('create-recovery-code.description')}>
|
|
40
|
+
<IconButton
|
|
41
|
+
label={t('create-recovery-code.label')}
|
|
42
|
+
icon='ph--receipt--duotone'
|
|
43
|
+
variant='default'
|
|
44
|
+
onClick={() => invokePromise(ClientOperation.CreateRecoveryCode)}
|
|
45
|
+
/>
|
|
46
|
+
</Form.Row>
|
|
47
|
+
</Form.Section>
|
|
48
|
+
<Form.Section title={t('credentials-list.label')}>
|
|
49
|
+
{recoveryCredentials.length < 1 ? (
|
|
50
|
+
<Message.Root valence='error'>
|
|
51
|
+
<Message.Title icon='ph--shield-warning--duotone'>{t('no-credentials.title')}</Message.Title>
|
|
52
|
+
<Message.Content>{t('no-credentials.message')}</Message.Content>
|
|
53
|
+
</Message.Root>
|
|
54
|
+
) : (
|
|
55
|
+
<Listbox.Root>
|
|
56
|
+
<Listbox.Content classNames='gap-1'>
|
|
57
|
+
{recoveryCredentials.map((credential, index) => (
|
|
58
|
+
<Listbox.Item
|
|
59
|
+
key={credential.id?.toHex() ?? index}
|
|
60
|
+
id={credential.id?.toHex() ?? `${index}`}
|
|
61
|
+
classNames='gap-2'
|
|
62
|
+
>
|
|
63
|
+
<Icon icon='ph--key--regular' />
|
|
64
|
+
<Listbox.ItemLabel>{credential.issuanceDate.toLocaleString()}</Listbox.ItemLabel>
|
|
65
|
+
</Listbox.Item>
|
|
66
|
+
))}
|
|
67
|
+
</Listbox.Content>
|
|
68
|
+
</Listbox.Root>
|
|
69
|
+
)}
|
|
70
|
+
</Form.Section>
|
|
71
|
+
</Form.Content>
|
|
72
|
+
</Form.Viewport>
|
|
73
|
+
</Form.Root>
|
|
65
74
|
);
|
|
66
75
|
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Context } from '@dxos/context';
|
|
8
|
+
import { log } from '@dxos/log';
|
|
9
|
+
import { type GetProfileUsageResponse } from '@dxos/protocols';
|
|
10
|
+
import { useAsyncEffect } from '@dxos/react-ui';
|
|
11
|
+
|
|
12
|
+
import { useHubHttpClient } from '../../hooks';
|
|
13
|
+
import { UsageView, type UsageViewState } from './UsageView';
|
|
14
|
+
|
|
15
|
+
/** State + payload kept together so `ready` always carries data (mirrors the discriminated `UsageViewProps`). */
|
|
16
|
+
type UsageFetchState = { state: Exclude<UsageViewState, 'ready'> } | { state: 'ready'; data: GetProfileUsageResponse };
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Fetches rolling-window profile usage from the hub service and renders it.
|
|
20
|
+
* Connected wrapper around the presentational {@link UsageView}.
|
|
21
|
+
*/
|
|
22
|
+
export const UsageContainer = () => {
|
|
23
|
+
const hubHttp = useHubHttpClient();
|
|
24
|
+
const [fetchState, setFetchState] = useState<UsageFetchState>({ state: 'loading' });
|
|
25
|
+
const [lastUpdated, setLastUpdated] = useState<number | undefined>();
|
|
26
|
+
const [refreshCount, setRefreshCount] = useState(0);
|
|
27
|
+
|
|
28
|
+
useAsyncEffect(async () => {
|
|
29
|
+
if (!hubHttp) {
|
|
30
|
+
setFetchState({ state: 'unavailable' });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// Keep prior data visible while refetching; only show the loading state on first load.
|
|
34
|
+
setFetchState((previous) => (previous.state === 'ready' ? previous : { state: 'loading' }));
|
|
35
|
+
try {
|
|
36
|
+
const result = await hubHttp.getProfileUsage(new Context());
|
|
37
|
+
setFetchState({ state: 'ready', data: result });
|
|
38
|
+
setLastUpdated(Date.now());
|
|
39
|
+
} catch (err) {
|
|
40
|
+
log.catch(err);
|
|
41
|
+
setFetchState({ state: 'error' });
|
|
42
|
+
}
|
|
43
|
+
}, [hubHttp, refreshCount]);
|
|
44
|
+
|
|
45
|
+
const handleRefresh = useCallback(() => setRefreshCount((count) => count + 1), []);
|
|
46
|
+
|
|
47
|
+
return <UsageView {...fetchState} lastUpdated={lastUpdated} onRefresh={handleRefresh} />;
|
|
48
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import { expect, userEvent, within } from 'storybook/test';
|
|
7
|
+
|
|
8
|
+
import { type GetProfileUsageResponse } from '@dxos/protocols';
|
|
9
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
10
|
+
|
|
11
|
+
import { translations } from '#translations';
|
|
12
|
+
|
|
13
|
+
import { UsageView } from './UsageView';
|
|
14
|
+
|
|
15
|
+
// Mock payload shaped like the `/api/metering/profile/usage` response.
|
|
16
|
+
const usageData: GetProfileUsageResponse = {
|
|
17
|
+
profileId: 'did:key:z6MkExampleProfileIdentity',
|
|
18
|
+
usage: [
|
|
19
|
+
{ eventType: 'ai', valueKey: 'inputTokens', subtypePattern: ['claude-opus-4'], amount: 1_284_500 },
|
|
20
|
+
{ eventType: 'ai', valueKey: 'outputTokens', subtypePattern: ['claude-opus-4'], amount: 238_900 },
|
|
21
|
+
{ eventType: 'ai', valueKey: 'inputTokens', subtypePattern: ['claude-sonnet-4'], amount: 92_300 },
|
|
22
|
+
{ eventType: 'ai', valueKey: 'outputTokens', subtypePattern: ['claude-sonnet-4'], amount: 18_400 },
|
|
23
|
+
],
|
|
24
|
+
limits: [
|
|
25
|
+
{
|
|
26
|
+
eventType: 'ai',
|
|
27
|
+
valueKey: 'inputTokens',
|
|
28
|
+
subtypePattern: ['claude-opus-4'],
|
|
29
|
+
limit: 5_000_000,
|
|
30
|
+
windowDuration: 24 * 7 * 60 * 60,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
eventType: 'ai',
|
|
34
|
+
valueKey: 'outputTokens',
|
|
35
|
+
subtypePattern: ['*'],
|
|
36
|
+
limit: 1_000_000,
|
|
37
|
+
windowDuration: 24 * 30 * 60 * 60,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
eventType: 'ai',
|
|
41
|
+
valueKey: 'inputTokens',
|
|
42
|
+
subtypePattern: ['*'],
|
|
43
|
+
limit: 20_000_000,
|
|
44
|
+
windowDuration: 24 * 30 * 60 * 60,
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
buckets: [],
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const meta = {
|
|
51
|
+
title: 'plugins/plugin-client/containers/UsageView',
|
|
52
|
+
component: UsageView,
|
|
53
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
54
|
+
tags: ['test'],
|
|
55
|
+
parameters: {
|
|
56
|
+
layout: 'fullscreen',
|
|
57
|
+
translations,
|
|
58
|
+
},
|
|
59
|
+
} satisfies Meta<typeof UsageView>;
|
|
60
|
+
|
|
61
|
+
export default meta;
|
|
62
|
+
|
|
63
|
+
type Story = StoryObj<typeof meta>;
|
|
64
|
+
|
|
65
|
+
export const Default: Story = {
|
|
66
|
+
args: {
|
|
67
|
+
state: 'ready',
|
|
68
|
+
data: usageData,
|
|
69
|
+
lastUpdated: new Date('2026-06-16T12:00:00Z').getTime(),
|
|
70
|
+
onRefresh: () => {},
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Internal plan: a single unlimited (`limit: null`) `ai` limit — renders without a meter.
|
|
75
|
+
export const Internal: Story = {
|
|
76
|
+
args: {
|
|
77
|
+
state: 'ready',
|
|
78
|
+
data: {
|
|
79
|
+
profileId: usageData.profileId,
|
|
80
|
+
usage: usageData.usage,
|
|
81
|
+
limits: [
|
|
82
|
+
{
|
|
83
|
+
eventType: 'ai',
|
|
84
|
+
valueKey: 'outputTokens',
|
|
85
|
+
subtypePattern: ['*'],
|
|
86
|
+
limit: null,
|
|
87
|
+
windowDuration: 24 * 30 * 60 * 60,
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
buckets: [],
|
|
91
|
+
},
|
|
92
|
+
lastUpdated: new Date('2026-06-16T12:00:00Z').getTime(),
|
|
93
|
+
onRefresh: () => {},
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const Loading: Story = {
|
|
98
|
+
args: { state: 'loading' },
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const Error: Story = {
|
|
102
|
+
args: { state: 'error' },
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const Empty: Story = {
|
|
106
|
+
args: {
|
|
107
|
+
state: 'ready',
|
|
108
|
+
data: { profileId: usageData.profileId, usage: [], limits: [], buckets: [] },
|
|
109
|
+
lastUpdated: new Date('2026-06-16T12:00:00Z').getTime(),
|
|
110
|
+
onRefresh: () => {},
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export const Unavailable: Story = {
|
|
115
|
+
args: { state: 'unavailable' },
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Drives the raw-response disclosure: collapsed until toggled, then the JSON viewer appears.
|
|
119
|
+
export const Test: Story = {
|
|
120
|
+
args: Default.args,
|
|
121
|
+
play: async () => {
|
|
122
|
+
const screen = within(document.body);
|
|
123
|
+
await expect(screen.queryByTestId('usage-raw-json')).toBeNull();
|
|
124
|
+
await userEvent.click(await screen.findByRole('button', { name: 'Raw JSON' }, { timeout: 10_000 }));
|
|
125
|
+
await screen.findByTestId('usage-raw-json');
|
|
126
|
+
},
|
|
127
|
+
};
|