dsh-subscriptions 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +64 -0
  2. package/lib/auth/rpc.d.ts +3 -1
  3. package/lib/auth/rpc.js +10 -1
  4. package/lib/client/ProviderAccountManager.js +34 -41
  5. package/lib/client/ProviderModelEditor.js +5 -9
  6. package/lib/client/SpeedSelect.js +8 -7
  7. package/lib/client/SubscriptionUsageBadge.js +6 -5
  8. package/lib/client/SubscriptionsSection.d.ts +22 -0
  9. package/lib/client/SubscriptionsSection.js +128 -38
  10. package/lib/client/UsageBadgeDisplaySetting.js +4 -8
  11. package/lib/client/host-contract.d.ts +1 -1
  12. package/lib/client/host-contract.js +22 -0
  13. package/lib/client/locales.d.ts +58 -0
  14. package/lib/client/locales.js +58 -0
  15. package/lib/client/provider-settings-styles.d.ts +3 -0
  16. package/lib/client/provider-settings-styles.js +206 -0
  17. package/lib/client/reset-action.d.ts +14 -0
  18. package/lib/client/reset-action.js +15 -0
  19. package/lib/client/reset-credits-view.d.ts +53 -0
  20. package/lib/client/reset-credits-view.js +103 -0
  21. package/lib/client.js +937 -310
  22. package/lib/client.js.map +1 -1
  23. package/lib/index.d.ts +9 -0
  24. package/lib/index.js +1099 -608
  25. package/lib/providers/account-preferences.js +3 -2
  26. package/lib/providers/catalog-store.d.ts +3 -2
  27. package/lib/providers/catalog-store.js +57 -26
  28. package/lib/providers/claude.d.ts +22 -3
  29. package/lib/providers/claude.js +113 -15
  30. package/lib/providers/codex.d.ts +15 -2
  31. package/lib/providers/codex.js +72 -4
  32. package/lib/providers/common.d.ts +25 -0
  33. package/lib/providers/common.js +22 -18
  34. package/lib/providers/copilot.d.ts +2 -1
  35. package/lib/providers/copilot.js +10 -3
  36. package/lib/providers/grok.d.ts +2 -2
  37. package/lib/providers/grok.js +2 -2
  38. package/lib/providers/pool-health.d.ts +2 -0
  39. package/lib/providers/pool-health.js +9 -0
  40. package/lib/providers/pool-usage.d.ts +13 -1
  41. package/lib/providers/pool-usage.js +39 -7
  42. package/lib/providers/pool.d.ts +7 -5
  43. package/lib/providers/pool.js +89 -12
  44. package/lib/providers/rate-limit.d.ts +0 -14
  45. package/lib/providers/rate-limit.js +0 -26
  46. package/lib/providers/replay.d.ts +4 -0
  47. package/lib/providers/replay.js +50 -0
  48. package/lib/providers/reset-redemption.d.ts +51 -0
  49. package/lib/providers/reset-redemption.js +102 -0
  50. package/lib/translate/anthropic.d.ts +21 -2
  51. package/lib/translate/anthropic.js +35 -11
  52. package/lib/translate/antigravity.js +13 -6
  53. package/lib/translate/chat-completions.js +1 -1
  54. package/lib/translate/resolved.d.ts +21 -0
  55. package/lib/translate/resolved.js +63 -3
  56. package/lib/translate/responses.js +8 -6
  57. package/lib/translate/sse.js +8 -0
  58. package/package.json +1 -1
