@carlonicora/nextjs-jsonapi 3.24.2 → 3.25.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/{AssistantMessageInterface-1Kbz8RVs.d.ts → AssistantMessageInterface-CubxuBIJ.d.ts} +1 -1
- package/dist/{AssistantMessageInterface-CoxJyuFM.d.mts → AssistantMessageInterface-sHjI2zPN.d.mts} +1 -1
- package/dist/{BlockNoteEditor-65HR6ULV.mjs → BlockNoteEditor-46O3JC4Z.mjs} +5 -5
- package/dist/{BlockNoteEditor-35D4R7HJ.js → BlockNoteEditor-HKS6AXDA.js} +20 -20
- package/dist/{BlockNoteEditor-35D4R7HJ.js.map → BlockNoteEditor-HKS6AXDA.js.map} +1 -1
- package/dist/{BlockNoteViewer-QDXDVYEM.js → BlockNoteViewer-7BFNTN4E.js} +4 -4
- package/dist/{BlockNoteViewer-QDXDVYEM.js.map → BlockNoteViewer-7BFNTN4E.js.map} +1 -1
- package/dist/{BlockNoteViewer-KDKGUIG5.mjs → BlockNoteViewer-KRTAFTUX.mjs} +3 -3
- package/dist/{HandbookSectionInterface-OePKPbqN.d.ts → HandbookSectionInterface-Ck1wZJ81.d.ts} +1 -1
- package/dist/{HandbookSectionInterface-BYsmnPEz.d.mts → HandbookSectionInterface-DgYH--Ik.d.mts} +1 -1
- package/dist/JsonApiRequest-TE5WD3WS.js +26 -0
- package/dist/{JsonApiRequest-G625QAH5.js.map → JsonApiRequest-TE5WD3WS.js.map} +1 -1
- package/dist/{JsonApiRequest-RRMNV7YE.mjs → JsonApiRequest-UHOWNI3O.mjs} +2 -2
- package/dist/analytics.modules-CfczU12P.d.ts +25 -0
- package/dist/analytics.modules-pzGwMASI.d.mts +25 -0
- package/dist/{auth.interface-BC1jp_ni.d.ts → auth.interface-BzI4onI_.d.ts} +1 -1
- package/dist/{auth.interface-dR2-6gPM.d.mts → auth.interface-Df4vXbNz.d.mts} +1 -1
- package/dist/billing/index.js +402 -402
- package/dist/billing/index.mjs +4 -4
- package/dist/{chunk-YXRVPEEJ.js → chunk-26MQIJNB.js} +1 -1
- package/dist/{chunk-YXRVPEEJ.js.map → chunk-26MQIJNB.js.map} +1 -1
- package/dist/{chunk-T76WLDE5.mjs → chunk-37DMFVLN.mjs} +1 -1
- package/dist/chunk-4Z2UONN2.mjs +110 -0
- package/dist/chunk-4Z2UONN2.mjs.map +1 -0
- package/dist/{chunk-3AR4KA5D.js → chunk-4Z45NYPR.js} +13 -13
- package/dist/{chunk-3AR4KA5D.js.map → chunk-4Z45NYPR.js.map} +1 -1
- package/dist/chunk-7B4E42R5.mjs +280 -0
- package/dist/chunk-7B4E42R5.mjs.map +1 -0
- package/dist/{chunk-U7QVISQS.js → chunk-CAKOFJZD.js} +450 -40
- package/dist/chunk-CAKOFJZD.js.map +1 -0
- package/dist/{chunk-IBVKPLHT.mjs → chunk-E3I4MT53.mjs} +422 -12
- package/dist/chunk-E3I4MT53.mjs.map +1 -0
- package/dist/chunk-GHFMKH3M.js +280 -0
- package/dist/chunk-GHFMKH3M.js.map +1 -0
- package/dist/chunk-KLV6RLDE.js +110 -0
- package/dist/chunk-KLV6RLDE.js.map +1 -0
- package/dist/{chunk-ZJI5ZZW5.mjs → chunk-Q6J2ICKO.mjs} +3 -3
- package/dist/{chunk-4O7R2Z2O.mjs → chunk-XDYILJ2S.mjs} +1749 -1737
- package/dist/{chunk-4O7R2Z2O.mjs.map → chunk-XDYILJ2S.mjs.map} +1 -1
- package/dist/{chunk-2XCUAHQT.js → chunk-Y3OKEQGL.js} +1126 -1114
- package/dist/chunk-Y3OKEQGL.js.map +1 -0
- package/dist/client/index.d.mts +6 -5
- package/dist/client/index.d.ts +6 -5
- package/dist/client/index.js +5 -5
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +4 -4
- package/dist/components/index.d.mts +17 -7
- package/dist/components/index.d.ts +17 -7
- package/dist/components/index.js +5 -5
- package/dist/components/index.mjs +4 -4
- package/dist/{config-B6UB7j69.d.ts → config-3N1LSrcC.d.ts} +1 -1
- package/dist/{config-Cd5xTEfZ.d.ts → config-AVtuvfM1.d.ts} +1 -1
- package/dist/{config-n-ZpPmOL.d.mts → config-BSKTYDoh.d.mts} +1 -1
- package/dist/{config-YyhFHxkU.d.mts → config-BmufyEj6.d.mts} +1 -1
- package/dist/contexts/index.d.mts +5 -4
- package/dist/contexts/index.d.ts +5 -4
- package/dist/contexts/index.js +5 -5
- package/dist/contexts/index.mjs +4 -4
- package/dist/core/index.d.mts +31 -14
- package/dist/core/index.d.ts +31 -14
- package/dist/core/index.js +9 -5
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +8 -4
- package/dist/features/analytics/index.css +30 -0
- package/dist/features/analytics/index.css.map +1 -0
- package/dist/features/analytics/index.d.mts +508 -0
- package/dist/features/analytics/index.d.ts +508 -0
- package/dist/features/analytics/index.js +1166 -0
- package/dist/features/analytics/index.js.map +1 -0
- package/dist/features/analytics/index.mjs +1166 -0
- package/dist/features/analytics/index.mjs.map +1 -0
- package/dist/features/analytics/server.d.mts +28 -0
- package/dist/features/analytics/server.d.ts +28 -0
- package/dist/features/analytics/server.js +41 -0
- package/dist/features/analytics/server.js.map +1 -0
- package/dist/features/analytics/server.mjs +41 -0
- package/dist/features/analytics/server.mjs.map +1 -0
- package/dist/features/help/index.d.mts +2 -2
- package/dist/features/help/index.d.ts +2 -2
- package/dist/features/help/index.js +38 -38
- package/dist/features/help/index.mjs +4 -4
- package/dist/features/tokenusage/index.d.mts +2 -2
- package/dist/features/tokenusage/index.d.ts +2 -2
- package/dist/features/tokenusage/index.js +139 -221
- package/dist/features/tokenusage/index.js.map +1 -1
- package/dist/features/tokenusage/index.mjs +26 -108
- package/dist/features/tokenusage/index.mjs.map +1 -1
- package/dist/index.d.mts +11 -9
- package/dist/index.d.ts +11 -9
- package/dist/index.js +10 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9 -5
- package/dist/notification.interface-BNCvnVCW.d.ts +34 -0
- package/dist/notification.interface-P_pNfzZQ.d.mts +34 -0
- package/dist/{s3.service-Bqr-fQmi.d.ts → s3.service-CbUa6a-8.d.ts} +3 -2
- package/dist/{s3.service-Buiy1ncj.d.mts → s3.service-DAp8FbuG.d.mts} +3 -2
- package/dist/server/index.d.mts +5 -4
- package/dist/server/index.d.ts +5 -4
- package/dist/server/index.js +39 -279
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +20 -260
- package/dist/server/index.mjs.map +1 -1
- package/dist/{types-bsm8Fp55.d.ts → types-Cn153-mH.d.ts} +1 -1
- package/dist/{types-Bq_UA8Lg.d.mts → types-D0uLYV2P.d.mts} +1 -1
- package/dist/{usePageUrlGenerator-J_ECv-oQ.d.ts → usePageUrlGenerator-B-3FRKBD.d.ts} +1 -1
- package/dist/{usePageUrlGenerator-uKBjAKqC.d.mts → usePageUrlGenerator-CCJ_cdQy.d.mts} +1 -1
- package/dist/{useSocket-BcH7l7B8.d.mts → useSocket-B9gTbuCj.d.mts} +1 -1
- package/dist/{useSocket-DCZd_eJk.d.ts → useSocket-BHVIyigI.d.ts} +1 -1
- package/dist/{notification.interface-MnRt1Tjm.d.mts → user.interface-CSaGWFbg.d.mts} +1 -31
- package/dist/{notification.interface-CjzV_KII.d.ts → user.interface-CrmwJLYW.d.ts} +1 -31
- package/package.json +11 -1
- package/src/client/index.ts +2 -0
- package/src/core/index.ts +4 -0
- package/src/core/registry/ModuleRegistry.ts +7 -0
- package/src/features/__tests__/i18n-keys.contract.spec.ts +1 -0
- package/src/features/analytics/__tests__/server-entry.spec.tsx +64 -0
- package/src/features/analytics/analytics.modules.ts +30 -0
- package/src/features/analytics/components/AnalyticsAdminContainer.tsx +97 -0
- package/src/features/analytics/components/AnalyticsAdminFilterBar.tsx +117 -0
- package/src/features/analytics/components/AnalyticsAdminPageContainer.tsx +25 -0
- package/src/features/analytics/components/AnalyticsAdminTiles.tsx +191 -0
- package/src/features/analytics/components/AnalyticsBreakdownTable.tsx +104 -0
- package/src/features/analytics/components/AnalyticsJourneyContainer.tsx +246 -0
- package/src/features/analytics/components/AnalyticsJourneyPageContainer.tsx +14 -0
- package/src/features/analytics/components/AnalyticsSessionsTable.tsx +107 -0
- package/src/features/analytics/components/AnalyticsTimelineChart.tsx +175 -0
- package/src/features/analytics/components/AnalyticsTracker.tsx +112 -0
- package/src/features/analytics/components/__tests__/AnalyticsAdminTiles.spec.tsx +39 -0
- package/src/features/analytics/components/__tests__/AnalyticsBreakdownTable.spec.tsx +39 -0
- package/src/features/analytics/components/__tests__/AnalyticsJourneyContainer.spec.tsx +124 -0
- package/src/features/analytics/components/__tests__/AnalyticsPageContainers.spec.tsx +117 -0
- package/src/features/analytics/components/__tests__/AnalyticsSessionsTable.spec.tsx +121 -0
- package/src/features/analytics/components/__tests__/AnalyticsTimelineChart.spec.tsx +35 -0
- package/src/features/analytics/components/__tests__/AnalyticsTracker.spec.tsx +248 -0
- package/src/features/analytics/components/server/AnalyticsAdminPage.tsx +28 -0
- package/src/features/analytics/components/server/AnalyticsJourneyPage.tsx +25 -0
- package/src/features/analytics/contexts/AnalyticsAdminContext.tsx +273 -0
- package/src/features/analytics/contexts/__tests__/AnalyticsAdminContext.spec.tsx +140 -0
- package/src/features/analytics/data/AnalyticsAdminService.ts +91 -0
- package/src/features/analytics/data/AnalyticsService.ts +23 -0
- package/src/features/analytics/data/__tests__/AnalyticsAdminService.spec.ts +117 -0
- package/src/features/analytics/data/__tests__/AnalyticsService.spec.ts +56 -0
- package/src/features/analytics/data/__tests__/models.spec.ts +300 -0
- package/src/features/analytics/data/analytics-breakdown.interface.ts +9 -0
- package/src/features/analytics/data/analytics-breakdown.ts +65 -0
- package/src/features/analytics/data/analytics-event.interface.ts +7 -0
- package/src/features/analytics/data/analytics-event.ts +63 -0
- package/src/features/analytics/data/analytics-page-view.interface.ts +9 -0
- package/src/features/analytics/data/analytics-page-view.ts +57 -0
- package/src/features/analytics/data/analytics-session.interface.ts +23 -0
- package/src/features/analytics/data/analytics-session.ts +144 -0
- package/src/features/analytics/data/analytics-summary.interface.ts +11 -0
- package/src/features/analytics/data/analytics-summary.ts +79 -0
- package/src/features/analytics/data/analytics-timeline.interface.ts +10 -0
- package/src/features/analytics/data/analytics-timeline.ts +67 -0
- package/src/features/analytics/data/analytics.types.ts +97 -0
- package/src/features/analytics/data/index.ts +15 -0
- package/src/features/analytics/i18n-keys.ts +76 -0
- package/src/features/analytics/index.ts +31 -0
- package/src/features/analytics/lib/__tests__/utm.spec.ts +20 -0
- package/src/features/analytics/lib/__tests__/visitorCookie.spec.ts +63 -0
- package/src/features/analytics/lib/utm.ts +29 -0
- package/src/features/analytics/lib/visitorCookie.ts +32 -0
- package/src/features/analytics/server-entry.ts +8 -0
- package/src/features/waitlist/components/lists/WaitlistList.tsx +68 -121
- package/src/features/waitlist/data/WaitlistService.ts +2 -2
- package/src/features/waitlist/data/index.ts +1 -0
- package/src/features/waitlist/data/waitlist.fields.ts +8 -0
- package/src/features/waitlist/hooks/useWaitlistTableStructure.tsx +69 -30
- package/dist/JsonApiRequest-G625QAH5.js +0 -26
- package/dist/chunk-2XCUAHQT.js.map +0 -1
- package/dist/chunk-IBVKPLHT.mjs.map +0 -1
- package/dist/chunk-U7QVISQS.js.map +0 -1
- /package/dist/{BlockNoteEditor-65HR6ULV.mjs.map → BlockNoteEditor-46O3JC4Z.mjs.map} +0 -0
- /package/dist/{BlockNoteViewer-KDKGUIG5.mjs.map → BlockNoteViewer-KRTAFTUX.mjs.map} +0 -0
- /package/dist/{JsonApiRequest-RRMNV7YE.mjs.map → JsonApiRequest-UHOWNI3O.mjs.map} +0 -0
- /package/dist/{chunk-T76WLDE5.mjs.map → chunk-37DMFVLN.mjs.map} +0 -0
- /package/dist/{chunk-ZJI5ZZW5.mjs.map → chunk-Q6J2ICKO.mjs.map} +0 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Server-safe public barrel for the analytics feature.
|
|
2
|
+
// Consumed via `@carlonicora/nextjs-jsonapi/analytics/server`. Must NOT receive a
|
|
3
|
+
// "use client" directive — these are async server components that read the
|
|
4
|
+
// session cookie before rendering the client containers from
|
|
5
|
+
// `@carlonicora/nextjs-jsonapi/analytics`.
|
|
6
|
+
|
|
7
|
+
export { AnalyticsAdminPage } from "./components/server/AnalyticsAdminPage";
|
|
8
|
+
export { AnalyticsJourneyPage } from "./components/server/AnalyticsJourneyPage";
|
|
@@ -1,68 +1,52 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { RefreshCw, Users } from "lucide-react";
|
|
3
|
+
import { RefreshCw } from "lucide-react";
|
|
5
4
|
import { useTranslations } from "next-intl";
|
|
6
|
-
import {
|
|
7
|
-
import { errorToast } from "../../../../components";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
Select,
|
|
12
|
-
SelectContent,
|
|
13
|
-
SelectItem,
|
|
14
|
-
SelectTrigger,
|
|
15
|
-
SelectValue,
|
|
16
|
-
Table,
|
|
17
|
-
TableBody,
|
|
18
|
-
TableCell,
|
|
19
|
-
TableHead,
|
|
20
|
-
TableHeader,
|
|
21
|
-
TableRow,
|
|
22
|
-
} from "../../../../shadcnui";
|
|
5
|
+
import { useMemo, useState } from "react";
|
|
6
|
+
import { ContentListTable, errorToast } from "../../../../components";
|
|
7
|
+
import { Modules } from "../../../../core";
|
|
8
|
+
import { DataListRetriever, useDataListRetriever } from "../../../../hooks";
|
|
9
|
+
import { Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../../../shadcnui";
|
|
23
10
|
import { showToast } from "../../../../utils/toast";
|
|
11
|
+
import { WaitlistFields } from "../../data/waitlist.fields";
|
|
24
12
|
import { WaitlistInterface } from "../../data/WaitlistInterface";
|
|
25
13
|
import { WaitlistService } from "../../data/WaitlistService";
|
|
26
|
-
import { useWaitlistTableStructure } from "../../hooks/useWaitlistTableStructure";
|
|
27
14
|
|
|
28
|
-
|
|
15
|
+
type WaitlistListProps = {
|
|
16
|
+
/**
|
|
17
|
+
* Pass this whenever the list is the page's own content inside a
|
|
18
|
+
* `RoundPageContainer fullWidth`. Without it ContentListTable wraps itself in
|
|
19
|
+
* `rounded-md border`, drawing a second bordered card inside the page's
|
|
20
|
+
* rounded shell.
|
|
21
|
+
*/
|
|
22
|
+
fullWidth?: boolean;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function WaitlistList({ fullWidth }: WaitlistListProps = {}) {
|
|
29
26
|
const t = useTranslations();
|
|
30
|
-
const [entries, setEntries] = useState<WaitlistInterface[]>([]);
|
|
31
|
-
const [total, setTotal] = useState(0);
|
|
32
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
33
27
|
const [statusFilter, setStatusFilter] = useState<string>("all");
|
|
34
28
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
fetchAll: true,
|
|
41
|
-
});
|
|
42
|
-
setEntries(result);
|
|
43
|
-
setTotal(result.length);
|
|
44
|
-
} catch (error) {
|
|
45
|
-
errorToast({ error });
|
|
46
|
-
} finally {
|
|
47
|
-
setIsLoading(false);
|
|
48
|
-
}
|
|
49
|
-
}, [statusFilter]);
|
|
50
|
-
|
|
51
|
-
useEffect(() => {
|
|
52
|
-
loadEntries();
|
|
53
|
-
}, [loadEntries]);
|
|
29
|
+
const data: DataListRetriever<WaitlistInterface> = useDataListRetriever({
|
|
30
|
+
retriever: (params) => WaitlistService.findMany(params),
|
|
31
|
+
retrieverParams: {},
|
|
32
|
+
module: Modules.Waitlist,
|
|
33
|
+
});
|
|
54
34
|
|
|
55
35
|
const handleInvite = async (entry: WaitlistInterface) => {
|
|
56
36
|
try {
|
|
57
37
|
await WaitlistService.invite(entry.id);
|
|
58
38
|
showToast(t("waitlist.admin.invite_sent", { email: entry.email }));
|
|
59
|
-
|
|
39
|
+
void data.refresh();
|
|
60
40
|
} catch (error) {
|
|
61
41
|
errorToast({ error });
|
|
62
42
|
}
|
|
63
43
|
};
|
|
64
44
|
|
|
65
|
-
const
|
|
45
|
+
const handleStatusChange = (value: string | null) => {
|
|
46
|
+
const status = value ?? "all";
|
|
47
|
+
setStatusFilter(status);
|
|
48
|
+
data.addAdditionalParameter("status", status === "all" ? null : status);
|
|
49
|
+
};
|
|
66
50
|
|
|
67
51
|
const statusItems = useMemo(
|
|
68
52
|
() => ({
|
|
@@ -75,83 +59,46 @@ export function WaitlistList() {
|
|
|
75
59
|
[t],
|
|
76
60
|
);
|
|
77
61
|
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
</div>
|
|
93
|
-
|
|
94
|
-
<div className="flex items-center gap-4">
|
|
95
|
-
{/* Status Filter */}
|
|
96
|
-
<Select items={statusItems} value={statusFilter} onValueChange={(value) => setStatusFilter(value ?? "all")}>
|
|
97
|
-
<SelectTrigger className="w-40">
|
|
98
|
-
<SelectValue placeholder={t("waitlist.admin.filter_placeholder")} />
|
|
99
|
-
</SelectTrigger>
|
|
100
|
-
<SelectContent>
|
|
101
|
-
<SelectItem value="all">{t("waitlist.admin.all_statuses")}</SelectItem>
|
|
102
|
-
<SelectItem value="pending">{t("waitlist.admin.status.pending")}</SelectItem>
|
|
103
|
-
<SelectItem value="confirmed">{t("waitlist.admin.status.confirmed")}</SelectItem>
|
|
104
|
-
<SelectItem value="invited">{t("waitlist.admin.status.invited")}</SelectItem>
|
|
105
|
-
<SelectItem value="registered">{t("waitlist.admin.status.registered")}</SelectItem>
|
|
106
|
-
</SelectContent>
|
|
107
|
-
</Select>
|
|
62
|
+
const filters = (
|
|
63
|
+
<div className="flex items-center gap-4">
|
|
64
|
+
<Select items={statusItems} value={statusFilter} onValueChange={handleStatusChange}>
|
|
65
|
+
<SelectTrigger className="w-40">
|
|
66
|
+
<SelectValue placeholder={t("waitlist.admin.filter_placeholder")} />
|
|
67
|
+
</SelectTrigger>
|
|
68
|
+
<SelectContent>
|
|
69
|
+
<SelectItem value="all">{t("waitlist.admin.all_statuses")}</SelectItem>
|
|
70
|
+
<SelectItem value="pending">{t("waitlist.admin.status.pending")}</SelectItem>
|
|
71
|
+
<SelectItem value="confirmed">{t("waitlist.admin.status.confirmed")}</SelectItem>
|
|
72
|
+
<SelectItem value="invited">{t("waitlist.admin.status.invited")}</SelectItem>
|
|
73
|
+
<SelectItem value="registered">{t("waitlist.admin.status.registered")}</SelectItem>
|
|
74
|
+
</SelectContent>
|
|
75
|
+
</Select>
|
|
108
76
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
</Button>
|
|
113
|
-
</div>
|
|
114
|
-
</div>
|
|
115
|
-
|
|
116
|
-
{/* Table */}
|
|
117
|
-
<div className="rounded-md border">
|
|
118
|
-
<Table>
|
|
119
|
-
<TableHeader>
|
|
120
|
-
{table.getHeaderGroups().map((headerGroup) => (
|
|
121
|
-
<TableRow key={headerGroup.id}>
|
|
122
|
-
{headerGroup.headers.map((header) => (
|
|
123
|
-
<TableHead key={header.id}>
|
|
124
|
-
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
|
125
|
-
</TableHead>
|
|
126
|
-
))}
|
|
127
|
-
</TableRow>
|
|
128
|
-
))}
|
|
129
|
-
</TableHeader>
|
|
130
|
-
<TableBody>
|
|
131
|
-
{isLoading ? (
|
|
132
|
-
<TableRow>
|
|
133
|
-
<TableCell colSpan={columns.length} className="h-24 text-center">
|
|
134
|
-
{t("waitlist.admin.loading")}
|
|
135
|
-
</TableCell>
|
|
136
|
-
</TableRow>
|
|
137
|
-
) : entries.length === 0 ? (
|
|
138
|
-
<TableRow>
|
|
139
|
-
<TableCell colSpan={columns.length} className="h-24 text-center">
|
|
140
|
-
{t("waitlist.admin.empty")}
|
|
141
|
-
</TableCell>
|
|
142
|
-
</TableRow>
|
|
143
|
-
) : (
|
|
144
|
-
table.getRowModel().rows.map((row) => (
|
|
145
|
-
<TableRow key={row.id}>
|
|
146
|
-
{row.getVisibleCells().map((cell) => (
|
|
147
|
-
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
|
|
148
|
-
))}
|
|
149
|
-
</TableRow>
|
|
150
|
-
))
|
|
151
|
-
)}
|
|
152
|
-
</TableBody>
|
|
153
|
-
</Table>
|
|
154
|
-
</div>
|
|
77
|
+
<Button variant="outline" size="icon" onClick={() => void data.refresh()} disabled={!data.isLoaded}>
|
|
78
|
+
<RefreshCw className={`h-4 w-4 ${!data.isLoaded ? "animate-spin" : ""}`} />
|
|
79
|
+
</Button>
|
|
155
80
|
</div>
|
|
156
81
|
);
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<ContentListTable
|
|
85
|
+
data={data}
|
|
86
|
+
fields={[
|
|
87
|
+
WaitlistFields.email,
|
|
88
|
+
WaitlistFields.status,
|
|
89
|
+
WaitlistFields.createdAt,
|
|
90
|
+
WaitlistFields.questionnaire,
|
|
91
|
+
WaitlistFields.actions,
|
|
92
|
+
]}
|
|
93
|
+
tableGeneratorType={Modules.Waitlist}
|
|
94
|
+
title={t("waitlist.admin.title")}
|
|
95
|
+
filters={filters}
|
|
96
|
+
/* The waitlist endpoint takes no search term, so rendering the box would
|
|
97
|
+
give the page a control that silently does nothing. */
|
|
98
|
+
allowSearch={false}
|
|
99
|
+
context={{ onInvite: handleInvite }}
|
|
100
|
+
fullWidth={fullWidth}
|
|
101
|
+
emptyState={t("waitlist.admin.empty")}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
157
104
|
}
|
|
@@ -42,7 +42,7 @@ export class WaitlistService extends AbstractService {
|
|
|
42
42
|
search?: string;
|
|
43
43
|
fetchAll?: boolean;
|
|
44
44
|
next?: NextRef;
|
|
45
|
-
|
|
45
|
+
previous?: PreviousRef;
|
|
46
46
|
}): Promise<WaitlistInterface[]> {
|
|
47
47
|
const endpoint = new EndpointCreator({ endpoint: Modules.Waitlist });
|
|
48
48
|
|
|
@@ -55,7 +55,7 @@ export class WaitlistService extends AbstractService {
|
|
|
55
55
|
method: HttpMethod.GET,
|
|
56
56
|
endpoint: endpoint.generate(),
|
|
57
57
|
next: params?.next,
|
|
58
|
-
previous: params?.
|
|
58
|
+
previous: params?.previous,
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
import { ColumnDef } from "@tanstack/react-table";
|
|
4
4
|
import { Send } from "lucide-react";
|
|
5
5
|
import { useTranslations } from "next-intl";
|
|
6
|
+
import { useMemo } from "react";
|
|
7
|
+
import { TableContent, UseTableStructureHook } from "../../../hooks";
|
|
6
8
|
import { Badge, Button } from "../../../shadcnui";
|
|
9
|
+
import { WaitlistFields } from "../data/waitlist.fields";
|
|
7
10
|
import { WaitlistInterface, WaitlistStatus } from "../data/WaitlistInterface";
|
|
8
11
|
|
|
9
|
-
interface UseWaitlistTableStructureProps {
|
|
10
|
-
onInvite: (entry: WaitlistInterface) => void;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
12
|
/**
|
|
14
13
|
* Parse questionnaire JSON string safely
|
|
15
14
|
*/
|
|
@@ -22,10 +21,25 @@ function parseQuestionnaire(questionnaire: string | undefined): Record<string, a
|
|
|
22
21
|
}
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Columns for the administrative waitlist. The invite action needs the list's
|
|
26
|
+
* own handler, so WaitlistList passes it through ContentListTable's `context`
|
|
27
|
+
* as `onInvite`.
|
|
28
|
+
*/
|
|
29
|
+
export const useWaitlistTableStructure: UseTableStructureHook<WaitlistInterface, WaitlistFields> = (params) => {
|
|
28
30
|
const t = useTranslations();
|
|
31
|
+
const onInvite: ((entry: WaitlistInterface) => void) | undefined = params.context?.onInvite;
|
|
32
|
+
|
|
33
|
+
const tableData = useMemo(() => {
|
|
34
|
+
return params.data.map((entry: WaitlistInterface) => {
|
|
35
|
+
const row: TableContent<WaitlistInterface> = { jsonApiData: entry };
|
|
36
|
+
row[WaitlistFields.waitlistId] = entry.id;
|
|
37
|
+
params.fields.forEach((field) => {
|
|
38
|
+
row[field] = (entry as any)[field as keyof WaitlistInterface];
|
|
39
|
+
});
|
|
40
|
+
return row;
|
|
41
|
+
});
|
|
42
|
+
}, [params.data, params.fields]);
|
|
29
43
|
|
|
30
44
|
const getStatusBadge = (status: WaitlistStatus) => {
|
|
31
45
|
const variants: Record<WaitlistStatus, { variant: "default" | "secondary" | "outline" | "destructive" }> = {
|
|
@@ -39,31 +53,44 @@ export function useWaitlistTableStructure({
|
|
|
39
53
|
return <Badge variant={config.variant}>{t(`waitlist.admin.status.${status}`)}</Badge>;
|
|
40
54
|
};
|
|
41
55
|
|
|
42
|
-
|
|
43
|
-
{
|
|
56
|
+
const fieldColumnMap: Partial<Record<WaitlistFields, () => any>> = {
|
|
57
|
+
[WaitlistFields.email]: () => ({
|
|
58
|
+
id: "email",
|
|
44
59
|
accessorKey: "email",
|
|
45
60
|
header: t("waitlist.admin.columns.email"),
|
|
46
|
-
cell: ({ row }
|
|
47
|
-
|
|
48
|
-
|
|
61
|
+
cell: ({ row }: { row: TableContent<WaitlistInterface> }) => (
|
|
62
|
+
<span className="font-medium">{row.original.jsonApiData.email}</span>
|
|
63
|
+
),
|
|
64
|
+
enableSorting: false,
|
|
65
|
+
enableHiding: false,
|
|
66
|
+
}),
|
|
67
|
+
[WaitlistFields.status]: () => ({
|
|
68
|
+
id: "status",
|
|
49
69
|
accessorKey: "status",
|
|
50
70
|
header: t("waitlist.admin.columns.status"),
|
|
51
|
-
cell: ({ row }) => getStatusBadge(row.original.status),
|
|
52
|
-
|
|
53
|
-
|
|
71
|
+
cell: ({ row }: { row: TableContent<WaitlistInterface> }) => getStatusBadge(row.original.jsonApiData.status),
|
|
72
|
+
enableSorting: false,
|
|
73
|
+
enableHiding: false,
|
|
74
|
+
}),
|
|
75
|
+
[WaitlistFields.createdAt]: () => ({
|
|
76
|
+
id: "createdAt",
|
|
54
77
|
accessorKey: "createdAt",
|
|
55
78
|
header: t("waitlist.admin.columns.submitted"),
|
|
56
|
-
cell: ({ row }) => {
|
|
57
|
-
|
|
58
|
-
|
|
79
|
+
cell: ({ row }: { row: TableContent<WaitlistInterface> }) => {
|
|
80
|
+
const entry: WaitlistInterface = row.original.jsonApiData;
|
|
81
|
+
if (!entry.createdAt) return "-";
|
|
82
|
+
return new Date(entry.createdAt).toLocaleDateString();
|
|
59
83
|
},
|
|
60
|
-
|
|
61
|
-
|
|
84
|
+
enableSorting: false,
|
|
85
|
+
enableHiding: false,
|
|
86
|
+
}),
|
|
87
|
+
[WaitlistFields.questionnaire]: () => ({
|
|
88
|
+
id: "questionnaire",
|
|
62
89
|
accessorKey: "questionnaire",
|
|
63
90
|
header: t("waitlist.admin.columns.questionnaire"),
|
|
64
|
-
cell: ({ row }) => {
|
|
91
|
+
cell: ({ row }: { row: TableContent<WaitlistInterface> }) => {
|
|
65
92
|
// IMPORTANT: Parse JSON string from backend
|
|
66
|
-
const questionnaire = parseQuestionnaire(row.original.questionnaire);
|
|
93
|
+
const questionnaire = parseQuestionnaire(row.original.jsonApiData.questionnaire);
|
|
67
94
|
if (!questionnaire || Object.keys(questionnaire).length === 0) {
|
|
68
95
|
return <span className="text-muted-foreground">-</span>;
|
|
69
96
|
}
|
|
@@ -84,16 +111,18 @@ export function useWaitlistTableStructure({
|
|
|
84
111
|
</details>
|
|
85
112
|
);
|
|
86
113
|
},
|
|
87
|
-
|
|
88
|
-
|
|
114
|
+
enableSorting: false,
|
|
115
|
+
enableHiding: false,
|
|
116
|
+
}),
|
|
117
|
+
[WaitlistFields.actions]: () => ({
|
|
89
118
|
id: "actions",
|
|
90
119
|
header: t("waitlist.admin.columns.actions"),
|
|
91
|
-
cell: ({ row }) => {
|
|
92
|
-
const entry = row.original;
|
|
120
|
+
cell: ({ row }: { row: TableContent<WaitlistInterface> }) => {
|
|
121
|
+
const entry: WaitlistInterface = row.original.jsonApiData;
|
|
93
122
|
|
|
94
123
|
if (entry.status === "confirmed") {
|
|
95
124
|
return (
|
|
96
|
-
<Button size="sm" variant="outline" onClick={() => onInvite(entry)}>
|
|
125
|
+
<Button size="sm" variant="outline" onClick={() => onInvite?.(entry)}>
|
|
97
126
|
<Send className="me-2 h-4 w-4" />
|
|
98
127
|
{t("waitlist.admin.actions.invite")}
|
|
99
128
|
</Button>
|
|
@@ -116,6 +145,16 @@ export function useWaitlistTableStructure({
|
|
|
116
145
|
<span className="text-muted-foreground text-sm">{t("waitlist.admin.actions.awaiting_confirmation")}</span>
|
|
117
146
|
);
|
|
118
147
|
},
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
148
|
+
enableSorting: false,
|
|
149
|
+
enableHiding: false,
|
|
150
|
+
}),
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const columns = useMemo(() => {
|
|
154
|
+
return params.fields.map((field) => fieldColumnMap[field]?.()).filter((col) => col !== undefined) as ColumnDef<
|
|
155
|
+
TableContent<WaitlistInterface>
|
|
156
|
+
>[];
|
|
157
|
+
}, [params.fields, fieldColumnMap, t]);
|
|
158
|
+
|
|
159
|
+
return useMemo(() => ({ data: tableData, columns: columns }), [tableData, columns]);
|
|
160
|
+
};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
var _chunkYXRVPEEJjs = require('./chunk-YXRVPEEJ.js');
|
|
13
|
-
require('./chunk-7QVYU63E.js');
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
exports.JsonApiDelete = _chunkYXRVPEEJjs.JsonApiDelete; exports.JsonApiGet = _chunkYXRVPEEJjs.JsonApiGet; exports.JsonApiPatch = _chunkYXRVPEEJjs.JsonApiPatch; exports.JsonApiPost = _chunkYXRVPEEJjs.JsonApiPost; exports.JsonApiPut = _chunkYXRVPEEJjs.JsonApiPut; exports.buildUrl = _chunkYXRVPEEJjs.buildUrl; exports.configureJsonApi = _chunkYXRVPEEJjs.configureJsonApi; exports.getApiUrl = _chunkYXRVPEEJjs.getApiUrl; exports.getAppUrl = _chunkYXRVPEEJjs.getAppUrl; exports.getTrackablePages = _chunkYXRVPEEJjs.getTrackablePages;
|
|
26
|
-
//# sourceMappingURL=JsonApiRequest-G625QAH5.js.map
|