@carlonicora/nextjs-jsonapi 4.0.6 → 4.0.8
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/{BlockNoteEditor-6CTF5DEV.mjs → BlockNoteEditor-A2E4VUYB.mjs} +2 -2
- package/dist/{BlockNoteEditor-JJ3S5COJ.js → BlockNoteEditor-MCYQBKT5.js} +9 -9
- package/dist/{BlockNoteEditor-JJ3S5COJ.js.map → BlockNoteEditor-MCYQBKT5.js.map} +1 -1
- package/dist/billing/index.js +335 -335
- package/dist/billing/index.mjs +1 -1
- package/dist/{chunk-Y2AWOWWH.mjs → chunk-DJRTJBSZ.mjs} +384 -330
- package/dist/chunk-DJRTJBSZ.mjs.map +1 -0
- package/dist/{chunk-XUUHPYMM.js → chunk-PL2FHXHR.js} +82 -28
- package/dist/chunk-PL2FHXHR.js.map +1 -0
- package/dist/client/index.js +2 -2
- package/dist/client/index.mjs +1 -1
- package/dist/components/index.d.mts +10 -1
- package/dist/components/index.d.ts +10 -1
- package/dist/components/index.js +4 -2
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +3 -1
- package/dist/contexts/index.js +2 -2
- package/dist/contexts/index.mjs +1 -1
- package/dist/features/analytics/index.js +88 -88
- package/dist/features/analytics/index.mjs +1 -1
- package/dist/features/help/index.js +31 -31
- package/dist/features/help/index.mjs +1 -1
- package/dist/features/tokenusage/index.js +75 -75
- package/dist/features/tokenusage/index.mjs +1 -1
- package/package.json +1 -1
- package/src/components/navigations/RecentPagesNavigator.tsx +11 -1
- package/src/components/navigations/__tests__/RecentPagesNavigator.spec.tsx +19 -0
- package/src/features/company/hooks/useSubscriptionStatus.test.ts +76 -10
- package/src/features/company/hooks/useSubscriptionStatus.ts +9 -9
- package/src/features/how-to/components/containers/HowToCommand.spec.tsx +79 -6
- package/src/features/how-to/components/containers/HowToCommand.tsx +12 -2
- package/src/features/how-to/components/forms/HowToSelector.spec.tsx +85 -0
- package/src/features/how-to/components/forms/HowToSelector.tsx +1 -0
- package/src/features/role/components/forms/UserRoleAdd.spec.tsx +71 -0
- package/src/features/role/components/forms/UserRoleAdd.tsx +1 -0
- package/src/features/user/components/forms/UserSelector.spec.tsx +85 -0
- package/src/features/user/components/forms/UserSelector.tsx +1 -0
- package/src/features/user/components/lists/UserListInAdd.spec.tsx +84 -0
- package/src/features/user/components/lists/UserListInAdd.tsx +1 -0
- package/src/features/user/components/widgets/UserSearchPopover.spec.tsx +68 -0
- package/src/features/user/components/widgets/UserSearchPopover.tsx +1 -0
- package/src/shadcnui/custom/multi-select.spec.tsx +70 -0
- package/src/shadcnui/custom/multi-select.tsx +26 -2
- package/src/shadcnui/custom/multiple-selector.spec.tsx +111 -0
- package/src/shadcnui/custom/multiple-selector.tsx +33 -10
- package/src/shadcnui/ui/command.spec.ts +26 -0
- package/src/shadcnui/ui/command.tsx +13 -1
- package/dist/chunk-XUUHPYMM.js.map +0 -1
- package/dist/chunk-Y2AWOWWH.mjs.map +0 -1
- /package/dist/{BlockNoteEditor-6CTF5DEV.mjs.map → BlockNoteEditor-A2E4VUYB.mjs.map} +0 -0
|
@@ -37,7 +37,7 @@ var _chunkC2WQXUL5js = require('../../chunk-C2WQXUL5.js');
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
var
|
|
40
|
+
var _chunkPL2FHXHRjs = require('../../chunk-PL2FHXHR.js');
|
|
41
41
|
require('../../chunk-7ZRQOLSV.js');
|
|
42
42
|
require('../../chunk-G4JR3SFV.js');
|
|
43
43
|
|
|
@@ -82,7 +82,7 @@ function TokenUsageAdminFilterBar({ granularity, companyId, metric, companies, o
|
|
|
82
82
|
};
|
|
83
83
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
84
84
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "max-md:w-full max-md:[&_#date]:w-full", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
85
|
-
|
|
85
|
+
_chunkPL2FHXHRjs.DateRangeSelector,
|
|
86
86
|
{
|
|
87
87
|
onDateChange: (range) => {
|
|
88
88
|
if (!_optionalChain([range, 'optionalAccess', _ => _.from]) || !_optionalChain([range, 'optionalAccess', _2 => _2.to])) return;
|
|
@@ -117,16 +117,16 @@ function TokenUsageAdminFilterBar({ granularity, companyId, metric, companies, o
|
|
|
117
117
|
}
|
|
118
118
|
),
|
|
119
119
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
120
|
-
|
|
120
|
+
_chunkPL2FHXHRjs.Select,
|
|
121
121
|
{
|
|
122
122
|
items: companyItems,
|
|
123
123
|
value: _nullishCoalesce(companyId, () => ( ALL_COMPANIES)),
|
|
124
124
|
onValueChange: (value) => onChange({ companyId: !value || value === ALL_COMPANIES ? void 0 : value }),
|
|
125
125
|
children: [
|
|
126
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
127
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
128
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
129
|
-
companies.map((company) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
126
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.SelectTrigger, { className: "w-56", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.SelectValue, { placeholder: t("token_usage.admin.all_companies") }) }),
|
|
127
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkPL2FHXHRjs.SelectContent, { children: [
|
|
128
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.SelectItem, { value: ALL_COMPANIES, children: t("token_usage.admin.all_companies") }),
|
|
129
|
+
companies.map((company) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.SelectItem, { value: company.id, children: company.label }, company.id))
|
|
130
130
|
] })
|
|
131
131
|
]
|
|
132
132
|
}
|
|
@@ -149,7 +149,7 @@ function Segmented({
|
|
|
149
149
|
children: options.map((option) => {
|
|
150
150
|
const selected = option.value === value;
|
|
151
151
|
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
152
|
-
|
|
152
|
+
_chunkPL2FHXHRjs.Button,
|
|
153
153
|
{
|
|
154
154
|
type: "button",
|
|
155
155
|
size: "sm",
|
|
@@ -231,7 +231,7 @@ var TokenUsageAdminProvider = /* @__PURE__ */ _chunk7QVYU63Ejs.__name.call(void
|
|
|
231
231
|
pageUrl = TOKEN_USAGE_ADMIN_PAGE_URL
|
|
232
232
|
}) => {
|
|
233
233
|
const t = _nextintl.useTranslations.call(void 0, );
|
|
234
|
-
const generateUrl =
|
|
234
|
+
const generateUrl = _chunkPL2FHXHRjs.usePageUrlGenerator.call(void 0, );
|
|
235
235
|
const [filters, setFilterState] = _react.useState.call(void 0, () => {
|
|
236
236
|
const range = defaultRange();
|
|
237
237
|
return {
|
|
@@ -349,7 +349,7 @@ var TokenUsageAdminProvider = /* @__PURE__ */ _chunk7QVYU63Ejs.__name.call(void
|
|
|
349
349
|
error
|
|
350
350
|
]
|
|
351
351
|
);
|
|
352
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
352
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.SharedProvider, { value: { breadcrumbs: breadcrumb(), title: title() }, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TokenUsageAdminContext.Provider, { value: contextValue, children }) });
|
|
353
353
|
}, "TokenUsageAdminProvider");
|
|
354
354
|
var useTokenUsageAdmin = /* @__PURE__ */ _chunk7QVYU63Ejs.__name.call(void 0, () => {
|
|
355
355
|
const ctx = _react.useContext.call(void 0, TokenUsageAdminContext);
|
|
@@ -446,7 +446,7 @@ function LeadTile({
|
|
|
446
446
|
previousLabel,
|
|
447
447
|
decimal
|
|
448
448
|
}) {
|
|
449
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
449
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.Card, { "data-testid": testId, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkPL2FHXHRjs.CardContent, { className: "grid gap-1", children: [
|
|
450
450
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-muted-foreground text-xs", children: label }),
|
|
451
451
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-primary text-xl font-semibold tabular-nums", children: value }),
|
|
452
452
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "flex items-center gap-1", children: [
|
|
@@ -463,7 +463,7 @@ function SupportingTile({
|
|
|
463
463
|
delta,
|
|
464
464
|
decimal
|
|
465
465
|
}) {
|
|
466
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
466
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.Card, { "data-testid": testId, size: "sm", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkPL2FHXHRjs.CardContent, { className: "grid gap-1", children: [
|
|
467
467
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-muted-foreground text-xs", children: label }),
|
|
468
468
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "flex items-center gap-2", children: [
|
|
469
469
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-sm font-medium tabular-nums", children: value }),
|
|
@@ -560,8 +560,8 @@ function TokenUsageBreakdownTable({ rows, metric }) {
|
|
|
560
560
|
// The package's <Table> wraps itself in an `overflow-x-clip` container, which
|
|
561
561
|
// would swallow the overflow before this scroller ever saw it — hence the
|
|
562
562
|
// child override. Wide metric tables must scroll, never widen the page.
|
|
563
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "overflow-x-auto [&_[data-slot=table-container]]:overflow-x-visible", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
564
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
563
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "overflow-x-auto [&_[data-slot=table-container]]:overflow-x-visible", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkPL2FHXHRjs.Table, { children: [
|
|
564
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.TableHeader, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.TableRow, { children: columns.map((column) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.TableHead, { className: _chunk5PNDDYGLjs.cn.call(void 0, column.numeric && "text-end"), children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
565
565
|
"button",
|
|
566
566
|
{
|
|
567
567
|
type: "button",
|
|
@@ -576,20 +576,20 @@ function TokenUsageBreakdownTable({ rows, metric }) {
|
|
|
576
576
|
]
|
|
577
577
|
}
|
|
578
578
|
) }, column.key)) }) }),
|
|
579
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
579
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.TableBody, { children: sortedRows.map((row) => {
|
|
580
580
|
const value = _chunkC2WQXUL5js.metricValue.call(void 0, row, metric);
|
|
581
581
|
const share = total > 0 ? value / total * 100 : 0;
|
|
582
|
-
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
583
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
584
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
585
|
-
showActiveUsers && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
586
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
587
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
588
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
589
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
590
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
591
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
592
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
582
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkPL2FHXHRjs.TableRow, { "data-testid": `breakdown-row-${row.id}`, children: [
|
|
583
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.TableCell, { className: "text-xs", children: row.id === "other" ? t("token_usage.admin.other") : row.label }),
|
|
584
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.TableCell, { className: "text-muted-foreground text-xs", children: _nullishCoalesce(row.sublabel, () => ( "")) }),
|
|
585
|
+
showActiveUsers && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.TableCell, { className: "text-end text-xs tabular-nums", children: row.activeUsers === void 0 ? "" : decimal(row.activeUsers, 0) }),
|
|
586
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.TableCell, { className: "text-end text-xs tabular-nums", children: decimal(row.calls, 0) }),
|
|
587
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.TableCell, { className: "text-end text-xs tabular-nums", children: decimal(row.tokensIn, 0) }),
|
|
588
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.TableCell, { className: "text-end text-xs tabular-nums", children: decimal(row.tokensOut, 0) }),
|
|
589
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.TableCell, { className: "text-end text-xs tabular-nums", children: percent(_chunkC2WQXUL5js.cacheHitPercentage.call(void 0, row.cached, row.tokensIn)) }),
|
|
590
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.TableCell, { className: "text-end text-xs tabular-nums", children: formatValue(row.cost, "cost") }),
|
|
591
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.TableCell, { className: "text-end text-xs tabular-nums", children: formatValue(row.credits, "credits") }),
|
|
592
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.TableCell, { className: "text-end text-xs tabular-nums", children: percent(share) })
|
|
593
593
|
] }, row.id);
|
|
594
594
|
}) })
|
|
595
595
|
] }) })
|
|
@@ -757,7 +757,7 @@ function TokenUsageTimelineChart({ rows, metric, stackBy, className }) {
|
|
|
757
757
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className, dir: "ltr", children: [
|
|
758
758
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "sr-only", "data-testid": "timeline-data", children: JSON.stringify(chartData) }),
|
|
759
759
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "sr-only", "data-testid": "timeline-series", children: JSON.stringify(seriesKeys) }),
|
|
760
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
760
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.ChartContainer, { config: chartConfig, className: "aspect-auto h-72 w-full", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _recharts.BarChart, { accessibilityLayer: true, data: chartData, margin: { top: 8, right: 8, bottom: 0, left: 0 }, children: [
|
|
761
761
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _recharts.CartesianGrid, { vertical: false }),
|
|
762
762
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
763
763
|
_recharts.XAxis,
|
|
@@ -781,10 +781,10 @@ function TokenUsageTimelineChart({ rows, metric, stackBy, className }) {
|
|
|
781
781
|
}
|
|
782
782
|
),
|
|
783
783
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
784
|
-
|
|
784
|
+
_chunkPL2FHXHRjs.ChartTooltip,
|
|
785
785
|
{
|
|
786
786
|
content: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
787
|
-
|
|
787
|
+
_chunkPL2FHXHRjs.ChartTooltipContent,
|
|
788
788
|
{
|
|
789
789
|
labelFormatter: (value) => bucketDate(String(value), granularity),
|
|
790
790
|
valueFormatter: (value) => formatValue(Number(value), metric),
|
|
@@ -793,7 +793,7 @@ function TokenUsageTimelineChart({ rows, metric, stackBy, className }) {
|
|
|
793
793
|
)
|
|
794
794
|
}
|
|
795
795
|
),
|
|
796
|
-
seriesKeys.length >= 2 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
796
|
+
seriesKeys.length >= 2 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.ChartLegend, { content: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.ChartLegendContent, {}) }) : null,
|
|
797
797
|
seriesKeys.map((series, index) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
798
798
|
_recharts.Bar,
|
|
799
799
|
{
|
|
@@ -845,19 +845,19 @@ function TokenUsageAdminContainer() {
|
|
|
845
845
|
[t]
|
|
846
846
|
);
|
|
847
847
|
if (error) {
|
|
848
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
848
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.RoundPageContainer, { fullWidth: true, forceHeader: true, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "p-4", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.Card, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "text-destructive text-xs/relaxed", children: error }) }) }) }) });
|
|
849
849
|
}
|
|
850
|
-
if (isLoading) return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
850
|
+
if (isLoading) return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.RoundPageContainer, { fullWidth: true, forceHeader: true });
|
|
851
851
|
const emptyLabel = t("token_usage.admin.no_data");
|
|
852
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
852
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.RoundPageContainer, { fullWidth: true, forceHeader: true, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex w-full flex-col gap-4 p-4", children: [
|
|
853
853
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, TokenUsageAdminTiles, { summary, metric: filters.metric, singleCustomerMode: hidePlatform }),
|
|
854
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
855
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
856
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
857
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
854
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkPL2FHXHRjs.Card, { children: [
|
|
855
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkPL2FHXHRjs.CardHeader, { children: [
|
|
856
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardTitle, { children: t("token_usage.admin.usage_over_time") }),
|
|
857
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardAction, { children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-2", children: [
|
|
858
858
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-muted-foreground text-xs", children: t("token_usage.admin.stack_by") }),
|
|
859
859
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
860
|
-
|
|
860
|
+
_chunkPL2FHXHRjs.Select,
|
|
861
861
|
{
|
|
862
862
|
items: stackByItems,
|
|
863
863
|
value: filters.stackBy,
|
|
@@ -865,29 +865,29 @@ function TokenUsageAdminContainer() {
|
|
|
865
865
|
if (value) setFilters({ stackBy: value });
|
|
866
866
|
},
|
|
867
867
|
children: [
|
|
868
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
869
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
868
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.SelectTrigger, { size: "sm", className: "w-36", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.SelectValue, {}) }),
|
|
869
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.SelectContent, { children: STACK_BY_VALUES.map((value) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.SelectItem, { value, children: stackByItems[value] }, value)) })
|
|
870
870
|
]
|
|
871
871
|
}
|
|
872
872
|
)
|
|
873
873
|
] }) })
|
|
874
874
|
] }),
|
|
875
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
875
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TokenUsageTimelineChart, { rows: timeline, metric: filters.metric, stackBy: filters.stackBy }) })
|
|
876
876
|
] }),
|
|
877
877
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "grid gap-4 md:grid-cols-2", children: [
|
|
878
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
879
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
880
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
878
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkPL2FHXHRjs.Card, { children: [
|
|
879
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardHeader, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardTitle, { children: t("token_usage.admin.by_company") }) }),
|
|
880
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TokenUsageRankedBar, { rows: byCompany, metric: filters.metric, emptyLabel }) })
|
|
881
881
|
] }),
|
|
882
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
883
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
884
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
882
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkPL2FHXHRjs.Card, { children: [
|
|
883
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardHeader, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardTitle, { children: t("token_usage.admin.by_user") }) }),
|
|
884
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TokenUsageRankedBar, { rows: byUser, metric: filters.metric, emptyLabel }) })
|
|
885
885
|
] })
|
|
886
886
|
] }),
|
|
887
887
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunk5PNDDYGLjs.cn.call(void 0, "grid gap-4", !hidePlatform && "md:grid-cols-2"), children: [
|
|
888
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
889
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
890
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
888
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkPL2FHXHRjs.Card, { children: [
|
|
889
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardHeader, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardTitle, { children: t("token_usage.admin.customer_by_operation") }) }),
|
|
890
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
891
891
|
TokenUsageRankedBar,
|
|
892
892
|
{
|
|
893
893
|
rows: byCustomerOperation,
|
|
@@ -897,9 +897,9 @@ function TokenUsageAdminContainer() {
|
|
|
897
897
|
}
|
|
898
898
|
) })
|
|
899
899
|
] }),
|
|
900
|
-
!hidePlatform && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
901
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
902
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
900
|
+
!hidePlatform && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkPL2FHXHRjs.Card, { children: [
|
|
901
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardHeader, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardTitle, { children: t("token_usage.admin.platform_by_operation") }) }),
|
|
902
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
903
903
|
TokenUsageRankedBar,
|
|
904
904
|
{
|
|
905
905
|
rows: byOperation,
|
|
@@ -910,9 +910,9 @@ function TokenUsageAdminContainer() {
|
|
|
910
910
|
) })
|
|
911
911
|
] })
|
|
912
912
|
] }),
|
|
913
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
914
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
915
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
913
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkPL2FHXHRjs.Card, { children: [
|
|
914
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardHeader, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardTitle, { children: t("token_usage.admin.detail") }) }),
|
|
915
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TokenUsageBreakdownTable, { rows: byCompany, metric: filters.metric }) })
|
|
916
916
|
] })
|
|
917
917
|
] }) });
|
|
918
918
|
}
|
|
@@ -1033,7 +1033,7 @@ var TOKEN_USAGE_REPORT_I18N_KEYS = [
|
|
|
1033
1033
|
|
|
1034
1034
|
function TokenUsageReportFilterBar({ onChange }) {
|
|
1035
1035
|
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { "data-help": "tokenusage.filters", className: "flex flex-wrap items-center gap-2", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1036
|
-
|
|
1036
|
+
_chunkPL2FHXHRjs.DateRangeSelector,
|
|
1037
1037
|
{
|
|
1038
1038
|
onDateChange: (range) => {
|
|
1039
1039
|
if (!_optionalChain([range, 'optionalAccess', _5 => _5.from]) || !_optionalChain([range, 'optionalAccess', _6 => _6.to])) return;
|
|
@@ -1066,7 +1066,7 @@ var TokenUsageReportProvider = /* @__PURE__ */ _chunk7QVYU63Ejs.__name.call(void
|
|
|
1066
1066
|
pageUrl = TOKEN_USAGE_REPORT_PAGE_URL
|
|
1067
1067
|
}) => {
|
|
1068
1068
|
const t = _nextintl.useTranslations.call(void 0, );
|
|
1069
|
-
const generateUrl =
|
|
1069
|
+
const generateUrl = _chunkPL2FHXHRjs.usePageUrlGenerator.call(void 0, );
|
|
1070
1070
|
const [filters, setFilterState] = _react.useState.call(void 0, () => {
|
|
1071
1071
|
const range = defaultRange2();
|
|
1072
1072
|
return { from: _nullishCoalesce(initialFrom, () => ( range.from)), to: _nullishCoalesce(initialTo, () => ( range.to)) };
|
|
@@ -1130,7 +1130,7 @@ var TokenUsageReportProvider = /* @__PURE__ */ _chunk7QVYU63Ejs.__name.call(void
|
|
|
1130
1130
|
() => ({ summary, timeline, byOperation, byTarget, targetPanelTitleKey, filters, setFilters, isLoading, error }),
|
|
1131
1131
|
[summary, timeline, byOperation, byTarget, targetPanelTitleKey, filters, setFilters, isLoading, error]
|
|
1132
1132
|
);
|
|
1133
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1133
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.SharedProvider, { value: { breadcrumbs: breadcrumb(), title: title() }, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TokenUsageReportContext.Provider, { value: contextValue, children }) });
|
|
1134
1134
|
}, "TokenUsageReportProvider");
|
|
1135
1135
|
var useTokenUsageReport = /* @__PURE__ */ _chunk7QVYU63Ejs.__name.call(void 0, () => {
|
|
1136
1136
|
const ctx = _react.useContext.call(void 0, TokenUsageReportContext);
|
|
@@ -1158,7 +1158,7 @@ function TokenUsageReportTiles({ summary, metric, balances }) {
|
|
|
1158
1158
|
const monthlyColor = monthlyPercentage > 75 ? "text-success" : monthlyPercentage >= 5 ? "text-warning" : "text-destructive";
|
|
1159
1159
|
const whole = /* @__PURE__ */ _chunk7QVYU63Ejs.__name.call(void 0, (value) => decimal(Math.max(0, Math.floor(value)), 0), "whole");
|
|
1160
1160
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "grid gap-3", children: [
|
|
1161
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1161
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.Card, { "data-testid": "tile-used", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkPL2FHXHRjs.CardContent, { className: "grid gap-1", children: [
|
|
1162
1162
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-muted-foreground text-xs", children: t("token_usage.report.used_in_period") }),
|
|
1163
1163
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-primary text-xl font-semibold tabular-nums", children: formatValue(_chunkC2WQXUL5js.metricValue.call(void 0, current, metric), metric) }),
|
|
1164
1164
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "flex items-center gap-1", children: [
|
|
@@ -1174,7 +1174,7 @@ function TokenUsageReportTiles({ summary, metric, balances }) {
|
|
|
1174
1174
|
] })
|
|
1175
1175
|
] }) }),
|
|
1176
1176
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "grid gap-3 sm:grid-cols-3", children: [
|
|
1177
|
-
balances && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1177
|
+
balances && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.Card, { "data-testid": "tile-monthly", size: "sm", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkPL2FHXHRjs.CardContent, { className: "grid gap-1", children: [
|
|
1178
1178
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-muted-foreground text-xs", children: t("token_usage.report.monthly_left") }),
|
|
1179
1179
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "flex items-baseline gap-1", children: [
|
|
1180
1180
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { "data-testid": "tile-monthly-value", className: _chunk5PNDDYGLjs.cn.call(void 0, "text-sm font-medium tabular-nums", monthlyColor), children: whole(balances.availableMonthlyCredits) }),
|
|
@@ -1184,11 +1184,11 @@ function TokenUsageReportTiles({ summary, metric, balances }) {
|
|
|
1184
1184
|
] })
|
|
1185
1185
|
] })
|
|
1186
1186
|
] }) }),
|
|
1187
|
-
balances && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1187
|
+
balances && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.Card, { "data-testid": "tile-extra", size: "sm", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkPL2FHXHRjs.CardContent, { className: "grid gap-1", children: [
|
|
1188
1188
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-muted-foreground text-xs", children: t("token_usage.report.extra_credits") }),
|
|
1189
1189
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-sm font-medium tabular-nums", children: whole(balances.availableExtraCredits) })
|
|
1190
1190
|
] }) }),
|
|
1191
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1191
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.Card, { "data-testid": "tile-calls", size: "sm", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkPL2FHXHRjs.CardContent, { className: "grid gap-1", children: [
|
|
1192
1192
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-muted-foreground text-xs", children: t("token_usage.report.calls") }),
|
|
1193
1193
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-sm font-medium tabular-nums", children: decimal(current.calls, 0) })
|
|
1194
1194
|
] }) })
|
|
@@ -1230,21 +1230,21 @@ function TokenUsageReportContainer({ balances = null }) {
|
|
|
1230
1230
|
const t = _nextintl.useTranslations.call(void 0, );
|
|
1231
1231
|
const { summary, timeline, byOperation, byTarget, isLoading, error, targetPanelTitleKey } = useTokenUsageReport();
|
|
1232
1232
|
if (error) {
|
|
1233
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1233
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.RoundPageContainer, { fullWidth: true, forceHeader: true, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "p-4", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.Card, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "text-destructive text-xs/relaxed", children: error }) }) }) }) });
|
|
1234
1234
|
}
|
|
1235
|
-
if (isLoading) return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1235
|
+
if (isLoading) return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.RoundPageContainer, { fullWidth: true, forceHeader: true });
|
|
1236
1236
|
const emptyLabel = t("token_usage.report.no_data");
|
|
1237
1237
|
const targetTitle = targetPanelTitleKey && byTarget.length > 0 ? t(targetPanelTitleKey) : void 0;
|
|
1238
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1238
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.RoundPageContainer, { fullWidth: true, forceHeader: true, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex w-full flex-col gap-4 p-4", children: [
|
|
1239
1239
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, TokenUsageReportTiles, { summary, metric: "credits", balances }),
|
|
1240
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
1241
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1242
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1240
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkPL2FHXHRjs.Card, { children: [
|
|
1241
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardHeader, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardTitle, { children: t("token_usage.report.usage_over_time") }) }),
|
|
1242
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TokenUsageTimelineChart, { rows: timeline, metric: "credits", stackBy: "type" }) })
|
|
1243
1243
|
] }),
|
|
1244
1244
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunk5PNDDYGLjs.cn.call(void 0, "grid gap-4", targetTitle && "md:grid-cols-2"), children: [
|
|
1245
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
1246
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1247
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1245
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkPL2FHXHRjs.Card, { children: [
|
|
1246
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardHeader, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardTitle, { children: t("token_usage.report.by_operation") }) }),
|
|
1247
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1248
1248
|
TokenUsageRankedBar,
|
|
1249
1249
|
{
|
|
1250
1250
|
rows: byOperation,
|
|
@@ -1254,9 +1254,9 @@ function TokenUsageReportContainer({ balances = null }) {
|
|
|
1254
1254
|
}
|
|
1255
1255
|
) })
|
|
1256
1256
|
] }),
|
|
1257
|
-
targetTitle && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
1258
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1259
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1257
|
+
targetTitle && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkPL2FHXHRjs.Card, { children: [
|
|
1258
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardHeader, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardTitle, { children: targetTitle }) }),
|
|
1259
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPL2FHXHRjs.CardContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TokenUsageRankedBar, { rows: byTarget, metric: "credits", emptyLabel }) })
|
|
1260
1260
|
] })
|
|
1261
1261
|
] })
|
|
1262
1262
|
] }) });
|
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ import { useAtomValue } from "jotai";
|
|
|
4
4
|
import { HistoryIcon } from "lucide-react";
|
|
5
5
|
import { useTranslations } from "next-intl";
|
|
6
6
|
import { usePathname } from "next/navigation";
|
|
7
|
+
import { useEffect, useState } from "react";
|
|
7
8
|
import { recentPagesAtom } from "../../atoms";
|
|
8
9
|
import {
|
|
9
10
|
DropdownMenu,
|
|
@@ -36,6 +37,15 @@ export function RecentPagesNavigator() {
|
|
|
36
37
|
const t = useTranslations();
|
|
37
38
|
const { isMobile } = useSidebar();
|
|
38
39
|
const pathname = usePathname();
|
|
40
|
+
const [mounted, setMounted] = useState(false);
|
|
41
|
+
|
|
42
|
+
// The list lives in localStorage, which the server cannot read, so the server
|
|
43
|
+
// renders nothing. Under Cache Components the sidebar hydrates in its own
|
|
44
|
+
// boundary, after usePageTracker has already loaded the stored list into the
|
|
45
|
+
// atom; rendering it before mount would not match the server HTML.
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
setMounted(true);
|
|
48
|
+
}, []);
|
|
39
49
|
|
|
40
50
|
// The page the user is standing on is not somewhere to navigate back to, so
|
|
41
51
|
// it is hidden while they are there. Filtering happens here rather than in
|
|
@@ -50,7 +60,7 @@ export function RecentPagesNavigator() {
|
|
|
50
60
|
|
|
51
61
|
const pages = recentPages.filter((page) => !isCurrentPage(page.url));
|
|
52
62
|
|
|
53
|
-
if (pages.length === 0) return null;
|
|
63
|
+
if (!mounted || pages.length === 0) return null;
|
|
54
64
|
|
|
55
65
|
return (
|
|
56
66
|
<SidebarMenuItem>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { render, screen } from "@testing-library/react";
|
|
2
2
|
import userEvent from "@testing-library/user-event";
|
|
3
3
|
import { createStore, Provider } from "jotai";
|
|
4
|
+
import { renderToString } from "react-dom/server";
|
|
4
5
|
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
6
|
import { recentPagesAtom, type RecentPage } from "../../../atoms";
|
|
6
7
|
import { configureI18n } from "../../../i18n";
|
|
@@ -78,6 +79,24 @@ function renderNavigator({
|
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
describe("RecentPagesNavigator", () => {
|
|
82
|
+
it("renders nothing before mount even when pages are stored, so the server HTML matches", () => {
|
|
83
|
+
// Regression: under Cache Components the sidebar hydrates after usePageTracker
|
|
84
|
+
// has loaded localStorage into the atom; rendering the row on the first pass
|
|
85
|
+
// put an <li> the server never sent and failed hydration.
|
|
86
|
+
const store = createStore();
|
|
87
|
+
store.set(recentPagesAtom, PAGES);
|
|
88
|
+
const html = renderToString(
|
|
89
|
+
<Provider store={store}>
|
|
90
|
+
<SidebarProvider open>
|
|
91
|
+
<SidebarMenu>
|
|
92
|
+
<RecentPagesNavigator />
|
|
93
|
+
</SidebarMenu>
|
|
94
|
+
</SidebarProvider>
|
|
95
|
+
</Provider>,
|
|
96
|
+
);
|
|
97
|
+
expect(html).not.toContain("sidebar-recent-pages");
|
|
98
|
+
});
|
|
99
|
+
|
|
81
100
|
it("renders nothing when no pages have been recorded", () => {
|
|
82
101
|
renderNavigator({ pages: [] });
|
|
83
102
|
expect(screen.queryByTestId("sidebar-recent-pages")).toBeNull();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, afterAll, beforeAll, vi } from "vitest";
|
|
2
2
|
import { renderHook } from "@testing-library/react";
|
|
3
3
|
|
|
4
4
|
// Mutable mocks the individual tests below configure per case, mirroring the
|
|
@@ -22,13 +22,37 @@ vi.mock("../../../client/config", () => ({
|
|
|
22
22
|
|
|
23
23
|
import { useSubscriptionStatus } from "./useSubscriptionStatus";
|
|
24
24
|
|
|
25
|
+
const DAY_MS = 1000 * 60 * 60 * 24;
|
|
26
|
+
|
|
27
|
+
// Every date case runs on a frozen clock in a zone with DST. The window math
|
|
28
|
+
// used to follow the host's local calendar, so the result depended on the day
|
|
29
|
+
// the suite ran and on the machine's timezone; pinning both makes it repeatable.
|
|
30
|
+
const ORIGINAL_TZ = process.env.TZ;
|
|
31
|
+
const FIXED_NOW = new Date("2026-06-15T12:00:00+02:00"); // no DST change within 14 days
|
|
32
|
+
|
|
33
|
+
const daysAgo = (days: number, now: Date = FIXED_NOW) => new Date(now.getTime() - days * DAY_MS);
|
|
34
|
+
|
|
25
35
|
describe("useSubscriptionStatus", () => {
|
|
36
|
+
beforeAll(() => {
|
|
37
|
+
process.env.TZ = "Europe/Rome";
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
afterAll(() => {
|
|
41
|
+
process.env.TZ = ORIGINAL_TZ;
|
|
42
|
+
});
|
|
43
|
+
|
|
26
44
|
beforeEach(() => {
|
|
45
|
+
vi.useFakeTimers();
|
|
46
|
+
vi.setSystemTime(FIXED_NOW);
|
|
27
47
|
userMock.currentUser = null;
|
|
28
48
|
userMock.company = null;
|
|
29
49
|
configMock.stripeKey = "pk_test_123";
|
|
30
50
|
});
|
|
31
51
|
|
|
52
|
+
afterEach(() => {
|
|
53
|
+
vi.useRealTimers();
|
|
54
|
+
});
|
|
55
|
+
|
|
32
56
|
it("reports loading while currentUser is still null", () => {
|
|
33
57
|
userMock.currentUser = null;
|
|
34
58
|
|
|
@@ -69,7 +93,7 @@ describe("useSubscriptionStatus", () => {
|
|
|
69
93
|
|
|
70
94
|
it("is never blocked when the company has an active subscription", () => {
|
|
71
95
|
userMock.currentUser = { roles: [] };
|
|
72
|
-
userMock.company = { isActiveSubscription: true, createdAt:
|
|
96
|
+
userMock.company = { isActiveSubscription: true, createdAt: FIXED_NOW };
|
|
73
97
|
|
|
74
98
|
const { result } = renderHook(() => useSubscriptionStatus());
|
|
75
99
|
|
|
@@ -79,8 +103,7 @@ describe("useSubscriptionStatus", () => {
|
|
|
79
103
|
|
|
80
104
|
it("does NOT block an unsubscribed company that is still inside its trial window", () => {
|
|
81
105
|
userMock.currentUser = { roles: [] };
|
|
82
|
-
const createdAt =
|
|
83
|
-
createdAt.setDate(createdAt.getDate() - 1); // 1 day old — squarely inside the 14-day trial
|
|
106
|
+
const createdAt = daysAgo(1); // 1 day old — squarely inside the 14-day trial
|
|
84
107
|
userMock.company = { isActiveSubscription: false, createdAt };
|
|
85
108
|
|
|
86
109
|
const { result } = renderHook(() => useSubscriptionStatus());
|
|
@@ -94,8 +117,7 @@ describe("useSubscriptionStatus", () => {
|
|
|
94
117
|
|
|
95
118
|
it("blocks once the trial window has elapsed with no active subscription", () => {
|
|
96
119
|
userMock.currentUser = { roles: [] };
|
|
97
|
-
const createdAt =
|
|
98
|
-
createdAt.setDate(createdAt.getDate() - 15); // past the 14-day trial
|
|
120
|
+
const createdAt = daysAgo(15); // past the 14-day trial
|
|
99
121
|
userMock.company = { isActiveSubscription: false, createdAt };
|
|
100
122
|
|
|
101
123
|
const { result } = renderHook(() => useSubscriptionStatus());
|
|
@@ -106,8 +128,7 @@ describe("useSubscriptionStatus", () => {
|
|
|
106
128
|
|
|
107
129
|
it("keeps daysRemaining counting down from createdAt, unaffected by the isBlocked change (1 day old)", () => {
|
|
108
130
|
userMock.currentUser = { roles: [] };
|
|
109
|
-
const createdAt =
|
|
110
|
-
createdAt.setDate(createdAt.getDate() - 1);
|
|
131
|
+
const createdAt = daysAgo(1);
|
|
111
132
|
userMock.company = { isActiveSubscription: false, createdAt };
|
|
112
133
|
|
|
113
134
|
const { result } = renderHook(() => useSubscriptionStatus());
|
|
@@ -118,8 +139,7 @@ describe("useSubscriptionStatus", () => {
|
|
|
118
139
|
|
|
119
140
|
it("keeps isGracePeriod true once daysRemaining is inside GRACE_DAYS (12 days old)", () => {
|
|
120
141
|
userMock.currentUser = { roles: [] };
|
|
121
|
-
const createdAt =
|
|
122
|
-
createdAt.setDate(createdAt.getDate() - 12);
|
|
142
|
+
const createdAt = daysAgo(12);
|
|
123
143
|
userMock.company = { isActiveSubscription: false, createdAt };
|
|
124
144
|
|
|
125
145
|
const { result } = renderHook(() => useSubscriptionStatus());
|
|
@@ -127,4 +147,50 @@ describe("useSubscriptionStatus", () => {
|
|
|
127
147
|
expect(result.current.daysRemaining).toBe(2);
|
|
128
148
|
expect(result.current.isGracePeriod).toBe(true);
|
|
129
149
|
});
|
|
150
|
+
|
|
151
|
+
// Regression: on 2026-10-12 in Europe/Rome the 1-day-old case reported 14.
|
|
152
|
+
// The trial end (2026-10-25) falls on the day CEST ends, so adding 14 local
|
|
153
|
+
// calendar days produced 14 days + 1 hour, and ceil(13.04) = 14.
|
|
154
|
+
it("reports 13 days for a 1-day-old company when the trial end crosses the autumn DST change", () => {
|
|
155
|
+
const now = new Date("2026-10-12T14:18:02+02:00");
|
|
156
|
+
vi.setSystemTime(now);
|
|
157
|
+
userMock.currentUser = { roles: [] };
|
|
158
|
+
const createdAt = daysAgo(1, now);
|
|
159
|
+
userMock.company = { isActiveSubscription: false, createdAt };
|
|
160
|
+
|
|
161
|
+
const { result } = renderHook(() => useSubscriptionStatus());
|
|
162
|
+
|
|
163
|
+
expect(result.current.daysRemaining).toBe(13);
|
|
164
|
+
expect(result.current.trialEndsAt?.getTime()).toBe(createdAt.getTime() + 14 * DAY_MS);
|
|
165
|
+
expect(result.current.status).toBe("trial");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("reports 13 days for a 1-day-old company when the trial end crosses the spring DST change", () => {
|
|
169
|
+
const now = new Date("2027-03-20T12:00:00+01:00"); // trial ends after CET -> CEST on 2027-03-28
|
|
170
|
+
vi.setSystemTime(now);
|
|
171
|
+
userMock.currentUser = { roles: [] };
|
|
172
|
+
const createdAt = daysAgo(1, now);
|
|
173
|
+
userMock.company = { isActiveSubscription: false, createdAt };
|
|
174
|
+
|
|
175
|
+
const { result } = renderHook(() => useSubscriptionStatus());
|
|
176
|
+
|
|
177
|
+
expect(result.current.daysRemaining).toBe(13);
|
|
178
|
+
expect(result.current.trialEndsAt?.getTime()).toBe(createdAt.getTime() + 14 * DAY_MS);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("blocks exactly 14 x 24h after createdAt, even when the window spans a DST change", () => {
|
|
182
|
+
const createdAt = new Date("2026-10-12T14:18:02+02:00");
|
|
183
|
+
userMock.currentUser = { roles: [] };
|
|
184
|
+
userMock.company = { isActiveSubscription: false, createdAt };
|
|
185
|
+
|
|
186
|
+
vi.setSystemTime(createdAt.getTime() + 14 * DAY_MS - 1);
|
|
187
|
+
const before = renderHook(() => useSubscriptionStatus());
|
|
188
|
+
expect(before.result.current.isBlocked).toBe(false);
|
|
189
|
+
expect(before.result.current.daysRemaining).toBe(1);
|
|
190
|
+
|
|
191
|
+
vi.setSystemTime(createdAt.getTime() + 14 * DAY_MS);
|
|
192
|
+
const after = renderHook(() => useSubscriptionStatus());
|
|
193
|
+
expect(after.result.current.isBlocked).toBe(true);
|
|
194
|
+
expect(after.result.current.status).toBe("expired");
|
|
195
|
+
});
|
|
130
196
|
});
|