@@ -0,0 +1,206 @@
1
+ /**
2
+ * The host loads one JS client bundle, so keep this small, scoped stylesheet
3
+ * with the dialog instead of relying on a separate CSS asset. Geometry and
4
+ * theme tokens follow the host's settings-form and ui-primitives controls.
5
+ *
6
+ * `color-scheme` is left to the host deliberately. Its boot script sets the
7
+ * property on `:root`, and it inherits, so native controls inside these panels
8
+ * follow the theme. Upstream also pins `color-scheme: light` here and flips it
9
+ * back under `body[data-ds-dark-theme]`; without that flip our scoped rules are
10
+ * more specific than `:root` and would force every native control light inside
11
+ * a dark theme.
12
+ */
13
+ const formScope = ':is(.dsh-subscription-manager, .dsh-subscriptions-settings)';
14
+ export const providerSettingsCss = `
15
+ .dsh-subscription-manager {
16
+ box-sizing: border-box;
17
+ padding: 0;
18
+ border: 0;
19
+ border-radius: var(--dsw-radius-panel, 16px);
20
+ background: var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-layer-1));
21
+ color: var(--dsw-alias-label-primary);
22
+ box-shadow: var(--dsw-elevation-prominent, 0 20px 70px #0004);
23
+ font-family: inherit;
24
+ font-size: 13px;
25
+ line-height: 1.5;
26
+ }
27
+ .dsh-subscriptions-settings {
28
+ font-family: inherit;
29
+ font-size: 13px;
30
+ line-height: 1.5;
31
+ }
32
+ .dsh-subscription-manager::backdrop {
33
+ background: var(--dsw-alias-bg-mask-1, #0006);
34
+ backdrop-filter: var(--dsw-mask-blur, none);
35
+ }
36
+ ${formScope} h2 {
37
+ font-size: 16px;
38
+ line-height: 24px;
39
+ font-weight: 500;
40
+ }
41
+ ${formScope} h3 {
42
+ font-size: 14px;
43
+ line-height: 22px;
44
+ font-weight: 500;
45
+ }
46
+ ${formScope} strong,
47
+ ${formScope} .dsh-subscription-label {
48
+ font-size: 13px;
49
+ line-height: 1.5;
50
+ font-weight: 500;
51
+ }
52
+ ${formScope} legend {
53
+ font-size: 14px;
54
+ line-height: 22px;
55
+ font-weight: 500;
56
+ }
57
+ ${formScope} small,
58
+ ${formScope} .dsh-subscription-hint,
59
+ ${formScope} [role="status"] {
60
+ margin: 0;
61
+ font-size: 12px;
62
+ line-height: 1.5;
63
+ color: var(--dsw-alias-label-tertiary);
64
+ }
65
+ ${formScope} [role="alert"] {
66
+ margin: 0;
67
+ font-size: 12px;
68
+ line-height: 1.5;
69
+ color: var(--dsw-alias-state-error-primary, #b42318);
70
+ }
71
+ ${formScope} input:not([type="checkbox"]),
72
+ ${formScope} select {
73
+ box-sizing: border-box;
74
+ min-width: 0;
75
+ height: 34px;
76
+ padding: 0 12px;
77
+ border: 0.5px solid var(--dsw-alias-border-l4, var(--dsw-alias-border-l2));
78
+ border-radius: var(--dsw-radius-md, 8px);
79
+ background: var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-layer-1));
80
+ color: var(--dsw-alias-label-primary);
81
+ font: inherit;
82
+ font-size: 13px;
83
+ font-weight: 400;
84
+ line-height: 1.5;
85
+ }
86
+ ${formScope} input::placeholder {
87
+ color: var(--dsw-alias-label-tertiary);
88
+ opacity: 1;
89
+ }
90
+ ${formScope} input:not([type="checkbox"]):focus-visible,
91
+ ${formScope} select:focus-visible {
92
+ outline: none;
93
+ border-color: var(--dsw-alias-state-business-primary, var(--dsw-alias-brand-primary));
94
+ }
95
+ ${formScope} input:not([type="checkbox"]):disabled,
96
+ ${formScope} select:disabled {
97
+ color: var(--dsw-alias-label-tertiary);
98
+ cursor: default;
99
+ }
100
+ ${formScope} input[aria-invalid="true"] {
101
+ border-color: var(--dsw-alias-state-error-primary, #b42318);
102
+ }
103
+ ${formScope} option {
104
+ background: var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-layer-1));
105
+ color: var(--dsw-alias-label-primary);
106
+ }
107
+ ${formScope} label:has(> input[type="checkbox"]) {
108
+ display: flex;
109
+ align-items: flex-start;
110
+ gap: 6px;
111
+ font-size: 14px;
112
+ line-height: 20px;
113
+ cursor: pointer;
114
+ }
115
+ ${formScope} input[type="checkbox"] {
116
+ flex: 0 0 auto;
117
+ width: 16px;
118
+ height: 16px;
119
+ margin: 2px 0;
120
+ accent-color: var(--dsw-alias-brand-primary);
121
+ cursor: inherit;
122
+ }
123
+ ${formScope} label:has(> input[type="checkbox"]:disabled) {
124
+ cursor: default;
125
+ opacity: 0.5;
126
+ }
127
+ ${formScope} button {
128
+ box-sizing: border-box;
129
+ display: inline-flex;
130
+ align-items: center;
131
+ justify-content: center;
132
+ flex-shrink: 0;
133
+ min-height: 28px;
134
+ padding: 0 10px;
135
+ border: 0.5px solid var(--dsw-alias-border-l3, var(--dsw-alias-border-l2));
136
+ border-radius: var(--dsw-radius-sm, 6px);
137
+ background: transparent;
138
+ color: var(--dsw-alias-label-primary);
139
+ font: inherit;
140
+ font-size: 12px;
141
+ line-height: 18px;
142
+ cursor: pointer;
143
+ }
144
+ ${formScope} button:hover:not(:disabled) {
145
+ background: var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-layer-3));
146
+ }
147
+ ${formScope} button:active:not(:disabled) {
148
+ background: var(--dsw-alias-interactive-bg-active, var(--dsw-alias-bg-layer-3));
149
+ }
150
+ ${formScope} button:disabled {
151
+ opacity: 0.4;
152
+ cursor: default;
153
+ }
154
+ ${formScope} button.dsh-subscription-quiet {
155
+ border-color: transparent;
156
+ color: var(--dsw-alias-label-secondary, var(--dsw-alias-label-primary));
157
+ }
158
+ ${formScope} :is(footer, .dsh-subscription-dialog-actions) button {
159
+ min-height: 34px;
160
+ padding: 0 14px;
161
+ border-radius: var(--dsw-radius-md, 8px);
162
+ font-size: 13px;
163
+ line-height: 1.5;
164
+ }
165
+ ${formScope} button.dsh-subscription-primary {
166
+ border-color: transparent;
167
+ background: var(--dsw-alias-button-primary-fill, var(--dsw-alias-label-primary));
168
+ color: var(--dsw-alias-label-primary-foreground, var(--dsw-alias-bg-layer-1));
169
+ font-weight: 500;
170
+ }
171
+ ${formScope} button.dsh-subscription-primary:hover:not(:disabled) {
172
+ background: var(--dsw-alias-button-primary-hover, var(--dsw-alias-label-secondary));
173
+ }
174
+ ${formScope} button:focus-visible,
175
+ ${formScope} input[type="checkbox"]:focus-visible,
176
+ ${formScope} summary:focus-visible {
177
+ outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
178
+ outline-offset: 2px;
179
+ }
180
+ ${formScope} summary {
181
+ border-radius: var(--dsw-radius-sm, 6px);
182
+ }
183
+ ${formScope} summary:hover {
184
+ color: var(--dsw-alias-label-secondary, var(--dsw-alias-label-primary));
185
+ }
186
+ `;
187
+ /** Composer controls retain their own geometry, separate from settings forms. */
188
+ export const subscriptionChromeCss = `
189
+ .dsh-subscription-usage-pill { corner-shape: round; }
190
+ .dsh-subscription-usage-panel {
191
+ --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
192
+ backdrop-filter: var(--dsw-menu-backdrop-filter, none);
193
+ }
194
+ .dsh-subscription-speed button:hover:not(:disabled) {
195
+ --dsh-subscription-speed-hover: var(--dsw-alias-interactive-bg-hover);
196
+ }
197
+ .dsh-subscription-speed button:active:not(:disabled) {
198
+ --dsh-subscription-speed-hover: var(--dsw-alias-interactive-bg-active);
199
+ }
200
+ .dsh-subscription-speed button:disabled { opacity: 0.4; cursor: default; }
201
+ .dsh-subscription-speed button:focus-visible,
202
+ .dsh-subscription-usage-pill:focus-visible {
203
+ outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
204
+ outline-offset: 2px;
205
+ }
206
+ `;
@@ -0,0 +1,14 @@
1
+ /** Explain client-side gating; the server independently revalidates before spending. */
2
+ export declare function resetActionBlock(usage: {
3
+ windows?: {
4
+ kind: string;
5
+ scope?: string;
6
+ usedPercent: number;
7
+ resetsAt?: number;
8
+ }[];
9
+ resetCredits?: {
10
+ id?: string;
11
+ expiresAt?: number;
12
+ }[];
13
+ resetCreditsError?: string;
14
+ }, now?: number): 'resetUseGuard' | 'resetUseRefresh' | 'resetUseRestart' | undefined;
@@ -0,0 +1,15 @@
1
+ /** Explain client-side gating; the server independently revalidates before spending. */
2
+ export function resetActionBlock(usage, now = Date.now()) {
3
+ const weekly = usage.windows?.find(w => w.kind === 'weekly' && w.scope === undefined);
4
+ if (!weekly || !Number.isFinite(weekly.usedPercent))
5
+ return 'resetUseRefresh';
6
+ if (weekly.usedPercent < 80)
7
+ return 'resetUseGuard';
8
+ if (weekly.resetsAt === undefined || weekly.resetsAt <= now || usage.resetCreditsError)
9
+ return 'resetUseRefresh';
10
+ if (!usage.resetCredits?.some(c => c.id))
11
+ return 'resetUseRestart';
12
+ if (!usage.resetCredits.some(c => c.id && (c.expiresAt === undefined || c.expiresAt > now)))
13
+ return 'resetUseRefresh';
14
+ return undefined;
15
+ }
@@ -0,0 +1,53 @@
1
+ import type { ResetCreditView, SubscriptionsSectionInjected } from './SubscriptionsSection.js';
2
+ type Translate = SubscriptionsSectionInjected['t'];
3
+ /** How one grant reads. Color is not the only carrier; the state word is in the row name. */
4
+ export type ResetCreditState = 'available' | 'notUsable' | 'paused' | 'exhausted' | 'expired';
5
+ /**
6
+ * Classify one grant. Expired outranks every other flag. A future
7
+ * `cooldownUntil` or `usableNow: false` is not usable even when `claimable`
8
+ * is true. `claimable` does not change the word: it is not an action.
9
+ */
10
+ export declare function resetCreditState(credit: ResetCreditView, now: number): ResetCreditState;
11
+ /**
12
+ * Headline count. Codex passes one entry per credit, so the count is the
13
+ * list length. Claude passes one entry per grant, so the count is the sum of
14
+ * `resetsLeft` on grants that are neither expired nor paused. A missing
15
+ * `resetsLeft` adds nothing; the row says the count is unavailable.
16
+ */
17
+ export declare function resetCreditsAvailableCount(credits: readonly ResetCreditView[], now: number, mode: 'credits' | 'grants'): number;
18
+ /** Codex Use control. Absent for Claude, which must not render a button. */
19
+ export interface ResetCreditUseAction {
20
+ accountKey: string;
21
+ busy: boolean;
22
+ disabled: boolean;
23
+ title: string;
24
+ label: string;
25
+ onUse: () => void;
26
+ }
27
+ /** Accessible name of one Claude grant row: state, remaining, and expiry, not a control name. */
28
+ export declare function resetCreditRowName(t: Translate, credit: ResetCreditView, now: number): string;
29
+ /** Claude omits an empty grant list; Codex still shows a defined, possibly empty, list. */
30
+ export declare function showsResetCredits(provider: 'codex' | 'claude', credits: readonly ResetCreditView[] | undefined): boolean;
31
+ /** The error line the settings card mounts. Claude never interpolates provider text. */
32
+ export declare function ResetCreditsErrorLine({ provider, message, t }: {
33
+ provider: 'codex' | 'claude';
34
+ message: string;
35
+ t: Translate;
36
+ }): import("react").JSX.Element;
37
+ /** Fixed sentence for Claude. Codex may include the lookup message; Claude must not. */
38
+ export declare function resetCreditsErrorText(provider: 'codex' | 'claude', message: string, t: Translate): string;
39
+ /**
40
+ * The closed disclosure both providers share. Claude rows are plain text:
41
+ * no button, no `role="button"`, and `cursor: default`, including when
42
+ * `claimable` is true.
43
+ */
44
+ export declare function ResetCreditsDisclosure({ mode, credits, t, now, nextCredit, useAction, statusMessage, }: {
45
+ mode: 'credits' | 'grants';
46
+ credits: readonly ResetCreditView[];
47
+ t: Translate;
48
+ now?: number;
49
+ nextCredit?: ResetCreditView;
50
+ useAction?: ResetCreditUseAction;
51
+ statusMessage?: string;
52
+ }): import("react").JSX.Element;
53
+ export {};
@@ -0,0 +1,103 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { subscriptionCardStyles as cardStyles } from './subscription-card-styles.js';
3
+ const STATE_KEYS = {
4
+ available: 'resetCreditAvailable',
5
+ notUsable: 'resetCreditNotUsable',
6
+ paused: 'resetCreditPaused',
7
+ exhausted: 'resetCreditExhausted',
8
+ expired: 'resetCreditExpired',
9
+ };
10
+ /**
11
+ * Classify one grant. Expired outranks every other flag. A future
12
+ * `cooldownUntil` or `usableNow: false` is not usable even when `claimable`
13
+ * is true. `claimable` does not change the word: it is not an action.
14
+ */
15
+ export function resetCreditState(credit, now) {
16
+ if (credit.expiresAt !== undefined && credit.expiresAt <= now)
17
+ return 'expired';
18
+ if (credit.paused === true)
19
+ return 'paused';
20
+ if (credit.resetsLeft === 0)
21
+ return 'exhausted';
22
+ if ((credit.cooldownUntil !== undefined && credit.cooldownUntil > now) || credit.usableNow === false)
23
+ return 'notUsable';
24
+ return 'available';
25
+ }
26
+ /**
27
+ * Headline count. Codex passes one entry per credit, so the count is the
28
+ * list length. Claude passes one entry per grant, so the count is the sum of
29
+ * `resetsLeft` on grants that are neither expired nor paused. A missing
30
+ * `resetsLeft` adds nothing; the row says the count is unavailable.
31
+ */
32
+ export function resetCreditsAvailableCount(credits, now, mode) {
33
+ if (mode === 'credits')
34
+ return credits.length;
35
+ let sum = 0;
36
+ for (const credit of credits) {
37
+ const state = resetCreditState(credit, now);
38
+ if (state === 'expired' || state === 'paused')
39
+ continue;
40
+ if (typeof credit.resetsLeft === 'number' && Number.isFinite(credit.resetsLeft) && credit.resetsLeft > 0) {
41
+ sum += credit.resetsLeft;
42
+ }
43
+ }
44
+ return sum;
45
+ }
46
+ function expiryText(t, credit) {
47
+ return credit.expiresAt === undefined
48
+ ? t('resetCreditExpiryUnknown')
49
+ : t('resetCreditExpires', { date: new Date(credit.expiresAt).toLocaleString() });
50
+ }
51
+ function remainingText(t, credit) {
52
+ const left = credit.resetsLeft;
53
+ const total = credit.resetsTotal;
54
+ const hasLeft = typeof left === 'number' && Number.isFinite(left);
55
+ const hasTotal = typeof total === 'number' && Number.isFinite(total);
56
+ if (hasLeft && hasTotal)
57
+ return t('resetCreditRemaining', { left, total });
58
+ if (hasLeft)
59
+ return t('resetCreditLeft', { left });
60
+ return t('resetCreditRemainingUnknown');
61
+ }
62
+ /** Accessible name of one Claude grant row: state, remaining, and expiry, not a control name. */
63
+ export function resetCreditRowName(t, credit, now) {
64
+ const state = resetCreditState(credit, now);
65
+ const parts = [t('resetCreditFull'), t(STATE_KEYS[state]), remainingText(t, credit), expiryText(t, credit)];
66
+ if (state === 'notUsable' && credit.cooldownUntil !== undefined && credit.cooldownUntil > now) {
67
+ parts.push(t('resetCreditCooldownUntil', { date: new Date(credit.cooldownUntil).toLocaleString() }));
68
+ }
69
+ return parts.join(', ');
70
+ }
71
+ /** Claude omits an empty grant list; Codex still shows a defined, possibly empty, list. */
72
+ export function showsResetCredits(provider, credits) {
73
+ if (provider === 'codex')
74
+ return credits !== undefined;
75
+ return (credits?.length ?? 0) > 0;
76
+ }
77
+ /** The error line the settings card mounts. Claude never interpolates provider text. */
78
+ export function ResetCreditsErrorLine({ provider, message, t }) {
79
+ return _jsx("p", { style: cardStyles.error, children: resetCreditsErrorText(provider, message, t) });
80
+ }
81
+ /** Fixed sentence for Claude. Codex may include the lookup message; Claude must not. */
82
+ export function resetCreditsErrorText(provider, message, t) {
83
+ return provider === 'claude' ? t('resetCreditsErrorFixed') : t('resetCreditsError', { message });
84
+ }
85
+ const summaryStyle = {
86
+ ...cardStyles.usageMeta, cursor: 'pointer', listStyle: 'none', justifyContent: 'flex-start', gap: 6,
87
+ };
88
+ /**
89
+ * The closed disclosure both providers share. Claude rows are plain text:
90
+ * no button, no `role="button"`, and `cursor: default`, including when
91
+ * `claimable` is true.
92
+ */
93
+ export function ResetCreditsDisclosure({ mode, credits, t, now = Date.now(), nextCredit, useAction, statusMessage, }) {
94
+ const count = resetCreditsAvailableCount(credits, now, mode);
95
+ return (_jsxs("details", { className: "subscriptions-reset-credits", style: cardStyles.usageRow, children: [_jsxs("summary", { style: summaryStyle, children: [_jsx("svg", { "aria-hidden": "true", width: "12", height: "12", viewBox: "0 0 16 16", style: { flexShrink: 0 }, children: _jsx("path", { d: "m6 3 5 5-5 5", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }), _jsx("span", { children: t('resetCreditsTitle') }), mode === 'grants' && _jsx("span", { children: t('resetCreditReadOnlyLabel') }), _jsx("span", { style: { marginLeft: 'auto' }, children: t('resetCreditsAvailable', { count }) })] }), _jsx("style", { children: '.subscriptions-reset-credits > summary::-webkit-details-marker { display: none; }'
96
+ + '.subscriptions-reset-credits[open] > summary > svg { transform: rotate(90deg); }' }), _jsxs("div", { style: { paddingLeft: 18, marginTop: 6 }, children: [mode === 'grants' && _jsx("p", { style: cardStyles.status, children: t('resetCreditReadOnly') }), credits.map((credit, index) => {
97
+ const state = resetCreditState(credit, now);
98
+ const showUse = mode === 'credits' && useAction !== undefined && credit === nextCredit;
99
+ return (_jsxs("div", { "data-reset-state": mode === 'grants' ? state : undefined, "data-reset-claimable": mode === 'grants' && credit.claimable === true ? 'true' : undefined, role: mode === 'grants' ? 'group' : undefined, "aria-label": mode === 'grants' ? resetCreditRowName(t, credit, now) : undefined, style: { ...cardStyles.usageMeta, minHeight: 28, gap: 12, cursor: mode === 'grants' ? 'default' : undefined }, children: [_jsxs("span", { style: { display: 'inline-flex', alignItems: 'center', gap: 8, flexShrink: 0 }, children: [t('resetCreditFull'), mode === 'grants' && (_jsx("span", { "aria-hidden": "true", "data-reset-state-word": state, children: t(STATE_KEYS[state]) })), showUse && (_jsx("button", { type: "button", "aria-disabled": useAction.disabled, title: useAction.title, style: { minHeight: 22, height: 22, padding: '0 7px', borderRadius: 6,
100
+ opacity: useAction.disabled ? 0.35 : 1, cursor: useAction.disabled ? 'not-allowed' : 'pointer' }, onClick: () => { if (!useAction.disabled)
101
+ useAction.onUse(); }, children: useAction.busy ? t('resetUseChecking') : useAction.label }))] }), _jsxs("span", { style: { textAlign: 'right' }, children: [mode === 'grants' && _jsxs("span", { "aria-hidden": "true", children: [remainingText(t, credit), " \u00B7 "] }), mode === 'grants' && state === 'notUsable' && credit.cooldownUntil !== undefined && credit.cooldownUntil > now && (_jsxs("span", { "aria-hidden": "true", children: [t('resetCreditCooldownUntil', { date: new Date(credit.cooldownUntil).toLocaleString() }), " \u00B7 "] })), _jsx("span", { "aria-hidden": mode === 'grants' ? true : undefined, children: expiryText(t, credit) })] })] }, credit.id ?? index));
102
+ }), statusMessage !== undefined && statusMessage !== '' && _jsx("p", { role: "status", style: cardStyles.status, children: statusMessage })] })] }));
103
+ }