@dxos/plugin-client 0.9.1-main.c7dcc2e112 → 0.10.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.
- package/dist/lib/neutral/{AccountContainer-LIMGLO2Y.mjs → AccountContainer-7SF53UON.mjs} +15 -11
- package/dist/lib/neutral/AccountContainer-7SF53UON.mjs.map +7 -0
- package/dist/lib/neutral/ClientPlugin.node.mjs +2 -2
- package/dist/lib/neutral/ClientPlugin.node.mjs.map +3 -3
- package/dist/lib/neutral/{DevicesContainer-X26OGLFK.mjs → DevicesContainer-PRWIN36N.mjs} +25 -16
- package/dist/lib/neutral/DevicesContainer-PRWIN36N.mjs.map +7 -0
- package/dist/lib/neutral/{InvitationsContainer-OBBCJ7CJ.mjs → InvitationsContainer-BI5RAWET.mjs} +42 -31
- package/dist/lib/neutral/InvitationsContainer-BI5RAWET.mjs.map +7 -0
- package/dist/lib/neutral/{ProfileContainer-FGS2MVWA.mjs → ProfileContainer-YEAYOTX5.mjs} +21 -20
- package/dist/lib/neutral/ProfileContainer-YEAYOTX5.mjs.map +7 -0
- package/dist/lib/neutral/{RecoveryCredentialsContainer-SL4LYHBE.mjs → RecoveryCredentialsContainer-6BZ2IWX7.mjs} +22 -13
- 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-FTXJNX37.mjs → app-graph-builder-RVXTD6OE.mjs} +3 -3
- package/dist/lib/neutral/app-graph-builder-RVXTD6OE.mjs.map +7 -0
- package/dist/lib/neutral/capabilities/index.mjs +4 -4
- package/dist/lib/neutral/capabilities/node.mjs +3 -3
- package/dist/lib/neutral/chunk-A7WDDIWY.mjs +162 -0
- package/dist/lib/neutral/chunk-A7WDDIWY.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/containers/index.mjs +6 -6
- package/dist/lib/neutral/{create-agent-U567RM7Z.mjs → create-agent-6UKJMZS5.mjs} +2 -2
- package/dist/lib/neutral/{create-identity-IKN2FJ5B.mjs → create-identity-ULCRB3YH.mjs} +2 -2
- package/dist/lib/neutral/{create-passkey-JAD3ACY7.mjs → create-passkey-I7VDS3AC.mjs} +2 -2
- package/dist/lib/neutral/{create-recovery-code-JZYXR4BY.mjs → create-recovery-code-DH2B6EGP.mjs} +2 -2
- package/dist/lib/neutral/index.mjs +4 -2
- package/dist/lib/neutral/{layer-specs-VQCJLV33.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/{navigation-handler-MPJAVHWY.mjs → navigation-handler-MQ6MZXLX.mjs} +2 -2
- package/dist/lib/neutral/{open-usage-24I7RAFL.mjs → open-usage-OJVTPGOY.mjs} +2 -2
- package/dist/lib/neutral/operations/index.mjs +1 -1
- package/dist/lib/neutral/plugin.mjs +1 -1
- package/dist/lib/neutral/{redeem-passkey-4DR7GPMU.mjs → redeem-passkey-TYP4XLHO.mjs} +2 -2
- package/dist/lib/neutral/{redeem-token-N5MBQG3I.mjs → redeem-token-2J33R3PV.mjs} +2 -2
- package/dist/lib/neutral/{share-identity-YJIFDIPJ.mjs → share-identity-TOROBLWL.mjs} +2 -2
- package/dist/lib/neutral/testing/index.mjs.map +2 -2
- package/dist/lib/neutral/types/index.mjs +3 -1
- 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/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/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/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 +1 -1
- package/dist/types/src/capabilities/schema-defs.d.ts +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.map +1 -1
- package/dist/types/src/containers/UsageContainer/UsageView.d.ts +16 -7
- package/dist/types/src/containers/UsageContainer/UsageView.d.ts.map +1 -1
- package/dist/types/src/containers/UsageContainer/UsageView.stories.d.ts +2 -0
- package/dist/types/src/containers/UsageContainer/UsageView.stories.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 +4 -0
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/testing/initializeIdentity.d.ts.map +1 -1
- package/dist/types/src/{state → types}/account-cache.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/tsconfig.tsbuildinfo +1 -1
- package/package.json +46 -45
- package/src/capabilities/account-cache.ts +1 -3
- package/src/capabilities/app-graph-builder.ts +80 -79
- package/src/capabilities/layer-specs.ts +4 -6
- package/src/commands/account/login/login.ts +2 -2
- package/src/containers/AccountContainer/AccountContainer.stories.tsx +46 -0
- package/src/containers/AccountContainer/AccountContainer.tsx +66 -62
- package/src/containers/DevicesContainer/DevicesContainer.tsx +69 -54
- package/src/containers/InvitationsContainer/InvitationsContainer.stories.tsx +46 -0
- package/src/containers/InvitationsContainer/InvitationsContainer.tsx +64 -61
- package/src/containers/ProfileContainer/ProfileContainer.tsx +37 -27
- package/src/containers/RecoveryCredentialsContainer/RecoveryCredentialsContainer.tsx +50 -41
- package/src/containers/UsageContainer/UsageContainer.tsx +11 -10
- package/src/containers/UsageContainer/UsageView.stories.tsx +13 -0
- package/src/containers/UsageContainer/UsageView.tsx +182 -150
- package/src/hooks/index.ts +5 -0
- package/src/testing/initializeIdentity.ts +1 -0
- package/src/types/capabilities.ts +1 -1
- package/src/types/index.ts +1 -0
- package/dist/lib/neutral/AccountContainer-LIMGLO2Y.mjs.map +0 -7
- package/dist/lib/neutral/DevicesContainer-X26OGLFK.mjs.map +0 -7
- package/dist/lib/neutral/InvitationsContainer-OBBCJ7CJ.mjs.map +0 -7
- package/dist/lib/neutral/ProfileContainer-FGS2MVWA.mjs.map +0 -7
- package/dist/lib/neutral/RecoveryCredentialsContainer-SL4LYHBE.mjs.map +0 -7
- package/dist/lib/neutral/UsageContainer-HBPMLIK7.mjs +0 -226
- package/dist/lib/neutral/UsageContainer-HBPMLIK7.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-FTXJNX37.mjs.map +0 -7
- package/dist/lib/neutral/chunk-54MDDWCV.mjs +0 -8
- package/dist/lib/neutral/chunk-DA3LZQSD.mjs +0 -136
- package/dist/lib/neutral/chunk-DA3LZQSD.mjs.map +0 -7
- package/dist/lib/neutral/layer-specs-VQCJLV33.mjs.map +0 -7
- package/dist/types/src/state/use-hub-http.d.ts.map +0 -1
- /package/dist/lib/neutral/{chunk-54MDDWCV.mjs.map → chunk-ZFABGG2E.mjs.map} +0 -0
- /package/dist/lib/neutral/{create-agent-U567RM7Z.mjs.map → create-agent-6UKJMZS5.mjs.map} +0 -0
- /package/dist/lib/neutral/{create-identity-IKN2FJ5B.mjs.map → create-identity-ULCRB3YH.mjs.map} +0 -0
- /package/dist/lib/neutral/{create-passkey-JAD3ACY7.mjs.map → create-passkey-I7VDS3AC.mjs.map} +0 -0
- /package/dist/lib/neutral/{create-recovery-code-JZYXR4BY.mjs.map → create-recovery-code-DH2B6EGP.mjs.map} +0 -0
- /package/dist/lib/neutral/{navigation-handler-MPJAVHWY.mjs.map → navigation-handler-MQ6MZXLX.mjs.map} +0 -0
- /package/dist/lib/neutral/{open-usage-24I7RAFL.mjs.map → open-usage-OJVTPGOY.mjs.map} +0 -0
- /package/dist/lib/neutral/{redeem-passkey-4DR7GPMU.mjs.map → redeem-passkey-TYP4XLHO.mjs.map} +0 -0
- /package/dist/lib/neutral/{redeem-token-N5MBQG3I.mjs.map → redeem-token-2J33R3PV.mjs.map} +0 -0
- /package/dist/lib/neutral/{share-identity-YJIFDIPJ.mjs.map → share-identity-TOROBLWL.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
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import { expect, userEvent, within } from 'storybook/test';
|
|
6
7
|
|
|
7
8
|
import { type GetProfileUsageResponse } from '@dxos/protocols';
|
|
8
9
|
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
@@ -50,6 +51,7 @@ const meta = {
|
|
|
50
51
|
title: 'plugins/plugin-client/containers/UsageView',
|
|
51
52
|
component: UsageView,
|
|
52
53
|
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
54
|
+
tags: ['test'],
|
|
53
55
|
parameters: {
|
|
54
56
|
layout: 'fullscreen',
|
|
55
57
|
translations,
|
|
@@ -112,3 +114,14 @@ export const Empty: Story = {
|
|
|
112
114
|
export const Unavailable: Story = {
|
|
113
115
|
args: { state: 'unavailable' },
|
|
114
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
|
+
};
|
|
@@ -2,28 +2,33 @@
|
|
|
2
2
|
// Copyright 2026 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
6
7
|
|
|
7
8
|
import { type GetProfileUsageResponse, type MeteringLimit, type MeteringUsageItem } from '@dxos/protocols';
|
|
8
|
-
import { IconButton, Message, useTranslation } from '@dxos/react-ui';
|
|
9
|
-
import {
|
|
9
|
+
import { IconButton, Message, Status, ToggleIconButton, useTranslation } from '@dxos/react-ui';
|
|
10
|
+
import { Form, type FormFieldProvider } from '@dxos/react-ui-form';
|
|
10
11
|
import { JsonHighlighter } from '@dxos/react-ui-syntax-highlighter';
|
|
11
12
|
|
|
12
13
|
import { meta } from '#meta';
|
|
13
14
|
|
|
14
15
|
export type UsageViewState = 'loading' | 'ready' | 'error' | 'unavailable';
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
/** Current fetch state; drives which content is shown. */
|
|
18
|
-
state: UsageViewState;
|
|
19
|
-
/** Profile usage payload; required when `state` is `'ready'`. */
|
|
20
|
-
data?: GetProfileUsageResponse;
|
|
17
|
+
type UsageViewCommonProps = {
|
|
21
18
|
/** Epoch milliseconds of the last successful fetch. */
|
|
22
19
|
lastUpdated?: number;
|
|
23
20
|
/** Invoked when the user requests a refresh. */
|
|
24
21
|
onRefresh?: () => void;
|
|
25
22
|
};
|
|
26
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Discriminated on `state`: the payload is required (and only present) when `state` is `'ready'`, so a
|
|
26
|
+
* missing payload can't be silently rendered as an empty plan.
|
|
27
|
+
*/
|
|
28
|
+
export type UsageViewProps =
|
|
29
|
+
| (UsageViewCommonProps & { state: Exclude<UsageViewState, 'ready'>; data?: undefined })
|
|
30
|
+
| (UsageViewCommonProps & { state: 'ready'; data: GetProfileUsageResponse });
|
|
31
|
+
|
|
27
32
|
type TFunction = (key: string, options?: Record<string, unknown>) => string;
|
|
28
33
|
|
|
29
34
|
/**
|
|
@@ -77,162 +82,189 @@ const formatWindow = (windowHours: number, t: TFunction): string => {
|
|
|
77
82
|
return t('usage-window-hours.label', { count: windowHours });
|
|
78
83
|
};
|
|
79
84
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
*/
|
|
84
|
-
const UsageRow = ({
|
|
85
|
-
label,
|
|
86
|
-
caption,
|
|
87
|
-
percent,
|
|
88
|
-
value,
|
|
89
|
-
}: {
|
|
85
|
+
type UsageRow = {
|
|
86
|
+
key: string;
|
|
87
|
+
/** Field label (schema `title` annotation). */
|
|
90
88
|
label: string;
|
|
91
|
-
caption
|
|
89
|
+
/** Field caption (schema `description` annotation). */
|
|
90
|
+
caption: string;
|
|
91
|
+
/** Consumption as a whole percentage (`0`–`100`); `undefined` for an unlimited limit. */
|
|
92
92
|
percent?: number;
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
93
|
+
windowHours: number;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/** One display row per limit, shorter windows first. A `null` limit is reported as unlimited. */
|
|
97
|
+
const computeRows = (data: GetProfileUsageResponse, t: TFunction): UsageRow[] =>
|
|
98
|
+
data.limits
|
|
99
|
+
.map((limit): UsageRow => {
|
|
100
|
+
const used = sumUsageForLimit(data.usage, limit);
|
|
101
|
+
const label = formatLimitLabel(limit, t);
|
|
102
|
+
const windowHours = limit.windowDuration / SECONDS_PER_HOUR;
|
|
103
|
+
const window = formatWindow(windowHours, t);
|
|
104
|
+
const key = `${limit.eventType}/${limit.subtypePattern.join(',')}/${limit.valueKey}/${limit.windowDuration}/${limit.limit ?? 'unlimited'}`;
|
|
105
|
+
if (limit.limit === null) {
|
|
106
|
+
return {
|
|
107
|
+
key,
|
|
108
|
+
label,
|
|
109
|
+
caption: t('usage-unlimited.description', { used: formatAmount(used), window }),
|
|
110
|
+
windowHours,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const percent = limit.limit > 0 ? Math.min(100, Math.round((used / limit.limit) * 100)) : used > 0 ? 100 : 0;
|
|
114
|
+
return {
|
|
115
|
+
key,
|
|
116
|
+
label,
|
|
117
|
+
caption: `${t('usage-percent-used.label', { percent })} · ${t('usage-limit.description', { used: formatAmount(used), limit: formatAmount(limit.limit), window })}`,
|
|
118
|
+
percent,
|
|
119
|
+
windowHours,
|
|
120
|
+
};
|
|
121
|
+
})
|
|
122
|
+
.sort((left, right) => left.windowHours - right.windowHours);
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* A single usage field: an optional number carrying its label/caption as `title`/`description`
|
|
126
|
+
* annotations and the consumption percentage as its value. Optional so the form renders no required
|
|
127
|
+
* markers; `Schema.Number` keeps the field context-free (`R = never`) so the struct stays assignable to `Form`.
|
|
128
|
+
*/
|
|
129
|
+
const usageField = (title: string, description: string) =>
|
|
130
|
+
Schema.optional(Schema.Number.annotations({ title, description }));
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Build the Effect schema (one annotated field per limit) and matching values. Unlimited limits have no
|
|
134
|
+
* value, so their field reads `undefined` and the renderer shows the "Unlimited" label instead of a meter.
|
|
135
|
+
*/
|
|
136
|
+
const buildUsageForm = (rows: UsageRow[]) => {
|
|
137
|
+
const fields: Record<string, ReturnType<typeof usageField>> = {};
|
|
138
|
+
const values: Record<string, number> = {};
|
|
139
|
+
rows.forEach((row, index) => {
|
|
140
|
+
const field = `metric_${index}`;
|
|
141
|
+
fields[field] = usageField(row.label, row.caption);
|
|
142
|
+
if (row.percent !== undefined) {
|
|
143
|
+
values[field] = row.percent;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
return { schema: Schema.Struct(fields), values };
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/** Non-data states shown as a single `Message`, keyed by message variant (translation keys for the copy). */
|
|
150
|
+
const STATE_MESSAGES = {
|
|
151
|
+
unavailable: {
|
|
152
|
+
valence: 'warning',
|
|
153
|
+
icon: 'ph--cloud-slash--duotone',
|
|
154
|
+
title: 'usage-unavailable.title',
|
|
155
|
+
description: 'usage-unavailable.description',
|
|
156
|
+
},
|
|
157
|
+
error: {
|
|
158
|
+
valence: 'error',
|
|
159
|
+
icon: 'ph--cloud-x--duotone',
|
|
160
|
+
title: 'usage-offline.title',
|
|
161
|
+
description: 'usage-offline.description',
|
|
162
|
+
},
|
|
163
|
+
empty: {
|
|
164
|
+
valence: 'neutral',
|
|
165
|
+
icon: 'ph--chart-bar--duotone',
|
|
166
|
+
title: 'usage-empty.title',
|
|
167
|
+
description: 'usage-empty.description',
|
|
168
|
+
},
|
|
169
|
+
} as const;
|
|
136
170
|
|
|
137
171
|
/**
|
|
138
|
-
* Presentational view of profile usage: renders loading/error/unavailable/empty states, or
|
|
139
|
-
*
|
|
172
|
+
* Presentational view of profile usage: renders loading/error/unavailable/empty states, or a
|
|
173
|
+
* schema-driven settings form (one read-only field per limit), plus a refresh control and a
|
|
174
|
+
* collapsible raw-response viewer. Pure — all data via props.
|
|
140
175
|
*/
|
|
141
176
|
export const UsageView = ({ state, data, lastUpdated, onRefresh }: UsageViewProps) => {
|
|
142
177
|
const { t } = useTranslation(meta.profile.key);
|
|
143
178
|
const [rawExpanded, setRawExpanded] = useState(false);
|
|
144
179
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return [];
|
|
149
|
-
}
|
|
150
|
-
return data.limits
|
|
151
|
-
.map((limit) => {
|
|
152
|
-
const used = sumUsageForLimit(data.usage, limit);
|
|
153
|
-
const label = formatLimitLabel(limit, t);
|
|
154
|
-
const windowHours = limit.windowDuration / SECONDS_PER_HOUR;
|
|
155
|
-
const window = formatWindow(windowHours, t);
|
|
156
|
-
const key = `${limit.eventType}/${limit.subtypePattern.join(',')}/${limit.valueKey}/${limit.windowDuration}/${limit.limit ?? 'unlimited'}`;
|
|
157
|
-
if (limit.limit === null) {
|
|
158
|
-
return {
|
|
159
|
-
key,
|
|
160
|
-
label,
|
|
161
|
-
caption: t('usage-unlimited.description', { used: formatAmount(used), window }),
|
|
162
|
-
percent: undefined,
|
|
163
|
-
value: t('usage-unlimited.label'),
|
|
164
|
-
windowHours,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
const percent = limit.limit > 0 ? Math.min(100, Math.round((used / limit.limit) * 100)) : used > 0 ? 100 : 0;
|
|
168
|
-
return {
|
|
169
|
-
key,
|
|
170
|
-
label,
|
|
171
|
-
caption: t('usage-limit.description', { used: formatAmount(used), limit: formatAmount(limit.limit), window }),
|
|
172
|
-
percent,
|
|
173
|
-
value: t('usage-percent-used.label', { percent }),
|
|
174
|
-
windowHours,
|
|
175
|
-
};
|
|
176
|
-
})
|
|
177
|
-
.sort((a, b) => a.windowHours - b.windowHours);
|
|
180
|
+
const { schema, values, empty } = useMemo(() => {
|
|
181
|
+
const rows = data ? computeRows(data, t) : [];
|
|
182
|
+
return { ...buildUsageForm(rows), empty: rows.length === 0 };
|
|
178
183
|
}, [data, t]);
|
|
179
184
|
|
|
185
|
+
// Non-data states resolve to a `Message`; `loading` shows a spinner and `ready` with data shows the form.
|
|
186
|
+
const message =
|
|
187
|
+
state === 'unavailable'
|
|
188
|
+
? STATE_MESSAGES.unavailable
|
|
189
|
+
: state === 'error'
|
|
190
|
+
? STATE_MESSAGES.error
|
|
191
|
+
: state === 'ready' && empty
|
|
192
|
+
? STATE_MESSAGES.empty
|
|
193
|
+
: undefined;
|
|
194
|
+
|
|
195
|
+
// Render each metric as a meter from its percentage value; unlimited limits (no value) show a label.
|
|
196
|
+
const meterFieldProvider = useCallback<FormFieldProvider>(
|
|
197
|
+
({ fieldProps: { label, description, getValue } }) => {
|
|
198
|
+
const percent = getValue();
|
|
199
|
+
return (
|
|
200
|
+
<Form.Row label={label} description={description}>
|
|
201
|
+
{typeof percent === 'number' ? (
|
|
202
|
+
<Status progress={percent / 100} aria-label={t('usage-percent-used.label', { percent })} />
|
|
203
|
+
) : (
|
|
204
|
+
t('usage-unlimited.label')
|
|
205
|
+
)}
|
|
206
|
+
</Form.Row>
|
|
207
|
+
);
|
|
208
|
+
},
|
|
209
|
+
[t],
|
|
210
|
+
);
|
|
211
|
+
|
|
180
212
|
return (
|
|
181
|
-
<
|
|
182
|
-
<
|
|
183
|
-
|
|
184
|
-
<
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
<
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
213
|
+
<Form.Root variant='settings' layout='static' schema={schema} values={values}>
|
|
214
|
+
<Form.Viewport scroll>
|
|
215
|
+
<Form.Content>
|
|
216
|
+
<Form.Section title={t('usage-section.title')} description={t('usage-section.description')}>
|
|
217
|
+
{state === 'loading' ? (
|
|
218
|
+
<Status indeterminate aria-label={t('usage-section.title')} />
|
|
219
|
+
) : message ? (
|
|
220
|
+
<Message.Root valence={message.valence}>
|
|
221
|
+
<Message.Title icon={message.icon}>{t(message.title)}</Message.Title>
|
|
222
|
+
<Message.Content>{t(message.description)}</Message.Content>
|
|
223
|
+
</Message.Root>
|
|
224
|
+
) : (
|
|
225
|
+
<Form.FieldSet fieldProvider={meterFieldProvider} />
|
|
226
|
+
)}
|
|
227
|
+
</Form.Section>
|
|
228
|
+
|
|
229
|
+
{state === 'ready' && data && (
|
|
230
|
+
<Form.Section>
|
|
231
|
+
<Form.Row
|
|
232
|
+
label={
|
|
233
|
+
lastUpdated !== undefined
|
|
234
|
+
? t('usage-last-updated.label', { time: new Date(lastUpdated).toLocaleTimeString() })
|
|
235
|
+
: t('usage-refresh.label')
|
|
236
|
+
}
|
|
237
|
+
>
|
|
238
|
+
{onRefresh && (
|
|
239
|
+
<IconButton
|
|
240
|
+
iconOnly
|
|
241
|
+
variant='ghost'
|
|
242
|
+
icon='ph--arrow-clockwise--regular'
|
|
243
|
+
label={t('usage-refresh.label')}
|
|
244
|
+
onClick={onRefresh}
|
|
245
|
+
/>
|
|
246
|
+
)}
|
|
247
|
+
</Form.Row>
|
|
248
|
+
</Form.Section>
|
|
249
|
+
)}
|
|
250
|
+
|
|
251
|
+
{state === 'ready' && data && (
|
|
252
|
+
<Form.Section>
|
|
253
|
+
<Form.Row label={t('usage-raw-json.label')}>
|
|
254
|
+
<ToggleIconButton
|
|
212
255
|
iconOnly
|
|
213
256
|
variant='ghost'
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
257
|
+
active={rawExpanded}
|
|
258
|
+
icon='ph--caret-right--regular'
|
|
259
|
+
label={t('usage-raw-json.label')}
|
|
260
|
+
onClick={() => setRawExpanded((value) => !value)}
|
|
217
261
|
/>
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
variant='ghost'
|
|
226
|
-
icon='ph--caret-right--regular'
|
|
227
|
-
iconClassNames={rawExpanded ? 'transition-transform rotate-90' : 'transition-transform'}
|
|
228
|
-
label={t('usage-raw-json.label')}
|
|
229
|
-
onClick={() => setRawExpanded((value) => !value)}
|
|
230
|
-
classNames='self-start'
|
|
231
|
-
/>
|
|
232
|
-
{rawExpanded && <JsonHighlighter data={data} classNames='mt-trim-sm text-xs overflow-auto' />}
|
|
233
|
-
</div>
|
|
234
|
-
)}
|
|
235
|
-
</Settings.Section>
|
|
236
|
-
</Settings.Viewport>
|
|
262
|
+
</Form.Row>
|
|
263
|
+
{rawExpanded && <JsonHighlighter data={data} testId='usage-raw-json' />}
|
|
264
|
+
</Form.Section>
|
|
265
|
+
)}
|
|
266
|
+
</Form.Content>
|
|
267
|
+
</Form.Viewport>
|
|
268
|
+
</Form.Root>
|
|
237
269
|
);
|
|
238
270
|
};
|
|
@@ -12,7 +12,7 @@ import { type HubHttpClient } from '@dxos/edge-client';
|
|
|
12
12
|
|
|
13
13
|
import { meta } from '#meta';
|
|
14
14
|
|
|
15
|
-
import { type AccountCache as AccountCacheType } from '
|
|
15
|
+
import { type AccountCache as AccountCacheType } from './account-cache';
|
|
16
16
|
|
|
17
17
|
export namespace ClientCapabilities {
|
|
18
18
|
export const Client = Capability.make<Client>(`${meta.profile.key}.capability.client`);
|
package/src/types/index.ts
CHANGED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/containers/AccountContainer/AccountContainer.tsx"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2026 DXOS.org\n//\n\nimport { useAtom, useAtomSet } from '@effect-atom/atom-react';\nimport React, { type FormEvent, useCallback, useState } from 'react';\n\nimport { useCapability, useOperationInvoker } from '@dxos/app-framework/ui';\nimport { LayoutOperation } from '@dxos/app-toolkit';\nimport { Context } from '@dxos/context';\nimport { createDidFromIdentityKey } from '@dxos/credentials';\nimport { useIdentity } from '@dxos/react-client/halo';\nimport { Button, Icon, IconButton, Input, Message, useAsyncEffect, useTranslation } from '@dxos/react-ui';\nimport { Settings } from '@dxos/react-ui-form';\n\nimport { meta } from '#meta';\nimport { ClientCapabilities } from '#types';\n\nimport { RESET_DIALOG } from '../../constants';\nimport { useHubHttpClient } from '../../state/use-hub-http';\n\ntype AccountState = 'loading' | 'present' | 'missing' | 'error';\n\nexport const AccountContainer = () => {\n const { t } = useTranslation(meta.profile.key);\n const identity = useIdentity();\n const { invokePromise } = useOperationInvoker();\n const accountCacheAtom = useCapability(ClientCapabilities.AccountCache);\n const [cache] = useAtom(accountCacheAtom);\n const setCache = useAtomSet(accountCacheAtom);\n const [resendStatus, setResendStatus] = useState<string | null>(null);\n const [accountState, setAccountState] = useState<AccountState>(cache.account ? 'present' : 'loading');\n const [requestEmail, setRequestEmail] = useState('');\n const [requestSubmitted, setRequestSubmitted] = useState(false);\n\n // Single shared instance keeps the VP-auth handshake (request → 401 → signed\n // retry) at one round-trip per session instead of one per panel.\n const hubHttp = useHubHttpClient();\n\n useAsyncEffect(async () => {\n if (!hubHttp) {\n return;\n }\n try {\n const account = await hubHttp.getAccount(new Context());\n setCache((prev) => ({ ...prev, account, fetchedAt: Date.now() }));\n setAccountState('present');\n } catch (err: any) {\n if (err?.data?.type === 'no_account') {\n setCache((prev) => ({ ...prev, account: undefined, fetchedAt: Date.now() }));\n setAccountState('missing');\n } else {\n setAccountState((prev) => (prev === 'present' ? 'present' : 'error'));\n }\n }\n }, [hubHttp, setCache]);\n\n const handleResend = useCallback(async () => {\n if (!hubHttp) {\n return;\n }\n try {\n const result = await hubHttp.resendVerificationEmail(new Context());\n if (result.sent) {\n setResendStatus(t('verification-sent.message'));\n } else if (result.cooldownSecondsRemaining) {\n setResendStatus(t('verification-cooldown.message', { seconds: result.cooldownSecondsRemaining }));\n } else {\n setResendStatus(t('verification-failed.message'));\n }\n } catch {\n setResendStatus(t('verification-failed.message'));\n }\n }, [hubHttp, t]);\n\n const handleRequestAccess = useCallback(\n async (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n const email = requestEmail.trim();\n if (!email || !hubHttp) {\n return;\n }\n try {\n const identityDid = identity ? await createDidFromIdentityKey(identity.identityKey) : undefined;\n await hubHttp.requestAccess(new Context(), { email, identityDid });\n } catch {\n // Surface a generic confirmation; failure details would leak signal.\n }\n setRequestSubmitted(true);\n },\n [hubHttp, identity, requestEmail],\n );\n\n // Opens the standard reset confirmation dialog. The `onBeforeReset` hook\n // deletes the hub account first; if that fails the reset is aborted so the\n // local identity is not wiped while the server record remains.\n const handleDeleteAccount = useCallback(() => {\n if (!hubHttp) {\n return;\n }\n void invokePromise(LayoutOperation.UpdateDialog, {\n subject: RESET_DIALOG,\n blockAlign: 'start',\n props: {\n mode: 'reset-storage',\n onBeforeReset: async () => {\n await hubHttp.deleteAccount(new Context());\n setCache(() => ({}));\n },\n },\n });\n }, [hubHttp, invokePromise, setCache]);\n\n const account = cache.account;\n\n return (\n <Settings.Viewport>\n <Settings.Section title={t('account-section.title')} description={t('account-section.description')}>\n {accountState === 'loading' ? null : accountState === 'missing' ? (\n <>\n <Message.Root valence='warning'>\n <Message.Title icon='ph--warning--duotone'>{t('no-edge-access.title')}</Message.Title>\n <Message.Content>{t('no-edge-access.description')}</Message.Content>\n </Message.Root>\n <Settings.Item title={t('request-access.label')} description={t('request-access.description')}>\n {requestSubmitted ? (\n <span className='text-sm text-description'>{t('access-request-submitted.message')}</span>\n ) : (\n <form onSubmit={handleRequestAccess} className='flex gap-2 items-center justify-end'>\n <Input.Root>\n <Input.TextInput\n type='email'\n required\n placeholder={t('access-request-email.placeholder')}\n value={requestEmail}\n onChange={(event) => setRequestEmail(event.target.value)}\n classNames='min-w-64'\n />\n </Input.Root>\n <Button type='submit' density='sm'>\n {t('request-access.label')}\n </Button>\n </form>\n )}\n </Settings.Item>\n </>\n ) : accountState === 'error' && !account ? (\n <Message.Root valence='error'>\n <Message.Title icon='ph--cloud-x--duotone'>{t('account-offline.title')}</Message.Title>\n <Message.Content>{t('account-offline.description')}</Message.Content>\n </Message.Root>\n ) : account ? (\n <>\n <Settings.Item title={t('email.label')} description={account.email}>\n {account.emailVerified ? (\n <Icon icon='ph--check-circle--duotone' size={5} classNames='text-success-text justify-self-end' />\n ) : (\n <div className='flex flex-col gap-1 items-end'>\n <IconButton\n icon='ph--paper-plane-tilt--regular'\n label={t('resend-verification.label')}\n onClick={handleResend}\n density='sm'\n />\n {resendStatus ? <span className='text-xs text-description'>{resendStatus}</span> : null}\n </div>\n )}\n </Settings.Item>\n <Settings.Item title={t('delete-account.label')} description={t('delete-account.description')}>\n <Button variant='destructive' density='sm' onClick={handleDeleteAccount}>\n {t('delete-account.label')}\n </Button>\n </Settings.Item>\n </>\n ) : null}\n </Settings.Section>\n </Settings.Viewport>\n );\n};\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["useAtom", "useAtomSet", "React", "useCallback", "useState", "useCapability", "useOperationInvoker", "LayoutOperation", "Context", "createDidFromIdentityKey", "useIdentity", "Button", "Icon", "IconButton", "Input", "Message", "useAsyncEffect", "useTranslation", "Settings", "meta", "ClientCapabilities", "AccountContainer", "identity", "useIdentity", "invokePromise", "useOperationInvoker", "accountCacheAtom", "useCapability", "cache", "useAtom", "setCache", "useAtomSet", "resendStatus", "setResendStatus", "useState", "accountState", "setAccountState", "requestEmail", "setRequestEmail", "requestSubmitted", "setRequestSubmitted", "hubHttp", "account", "fetchedAt", "err", "type", "prev", "undefined", "t", "result", "seconds", "cooldownSecondsRemaining", "email", "identityDid", "blockAlign", "props", "onBeforeReset", "title", "valence", "Message", "icon", "description", "className", "onSubmit", "handleRequestAccess", "required", "placeholder", "value", "onChange", "event", "classNames", "Button", "emailVerified", "IconButton", "onClick", "density", "Settings", "variant", "handleDeleteAccount"]
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/containers/DevicesContainer/DevicesContainer.tsx"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2025 DXOS.org\n//\n\nimport React, { useCallback, useEffect, useState } from 'react';\nimport { QR } from 'react-qr-rounded';\n\nimport { useOperationInvoker } from '@dxos/app-framework/ui';\nimport { log } from '@dxos/log';\nimport { useClient, useMulticastObservable } from '@dxos/react-client';\nimport { type Device, useDevices } from '@dxos/react-client/halo';\nimport { type CancellableInvitationObservable, Invitation, InvitationEncoder } from '@dxos/react-client/invitations';\nimport { useNetworkStatus } from '@dxos/react-client/mesh';\nimport { Button, Clipboard, Icon, IconButton, List, useId, useTranslation } from '@dxos/react-ui';\nimport { Settings } from '@dxos/react-ui-form';\nimport { AuthCode, Centered, DeviceListItem, Emoji, Viewport } from '@dxos/shell/react';\nimport { osTranslations } from '@dxos/ui-theme';\nimport { hexToEmoji } from '@dxos/util';\n\nimport { meta } from '#meta';\nimport { ClientOperation } from '#operations';\n\nexport type DevicesContainerProps = {\n createInvitationUrl?: (invitationCode: string) => string;\n};\n\nexport const DevicesContainer = ({ createInvitationUrl }: DevicesContainerProps) => {\n const { t } = useTranslation(meta.profile.key);\n const { invokePromise } = useOperationInvoker();\n const devices = useDevices();\n const { swarm: connectionState } = useNetworkStatus();\n\n const handleResetStorage = useCallback(() => invokePromise(ClientOperation.ResetStorage, {}), [invokePromise]);\n\n const handleRecover = useCallback(\n () => invokePromise(ClientOperation.ResetStorage, { mode: 'recover' }),\n [invokePromise],\n );\n\n const handleJoinNewIdentity = useCallback(\n () => invokePromise(ClientOperation.ResetStorage, { mode: 'join-new-identity' }),\n [invokePromise],\n );\n\n return (\n <Clipboard.Provider>\n <Settings.Viewport>\n <Settings.Section\n title={t('devices-verbose.label', { ns: meta.profile.key })}\n description={t('devices.description', { ns: meta.profile.key })}\n >\n <Settings.Panel>\n <div role='group' className='min-w-0'>\n <h3 className='text-lg mb-2'>{t('devices.label', { ns: meta.profile.key })}</h3>\n <List>\n {devices.map((device: Device) => (\n <DeviceListItem key={device.deviceKey.toHex()} device={device} connectionState={connectionState} />\n ))}\n </List>\n </div>\n {createInvitationUrl && (\n <div role='group' className='min-w-0'>\n <h3 className='text-lg mb-2'>{t('add-device.label')}</h3>\n <DeviceInvitation createInvitationUrl={createInvitationUrl} />\n </div>\n )}\n </Settings.Panel>\n </Settings.Section>\n <Settings.Section\n title={t('danger-zone.title', { ns: meta.profile.key })}\n description={t('danger-zone.description', { ns: meta.profile.key })}\n >\n <Settings.Item\n title={t('reset-device.label')}\n description={t('reset-device.description', { ns: meta.profile.key })}\n >\n <Button variant='destructive' onClick={handleResetStorage} data-testid='devicesContainer.reset'>\n {t('reset-device.label')}\n </Button>\n </Settings.Item>\n <Settings.Item\n title={t('recover-identity.label')}\n description={t('recover-identity.description', { ns: meta.profile.key })}\n >\n <Button variant='destructive' onClick={handleRecover} data-testid='devicesContainer.recover'>\n {t('recover-identity.label')}\n </Button>\n </Settings.Item>\n <Settings.Item\n title={t('join-new-identity.label')}\n description={t('join-new-identity.description', { ns: meta.profile.key })}\n >\n <Button variant='destructive' onClick={handleJoinNewIdentity} data-testid='devicesContainer.joinExisting'>\n {t('join-new-identity.label')}\n </Button>\n </Settings.Item>\n </Settings.Section>\n </Settings.Viewport>\n </Clipboard.Provider>\n );\n};\n\ntype DeviceInvitationProps = {\n invitation?: CancellableInvitationObservable;\n createInvitationUrl: (invitationCode: string) => string;\n onInvitationDone: () => void;\n onInvitationCreate: () => void;\n};\n\nconst DeviceInvitation = (props: Pick<DeviceInvitationProps, 'createInvitationUrl'>) => {\n const client = useClient();\n const [invitation, setInvitation] = useState<CancellableInvitationObservable>();\n\n const onInvitationCreate = useCallback(() => {\n const invitation = client.halo.share();\n if (client.config.values.runtime?.app?.env?.DX_ENVIRONMENT !== 'production') {\n const subscription = invitation.subscribe((invitation: Invitation) => {\n const invitationCode = InvitationEncoder.encode(invitation);\n if (invitation.state === Invitation.State.CONNECTING) {\n log.info(JSON.stringify({ invitationCode, authCode: invitation.authCode }));\n subscription.unsubscribe();\n }\n });\n }\n setInvitation(invitation);\n }, [client]);\n\n const onInvitationDone = useCallback(() => {\n setInvitation(undefined);\n }, []);\n\n if (invitation) {\n return <DeviceInvitationImpl {...props} {...{ invitation, onInvitationCreate, onInvitationDone }} />;\n } else {\n return <InvitationSection {...props} {...{ onInvitationCreate, onInvitationDone }} />;\n }\n};\n\nconst DeviceInvitationImpl = ({\n invitation: invitationObservable,\n createInvitationUrl,\n onInvitationDone,\n onInvitationCreate,\n}: DeviceInvitationProps) => {\n const invitation = useMulticastObservable(invitationObservable!);\n const url = createInvitationUrl(InvitationEncoder.encode(invitation));\n\n useEffect(() => {\n if (invitation.state >= Invitation.State.SUCCESS) {\n onInvitationDone();\n }\n }, [invitation.state]);\n\n return <InvitationSection {...invitation} {...{ url, onInvitationDone, onInvitationCreate }} />;\n};\n\ntype InvitationComponentProps = Partial<\n Pick<Invitation, 'authCode' | 'invitationId'> &\n Pick<DeviceInvitationProps, 'onInvitationDone' | 'onInvitationCreate'> & {\n state: number;\n url: string;\n }\n>;\n\nconst InvitationSection = ({\n state = -1,\n authCode,\n invitationId = 'never',\n url = 'never',\n onInvitationDone = () => {},\n onInvitationCreate = () => {},\n}: InvitationComponentProps) => {\n const { t } = useTranslation(meta.profile.key);\n const activeView =\n state < 0\n ? 'init'\n : state >= Invitation.State.CANCELLED\n ? 'complete'\n : state >= Invitation.State.READY_FOR_AUTHENTICATION && authCode\n ? 'auth-code'\n : 'qr-code';\n\n return activeView === 'init' ? (\n <>\n <p className='text-description mb-2'>{t('add-device.description')}</p>\n <IconButton\n icon='ph--plus--regular'\n label={t('create-device-invitation.label')}\n disabled={state >= 0}\n classNames='w-full'\n data-testid='devicesContainer.createInvitation'\n onClick={onInvitationCreate}\n />\n </>\n ) : (\n <Viewport.Root activeView={activeView}>\n <Viewport.Views>\n <Viewport.View id='init'>\n {/* This view intentionally left blank while conditionally rendering the viewport. */}\n </Viewport.View>\n <Viewport.View id='complete'>\n <InvitationComplete statusValue={state} />\n </Viewport.View>\n <Viewport.View id='auth-code'>\n <InvitationAuthCode id={invitationId} code={authCode ?? 'never'} onCancel={onInvitationDone} />\n </Viewport.View>\n <Viewport.View id='qr-code'>\n <InvitationQR id={invitationId} url={url} onCancel={onInvitationDone} />\n </Viewport.View>\n </Viewport.Views>\n </Viewport.Root>\n );\n};\n\nconst InvitationQR = ({ id, url, onCancel }: { id: string; url: string; onCancel: () => void }) => {\n const { t } = useTranslation(osTranslations);\n const qrLabel = useId('devices-container__qr-code');\n const emoji = hexToEmoji(id);\n return (\n <>\n <p className='text-description'>{t('qr-code.description', { ns: meta.profile.key })}</p>\n <div role='group' className='grid grid-cols-[1fr_min-content]'>\n <div className='flex justify-center py-4'>\n <div className='w-full md:max-w-80 aspect-square relative text-description'>\n <QR\n rounding={100}\n backgroundColor='transparent'\n color='currentColor'\n aria-labelledby={qrLabel}\n errorCorrectionLevel='Q'\n cutout={true}\n >\n {url ?? 'never'}\n </QR>\n <Centered>\n <Emoji text={emoji} />\n </Centered>\n </div>\n </div>\n <span id={qrLabel} className='sr-only'>\n {t('qr.label')}\n </span>\n </div>\n {/* TODO(burdon): Factor out button bar */}\n <div className='flex justify-center'>\n <div className='flex gap-2'>\n <Clipboard.Button value={url ?? 'never'} />\n <Button variant='ghost' onClick={onCancel}>\n {t('cancel.label')}\n </Button>\n </div>\n </div>\n </>\n );\n};\n\nconst InvitationAuthCode = ({ id, code, onCancel }: { id: string; code: string; onCancel: () => void }) => {\n const { t } = useTranslation(osTranslations);\n const emoji = hexToEmoji(id);\n\n return (\n <>\n <p className='text-description'>{t('auth-other-device-emoji.message')}</p>\n {emoji && <Emoji text={emoji} className='mx-auto my-2 text-center' />}\n <p className='text-description'>{t('auth-code.message')}</p>\n <AuthCode code={code} large classNames='mx-auto my-2 text-center grow' />\n <Button variant='ghost' onClick={onCancel}>\n {t('cancel.label')}\n </Button>\n </>\n );\n};\n\nconst InvitationComplete = ({ statusValue }: { statusValue: number }) => {\n return statusValue > 0 ? (\n <Icon icon='ph--check--regular' size={6} classNames='m-1.5' />\n ) : (\n <Icon icon='ph--x--regular' size={6} classNames='m-1.5' />\n );\n};\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["React", "useCallback", "useEffect", "useState", "QR", "useOperationInvoker", "log", "useClient", "useMulticastObservable", "useDevices", "Invitation", "InvitationEncoder", "useNetworkStatus", "Button", "Clipboard", "Icon", "IconButton", "List", "useId", "useTranslation", "Settings", "AuthCode", "Centered", "DeviceListItem", "Emoji", "Viewport", "osTranslations", "hexToEmoji", "meta", "ClientOperation", "DevicesContainer", "invokePromise", "devices", "swarm", "connectionState", "handleResetStorage", "profile", "key", "description", "t", "role", "className", "ns", "device", "deviceKey", "createInvitationUrl", "title", "variant", "data-testid", "handleRecover", "handleJoinNewIdentity", "DeviceInvitation", "props", "setInvitation", "onInvitationCreate", "invitation", "halo", "share", "values", "app", "env", "subscription", "subscribe", "invitationCode", "state", "State", "stringify", "authCode", "unsubscribe", "client", "onInvitationDone", "SUCCESS", "url", "CANCELLED", "READY_FOR_AUTHENTICATION", "activeView", "icon", "label", "disabled", "classNames", "onClick", "Views", "id", "InvitationComplete", "statusValue", "InvitationAuthCode", "invitationId", "onCancel", "InvitationQR", "emoji", "div", "rounding", "backgroundColor", "color", "aria-labelledby", "errorCorrectionLevel", "cutout", "text", "span", "qrLabel", "value", "code", "large", "size"]
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/containers/InvitationsContainer/InvitationsContainer.tsx"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2026 DXOS.org\n//\n\nimport { useAtom, useAtomSet } from '@effect-atom/atom-react';\nimport React, { useCallback, useState } from 'react';\n\nimport { useCapability } from '@dxos/app-framework/ui';\nimport { Context } from '@dxos/context';\nimport { Clipboard, Icon, IconButton, List, ListItem, useAsyncEffect, useTranslation } from '@dxos/react-ui';\nimport { Settings } from '@dxos/react-ui-form';\n\nimport { meta } from '#meta';\nimport { ClientCapabilities } from '#types';\n\nimport { type AccountCacheInvitation } from '../../state/account-cache';\nimport { useHubHttpClient } from '../../state/use-hub-http';\n\nexport const InvitationsContainer = () => {\n const { t } = useTranslation(meta.profile.key);\n const accountCacheAtom = useCapability(ClientCapabilities.AccountCache);\n const [cache] = useAtom(accountCacheAtom);\n const setCache = useAtomSet(accountCacheAtom);\n const [pending, setPending] = useState(false);\n\n // Account/invitation routes live on hub-service, not the edge worker.\n const hubHttp = useHubHttpClient();\n\n useAsyncEffect(async () => {\n if (!hubHttp) {\n return;\n }\n try {\n const result = await hubHttp.listAccountInvitations(new Context());\n setCache((prev) => ({ ...prev, invitations: result.invitations, fetchedAt: Date.now() }));\n } catch {\n // Offline: keep cache.\n }\n }, [hubHttp, setCache]);\n\n const handleIssue = useCallback(async () => {\n if (!hubHttp) {\n return;\n }\n setPending(true);\n try {\n const result = await hubHttp.issueAccountInvitation(new Context());\n // Optimistically push the new code and decrement the remaining quota; the\n // server consumes one slot at issue time. Next refresh reconciles.\n setCache((prev) => ({\n ...prev,\n account: prev.account\n ? { ...prev.account, invitationsRemaining: Math.max(0, prev.account.invitationsRemaining - 1) }\n : prev.account,\n invitations: [{ code: result.code, createdAt: new Date().toISOString() }, ...(prev.invitations ?? [])],\n }));\n } finally {\n setPending(false);\n }\n }, [hubHttp, setCache]);\n\n const remaining = cache.account?.invitationsRemaining ?? 0;\n const list = cache.invitations ?? [];\n const available = list.filter((row) => !row.redeemedByIdentityDid);\n const redeemed = list.filter((row) => Boolean(row.redeemedByIdentityDid));\n\n return (\n <Clipboard.Provider>\n <Settings.Viewport>\n <Settings.Section title={t('invitations-section.title')} description={t('invitations-section.description')}>\n <Settings.Item\n title={t('generate-invitation.label')}\n description={t('generate-invitation.description', { count: remaining })}\n >\n <IconButton\n icon='ph--plus--regular'\n label={t('generate-invitation.label')}\n variant='primary'\n onClick={handleIssue}\n disabled={pending || remaining <= 0}\n />\n </Settings.Item>\n </Settings.Section>\n\n {available.length > 0 ? (\n <Settings.Section title={t('available-invitations.title')}>\n <List>\n {available.map((row) => (\n <AvailableInvitationItem key={row.code} row={row} />\n ))}\n </List>\n </Settings.Section>\n ) : null}\n\n {redeemed.length > 0 ? (\n <Settings.Section title={t('redeemed-invitations.title')}>\n <List>\n {redeemed.map((row) => (\n <RedeemedInvitationItem key={row.code} row={row} />\n ))}\n </List>\n </Settings.Section>\n ) : null}\n </Settings.Viewport>\n </Clipboard.Provider>\n );\n};\n\nconst AvailableInvitationItem = ({ row }: { row: AccountCacheInvitation }) => (\n <ListItem.Root classNames='grid grid-cols-[min-content_1fr_min-content] items-center gap-2'>\n <ListItem.Endcap>\n <Icon icon='ph--paper-plane-tilt--duotone' size={5} classNames='text-description' />\n </ListItem.Endcap>\n <div className='flex flex-col min-w-0'>\n <ListItem.Heading classNames='font-mono truncate'>{row.code}</ListItem.Heading>\n <p className='text-description text-xs'>{new Date(row.createdAt).toLocaleString()}</p>\n </div>\n <ListItem.Endcap>\n <Clipboard.IconButton value={row.code} />\n </ListItem.Endcap>\n </ListItem.Root>\n);\n\nconst RedeemedInvitationItem = ({ row }: { row: AccountCacheInvitation }) => {\n const date = row.redeemedAt ?? row.createdAt;\n return (\n <ListItem.Root classNames='grid grid-cols-[min-content_1fr] items-center gap-2'>\n <ListItem.Endcap>\n <Icon icon='ph--check-circle--duotone' size={5} classNames='text-success-text' />\n </ListItem.Endcap>\n <div className='flex flex-col min-w-0'>\n <ListItem.Heading classNames='font-mono truncate'>{row.code}</ListItem.Heading>\n <p className='text-description text-xs'>{new Date(date).toLocaleString()}</p>\n </div>\n </ListItem.Root>\n );\n};\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["useAtom", "useAtomSet", "React", "useCallback", "useState", "useCapability", "Context", "Clipboard", "Icon", "IconButton", "List", "ListItem", "useAsyncEffect", "useTranslation", "Settings", "meta", "ClientCapabilities", "InvitationsContainer", "accountCacheAtom", "useCapability", "cache", "setCache", "useAtomSet", "pending", "setPending", "hubHttp", "useHubHttpClient", "invitations", "fetchedAt", "result", "account", "prev", "invitationsRemaining", "code", "available", "list", "filter", "row", "redeemedByIdentityDid", "redeemed", "Clipboard", "title", "Settings", "t", "count", "remaining", "icon", "variant", "onClick", "handleIssue", "disabled", "List", "key", "map", "AvailableInvitationItem", "classNames", "className", "ListItem", "Heading", "p", "Date", "toLocaleString", "value", "RedeemedInvitationItem", "Endcap"]
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/containers/ProfileContainer/ProfileContainer.tsx"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Schema from 'effect/Schema';\nimport React, { type ChangeEvent, useCallback, useMemo, useState } from 'react';\n\nimport { debounce } from '@dxos/async';\nimport { useClient } from '@dxos/react-client';\nimport { type Identity, useIdentity } from '@dxos/react-client/halo';\nimport { ButtonGroup, Clipboard, Input, useTranslation } from '@dxos/react-ui';\nimport { Form, type FormFieldMap, type FormUpdateMeta, Settings } from '@dxos/react-ui-form';\nimport { EmojiPickerBlock, HuePicker } from '@dxos/react-ui-pickers';\nimport { hexToEmoji, hexToHue } from '@dxos/util';\n\nimport { meta } from '#meta';\n\n// TOOD(burdon): Factor out?\n// TODO(wittjosiah): Integrate annotations with translations.\nconst UserProfile = Schema.Struct({\n did: Schema.String.annotations({ title: 'DID' }),\n displayName: Schema.String.annotations({ title: 'Display name' }),\n emoji: Schema.String.annotations({ title: 'Avatar' }),\n hue: Schema.String.annotations({ title: 'Color' }),\n});\n\ntype UserProfile = Schema.Schema.Type<typeof UserProfile>;\n\n// TODO(thure): Factor out?\nconst getDefaultHueValue = (identity: Identity | null) => hexToHue(identity?.identityKey.toHex() ?? '0');\nconst getHueValue = (identity: Identity | null) => identity?.profile?.data?.hue || getDefaultHueValue(identity);\nconst getDefaultEmojiValue = (identity: Identity | null) => hexToEmoji(identity?.identityKey.toHex() ?? '0');\nconst getEmojiValue = (identity: Identity | null) => identity?.profile?.data?.emoji || getDefaultEmojiValue(identity);\n\nexport const ProfileContainer = () => {\n const { t } = useTranslation(meta.profile.key);\n const client = useClient();\n const identity = useIdentity();\n const [displayName, setDisplayNameDirectly] = useState(identity?.profile?.displayName ?? '');\n const [emoji, setEmojiDirectly] = useState<string>(getEmojiValue(identity));\n const [hue, setHueDirectly] = useState<string>(getHueValue(identity));\n\n const updateProfile = useMemo(\n () =>\n debounce(\n (profile: Partial<UserProfile>) =>\n client.halo.updateProfile({\n displayName: profile.displayName,\n data: {\n emoji: profile.emoji,\n hue: profile.hue,\n },\n }),\n 2_000,\n ),\n [],\n );\n\n const handleChange = useCallback(\n (profile: Partial<UserProfile>, meta: FormUpdateMeta<UserProfile>) => {\n for (const [path, changed] of Object.entries(meta.changed)) {\n if (changed) {\n switch (path) {\n case 'displayName':\n setDisplayNameDirectly(profile.displayName ?? '');\n break;\n case 'emoji':\n setEmojiDirectly(profile.emoji ?? getDefaultEmojiValue(identity));\n break;\n case 'hue':\n setHueDirectly(profile.hue ?? getDefaultHueValue(identity));\n break;\n default:\n break;\n }\n }\n }\n\n void updateProfile(profile);\n },\n [identity],\n );\n\n const values = useMemo(\n () => ({\n did: identity?.did,\n displayName,\n emoji,\n hue,\n }),\n [identity, displayName, emoji, hue],\n );\n\n // TODO(wittjosiah): Integrate descriptions with the form schema.\n const fieldMap = useMemo<FormFieldMap>(\n () => ({\n displayName: ({ type, label, getValue, onValueChange }) => {\n const handleChange = useCallback(\n ({ target: { value } }: ChangeEvent<HTMLInputElement>) => onValueChange(type, value),\n [onValueChange, type],\n );\n\n return (\n <Settings.Item title={label} description={t('display-name.description')}>\n <Input.TextInput\n value={getValue()}\n onChange={handleChange}\n placeholder={t('display-name-input.placeholder')}\n classNames='min-w-64'\n />\n </Settings.Item>\n );\n },\n emoji: ({ type, label, getValue, onValueChange }) => {\n const handleChange = useCallback((nextEmoji: string) => onValueChange(type, nextEmoji), [onValueChange, type]);\n const handleEmojiReset = useCallback(\n () => onValueChange(type, getDefaultEmojiValue(identity)),\n [onValueChange, type],\n );\n\n return (\n <Settings.Item title={label} description={t('icon.description')}>\n <EmojiPickerBlock\n triggerVariant='default'\n emoji={getValue()}\n onChangeEmoji={handleChange}\n onClickClear={handleEmojiReset}\n classNames='justify-self-end'\n />\n </Settings.Item>\n );\n },\n hue: ({ type, label, getValue, onValueChange }) => {\n const handleChange = useCallback((nextHue: string) => onValueChange(type, nextHue), [onValueChange, type]);\n const handleHueReset = useCallback(\n () => onValueChange(type, getDefaultHueValue(identity)),\n [onValueChange, type],\n );\n\n return (\n <Settings.Item title={label} description={t('hue.description')}>\n <div className='flex justify-self-end'>\n <HuePicker value={getValue()} onChange={handleChange} onReset={handleHueReset} />\n </div>\n </Settings.Item>\n );\n },\n // TODO(wittjosiah): We need text input annotations for disabled and copyable.\n did: ({ label, getValue }) => {\n return (\n <Settings.Item title={label} description={t('did.description')}>\n <ButtonGroup classNames='w-full'>\n <Input.TextInput value={getValue()} disabled classNames='min-w-64' />\n <Clipboard.IconButton value={getValue() ?? ''} />\n </ButtonGroup>\n </Settings.Item>\n );\n },\n }),\n [t],\n );\n\n return (\n <Clipboard.Provider>\n <Settings.Viewport>\n <Settings.Section title={t('profile.label')} description={t('profile.description')}>\n <Form.Root schema={UserProfile} values={values} fieldMap={fieldMap} onValuesChanged={handleChange}>\n <Form.Content classNames='gap-form-gap'>\n <Form.FieldSet />\n </Form.Content>\n </Form.Root>\n </Settings.Section>\n </Settings.Viewport>\n </Clipboard.Provider>\n );\n};\n"],
|
|
5
|
-
"mappings": ";;;AAIA,YAAYA,YAAY;AACxB,OAAOC,SAA2BC,aAAaC,SAASC,gBAAgB;AAExE,SAASC,gBAAgB;AACzB,SAASC,iBAAiB;AAC1B,SAAwBC,mBAAmB;AAC3C,SAASC,aAAaC,WAAWC,OAAOC,sBAAsB;AAC9D,SAASC,MAA8CC,gBAAgB;AACvE,SAASC,kBAAkBC,iBAAiB;AAC5C,SAASC,YAAYC,gBAAgB;AAErC,SAASC,YAAY;AAIrB,IAAMC,cAAqBC,cAAO;EAChCC,KAAYC,cAAOC,YAAY;IAAEC,OAAO;EAAM,CAAA;EAC9CC,aAAoBH,cAAOC,YAAY;IAAEC,OAAO;EAAe,CAAA;EAC/DE,OAAcJ,cAAOC,YAAY;IAAEC,OAAO;EAAS,CAAA;EACnDG,KAAYL,cAAOC,YAAY;IAAEC,OAAO;EAAQ,CAAA;AAClD,CAAA;AAKA,IAAMI,qBAAqB,CAACC,aAA8BZ,SAASY,UAAUC,YAAYC,MAAAA,KAAW,GAAA;AACpG,IAAMC,cAAc,CAACH,aAA8BA,UAAUI,SAASC,MAAMP,OAAOC,mBAAmBC,QAAAA;AACtG,IAAMM,uBAAuB,CAACN,aAA8Bb,WAAWa,UAAUC,YAAYC,MAAAA,KAAW,GAAA;AACxG,IAAMK,gBAAgB,CAACP,aAA8BA,UAAUI,SAASC,MAAMR,SAASS,qBAAqBN,QAAAA;AAErG,IAAMQ,mBAAmB,MAAA;AAC9B,QAAM,EAAEC,EAAC,IAAK3B,eAAeO,KAAKe,QAAQM,GAAG;AAC7C,QAAMC,SAASlC,UAAAA;AACf,QAAMuB,WAAWtB,YAAAA;AACjB,QAAM,CAACkB,aAAagB,sBAAAA,IAA0BrC,SAASyB,UAAUI,SAASR,eAAe,EAAA;AACzF,QAAM,CAACC,OAAOgB,gBAAAA,IAAoBtC,SAAiBgC,cAAcP,QAAAA,CAAAA;AACjE,QAAM,CAACF,KAAKgB,cAAAA,IAAkBvC,SAAiB4B,YAAYH,QAAAA,CAAAA;AAE3D,QAAMe,gBAAgBzC,QACpB,MACEE,SACE,CAAC4B,YACCO,OAAOK,KAAKD,cAAc;IACxBnB,aAAaQ,QAAQR;IACrBS,MAAM;MACJR,OAAOO,QAAQP;MACfC,KAAKM,QAAQN;IACf;EACF,CAAA,GACF,GAAA,GAEJ,CAAA,CAAE;AAGJ,QAAMmB,eAAe5C,YACnB,CAAC+B,SAA+Bf,UAAAA;AAC9B,eAAW,CAAC6B,MAAMC,OAAAA,KAAYC,OAAOC,QAAQhC,MAAK8B,OAAO,GAAG;AAC1D,UAAIA,SAAS;AACX,gBAAQD,MAAAA;UACN,KAAK;AACHN,mCAAuBR,QAAQR,eAAe,EAAA;AAC9C;UACF,KAAK;AACHiB,6BAAiBT,QAAQP,SAASS,qBAAqBN,QAAAA,CAAAA;AACvD;UACF,KAAK;AACHc,2BAAeV,QAAQN,OAAOC,mBAAmBC,QAAAA,CAAAA;AACjD;UACF;AACE;QACJ;MACF;IACF;AAEA,SAAKe,cAAcX,OAAAA;EACrB,GACA;IAACJ;GAAS;AAGZ,QAAMsB,SAAShD,QACb,OAAO;IACLkB,KAAKQ,UAAUR;IACfI;IACAC;IACAC;EACF,IACA;IAACE;IAAUJ;IAAaC;IAAOC;GAAI;AAIrC,QAAMyB,WAAWjD,QACf,OAAO;IACLsB,aAAa,CAAC,EAAE4B,MAAMC,OAAOC,UAAUC,cAAa,MAAE;AACpD,YAAMV,gBAAe5C,YACnB,CAAC,EAAEuD,QAAQ,EAAEC,MAAK,EAAE,MAAsCF,cAAcH,MAAMK,KAAAA,GAC9E;QAACF;QAAeH;OAAK;AAGvB,aACE,sBAAA,cAACxC,SAAS8C,MAAI;QAACnC,OAAO8B;QAAOM,aAAatB,EAAE,0BAAA;SAC1C,sBAAA,cAAC5B,MAAMmD,WAAS;QACdH,OAAOH,SAAAA;QACPO,UAAUhB;QACViB,aAAazB,EAAE,gCAAA;QACf0B,YAAW;;IAInB;IACAtC,OAAO,CAAC,EAAE2B,MAAMC,OAAOC,UAAUC,cAAa,MAAE;AAC9C,YAAMV,gBAAe5C,YAAY,CAAC+D,cAAsBT,cAAcH,MAAMY,SAAAA,GAAY;QAACT;QAAeH;OAAK;AAC7G,YAAMa,mBAAmBhE,YACvB,MAAMsD,cAAcH,MAAMlB,qBAAqBN,QAAAA,CAAAA,GAC/C;QAAC2B;QAAeH;OAAK;AAGvB,aACE,sBAAA,cAACxC,SAAS8C,MAAI;QAACnC,OAAO8B;QAAOM,aAAatB,EAAE,kBAAA;SAC1C,sBAAA,cAACxB,kBAAAA;QACCqD,gBAAe;QACfzC,OAAO6B,SAAAA;QACPa,eAAetB;QACfuB,cAAcH;QACdF,YAAW;;IAInB;IACArC,KAAK,CAAC,EAAE0B,MAAMC,OAAOC,UAAUC,cAAa,MAAE;AAC5C,YAAMV,gBAAe5C,YAAY,CAACoE,YAAoBd,cAAcH,MAAMiB,OAAAA,GAAU;QAACd;QAAeH;OAAK;AACzG,YAAMkB,iBAAiBrE,YACrB,MAAMsD,cAAcH,MAAMzB,mBAAmBC,QAAAA,CAAAA,GAC7C;QAAC2B;QAAeH;OAAK;AAGvB,aACE,sBAAA,cAACxC,SAAS8C,MAAI;QAACnC,OAAO8B;QAAOM,aAAatB,EAAE,iBAAA;SAC1C,sBAAA,cAACkC,OAAAA;QAAIC,WAAU;SACb,sBAAA,cAAC1D,WAAAA;QAAU2C,OAAOH,SAAAA;QAAYO,UAAUhB;QAAc4B,SAASH;;IAIvE;;IAEAlD,KAAK,CAAC,EAAEiC,OAAOC,SAAQ,MAAE;AACvB,aACE,sBAAA,cAAC1C,SAAS8C,MAAI;QAACnC,OAAO8B;QAAOM,aAAatB,EAAE,iBAAA;SAC1C,sBAAA,cAAC9B,aAAAA;QAAYwD,YAAW;SACtB,sBAAA,cAACtD,MAAMmD,WAAS;QAACH,OAAOH,SAAAA;QAAYoB,UAAAA;QAASX,YAAW;UACxD,sBAAA,cAACvD,UAAUmE,YAAU;QAAClB,OAAOH,SAAAA,KAAc;;IAInD;EACF,IACA;IAACjB;GAAE;AAGL,SACE,sBAAA,cAAC7B,UAAUoE,UAAQ,MACjB,sBAAA,cAAChE,SAASiE,UAAQ,MAChB,sBAAA,cAACjE,SAASkE,SAAO;IAACvD,OAAOc,EAAE,eAAA;IAAkBsB,aAAatB,EAAE,qBAAA;KAC1D,sBAAA,cAAC1B,KAAKoE,MAAI;IAACC,QAAQ9D;IAAagC;IAAgBC;IAAoB8B,iBAAiBpC;KACnF,sBAAA,cAAClC,KAAKuE,SAAO;IAACnB,YAAW;KACvB,sBAAA,cAACpD,KAAKwE,UAAQ,IAAA,CAAA,CAAA,CAAA,CAAA,CAAA;AAO5B;",
|
|
6
|
-
"names": ["Schema", "React", "useCallback", "useMemo", "useState", "debounce", "useClient", "useIdentity", "ButtonGroup", "Clipboard", "Input", "useTranslation", "Form", "Settings", "EmojiPickerBlock", "HuePicker", "hexToEmoji", "hexToHue", "meta", "UserProfile", "Struct", "did", "String", "annotations", "title", "displayName", "emoji", "hue", "getDefaultHueValue", "identity", "identityKey", "toHex", "getHueValue", "profile", "data", "getDefaultEmojiValue", "getEmojiValue", "ProfileContainer", "t", "key", "client", "setDisplayNameDirectly", "setEmojiDirectly", "setHueDirectly", "updateProfile", "halo", "handleChange", "path", "changed", "Object", "entries", "values", "fieldMap", "type", "label", "getValue", "onValueChange", "target", "value", "Item", "description", "TextInput", "onChange", "placeholder", "classNames", "nextEmoji", "handleEmojiReset", "triggerVariant", "onChangeEmoji", "onClickClear", "nextHue", "handleHueReset", "div", "className", "onReset", "disabled", "IconButton", "Provider", "Viewport", "Section", "Root", "schema", "onValuesChanged", "Content", "FieldSet"]
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/containers/RecoveryCredentialsContainer/RecoveryCredentialsContainer.tsx"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2024 DXOS.org\n//\n\nimport React from 'react';\n\nimport { useOperationInvoker } from '@dxos/app-framework/ui';\nimport { useCredentials } from '@dxos/react-client/halo';\nimport { Icon, IconButton, List, ListItem, Message, useTranslation } from '@dxos/react-ui';\nimport { Settings } from '@dxos/react-ui-form';\n\nimport { meta } from '#meta';\nimport { ClientOperation } from '#operations';\n\nexport const MANAGE_CREDENTIALS_DIALOG = `${meta.profile.key}.ManageCredentialsDialog`;\n\nexport const RecoveryCredentialsContainer = () => {\n const { t } = useTranslation(meta.profile.key);\n const { invokePromise } = useOperationInvoker();\n const credentials = useCredentials();\n const recoveryCredentials = credentials.filter(\n (credential) => credential.subject.assertion['@type'] === 'dxos.halo.credentials.IdentityRecovery',\n );\n\n return (\n <Settings.Viewport>\n <Settings.Section title={t('recovery-setup-dialog.title')} description={t('recovery-setup-dialog.description')}>\n <Settings.Item title={t('create-passkey.label')} description={t('create-passkey.description')}>\n <IconButton\n label={t('create-passkey.label')}\n icon='ph--key--duotone'\n variant='primary'\n onClick={() => invokePromise(ClientOperation.CreatePasskey)}\n />\n </Settings.Item>\n <Settings.Item title={t('create-recovery-code.label')} description={t('create-recovery-code.description')}>\n <IconButton\n label={t('create-recovery-code.label')}\n icon='ph--receipt--duotone'\n variant='default'\n onClick={() => invokePromise(ClientOperation.CreateRecoveryCode)}\n />\n </Settings.Item>\n </Settings.Section>\n <Settings.Section title={t('credentials-list.label')}>\n {recoveryCredentials.length < 1 ? (\n <Message.Root valence='error'>\n <Message.Title icon='ph--shield-warning--duotone'>{t('no-credentials.title')}</Message.Title>\n <Message.Content>{t('no-credentials.message')}</Message.Content>\n </Message.Root>\n ) : (\n <List>\n {recoveryCredentials.map((credential) => (\n <ListItem.Root key={credential.id?.toHex()}>\n <ListItem.Endcap>\n <Icon icon='ph--key--regular' />\n </ListItem.Endcap>\n <ListItem.Heading>{credential.issuanceDate.toLocaleString()}</ListItem.Heading>\n </ListItem.Root>\n ))}\n </List>\n )}\n </Settings.Section>\n </Settings.Viewport>\n );\n};\n"],
|
|
5
|
-
"mappings": ";;;AAIA,OAAOA,WAAW;AAElB,SAASC,2BAA2B;AACpC,SAASC,sBAAsB;AAC/B,SAASC,MAAMC,YAAYC,MAAMC,UAAUC,SAASC,sBAAsB;AAC1E,SAASC,gBAAgB;AAEzB,SAASC,YAAY;AACrB,SAASC,uBAAuB;AAEzB,IAAMC,4BAA4B,GAAGF,KAAKG,QAAQC,GAAG;AAErD,IAAMC,+BAA+B,MAAA;AAC1C,QAAM,EAAEC,EAAC,IAAKR,eAAeE,KAAKG,QAAQC,GAAG;AAC7C,QAAM,EAAEG,cAAa,IAAKhB,oBAAAA;AAC1B,QAAMiB,cAAchB,eAAAA;AACpB,QAAMiB,sBAAsBD,YAAYE,OACtC,CAACC,eAAeA,WAAWC,QAAQC,UAAU,OAAA,MAAa,wCAAA;AAG5D,SACE,sBAAA,cAACd,SAASe,UAAQ,MAChB,sBAAA,cAACf,SAASgB,SAAO;IAACC,OAAOV,EAAE,6BAAA;IAAgCW,aAAaX,EAAE,mCAAA;KACxE,sBAAA,cAACP,SAASmB,MAAI;IAACF,OAAOV,EAAE,sBAAA;IAAyBW,aAAaX,EAAE,4BAAA;KAC9D,sBAAA,cAACZ,YAAAA;IACCyB,OAAOb,EAAE,sBAAA;IACTc,MAAK;IACLC,SAAQ;IACRC,SAAS,MAAMf,cAAcN,gBAAgBsB,aAAa;OAG9D,sBAAA,cAACxB,SAASmB,MAAI;IAACF,OAAOV,EAAE,4BAAA;IAA+BW,aAAaX,EAAE,kCAAA;KACpE,sBAAA,cAACZ,YAAAA;IACCyB,OAAOb,EAAE,4BAAA;IACTc,MAAK;IACLC,SAAQ;IACRC,SAAS,MAAMf,cAAcN,gBAAgBuB,kBAAkB;QAIrE,sBAAA,cAACzB,SAASgB,SAAO;IAACC,OAAOV,EAAE,wBAAA;KACxBG,oBAAoBgB,SAAS,IAC5B,sBAAA,cAAC5B,QAAQ6B,MAAI;IAACC,SAAQ;KACpB,sBAAA,cAAC9B,QAAQ+B,OAAK;IAACR,MAAK;KAA+Bd,EAAE,sBAAA,CAAA,GACrD,sBAAA,cAACT,QAAQgC,SAAO,MAAEvB,EAAE,wBAAA,CAAA,CAAA,IAGtB,sBAAA,cAACX,MAAAA,MACEc,oBAAoBqB,IAAI,CAACnB,eACxB,sBAAA,cAACf,SAAS8B,MAAI;IAACtB,KAAKO,WAAWoB,IAAIC,MAAAA;KACjC,sBAAA,cAACpC,SAASqC,QAAM,MACd,sBAAA,cAACxC,MAAAA;IAAK2B,MAAK;OAEb,sBAAA,cAACxB,SAASsC,SAAO,MAAEvB,WAAWwB,aAAaC,eAAc,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;AAQzE;",
|
|
6
|
-
"names": ["React", "useOperationInvoker", "useCredentials", "Icon", "IconButton", "List", "ListItem", "Message", "useTranslation", "Settings", "meta", "ClientOperation", "MANAGE_CREDENTIALS_DIALOG", "profile", "key", "RecoveryCredentialsContainer", "t", "invokePromise", "credentials", "recoveryCredentials", "filter", "credential", "subject", "assertion", "Viewport", "Section", "title", "description", "Item", "label", "icon", "variant", "onClick", "CreatePasskey", "CreateRecoveryCode", "length", "Root", "valence", "Title", "Content", "map", "id", "toHex", "Endcap", "Heading", "issuanceDate", "toLocaleString"]
|
|
7
|
-
}
|