@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.
Files changed (178) hide show
  1. package/dist/{AssistantMessageInterface-1Kbz8RVs.d.ts → AssistantMessageInterface-CubxuBIJ.d.ts} +1 -1
  2. package/dist/{AssistantMessageInterface-CoxJyuFM.d.mts → AssistantMessageInterface-sHjI2zPN.d.mts} +1 -1
  3. package/dist/{BlockNoteEditor-65HR6ULV.mjs → BlockNoteEditor-46O3JC4Z.mjs} +5 -5
  4. package/dist/{BlockNoteEditor-35D4R7HJ.js → BlockNoteEditor-HKS6AXDA.js} +20 -20
  5. package/dist/{BlockNoteEditor-35D4R7HJ.js.map → BlockNoteEditor-HKS6AXDA.js.map} +1 -1
  6. package/dist/{BlockNoteViewer-QDXDVYEM.js → BlockNoteViewer-7BFNTN4E.js} +4 -4
  7. package/dist/{BlockNoteViewer-QDXDVYEM.js.map → BlockNoteViewer-7BFNTN4E.js.map} +1 -1
  8. package/dist/{BlockNoteViewer-KDKGUIG5.mjs → BlockNoteViewer-KRTAFTUX.mjs} +3 -3
  9. package/dist/{HandbookSectionInterface-OePKPbqN.d.ts → HandbookSectionInterface-Ck1wZJ81.d.ts} +1 -1
  10. package/dist/{HandbookSectionInterface-BYsmnPEz.d.mts → HandbookSectionInterface-DgYH--Ik.d.mts} +1 -1
  11. package/dist/JsonApiRequest-TE5WD3WS.js +26 -0
  12. package/dist/{JsonApiRequest-G625QAH5.js.map → JsonApiRequest-TE5WD3WS.js.map} +1 -1
  13. package/dist/{JsonApiRequest-RRMNV7YE.mjs → JsonApiRequest-UHOWNI3O.mjs} +2 -2
  14. package/dist/analytics.modules-CfczU12P.d.ts +25 -0
  15. package/dist/analytics.modules-pzGwMASI.d.mts +25 -0
  16. package/dist/{auth.interface-BC1jp_ni.d.ts → auth.interface-BzI4onI_.d.ts} +1 -1
  17. package/dist/{auth.interface-dR2-6gPM.d.mts → auth.interface-Df4vXbNz.d.mts} +1 -1
  18. package/dist/billing/index.js +402 -402
  19. package/dist/billing/index.mjs +4 -4
  20. package/dist/{chunk-YXRVPEEJ.js → chunk-26MQIJNB.js} +1 -1
  21. package/dist/{chunk-YXRVPEEJ.js.map → chunk-26MQIJNB.js.map} +1 -1
  22. package/dist/{chunk-T76WLDE5.mjs → chunk-37DMFVLN.mjs} +1 -1
  23. package/dist/chunk-4Z2UONN2.mjs +110 -0
  24. package/dist/chunk-4Z2UONN2.mjs.map +1 -0
  25. package/dist/{chunk-3AR4KA5D.js → chunk-4Z45NYPR.js} +13 -13
  26. package/dist/{chunk-3AR4KA5D.js.map → chunk-4Z45NYPR.js.map} +1 -1
  27. package/dist/chunk-7B4E42R5.mjs +280 -0
  28. package/dist/chunk-7B4E42R5.mjs.map +1 -0
  29. package/dist/{chunk-U7QVISQS.js → chunk-CAKOFJZD.js} +450 -40
  30. package/dist/chunk-CAKOFJZD.js.map +1 -0
  31. package/dist/{chunk-IBVKPLHT.mjs → chunk-E3I4MT53.mjs} +422 -12
  32. package/dist/chunk-E3I4MT53.mjs.map +1 -0
  33. package/dist/chunk-GHFMKH3M.js +280 -0
  34. package/dist/chunk-GHFMKH3M.js.map +1 -0
  35. package/dist/chunk-KLV6RLDE.js +110 -0
  36. package/dist/chunk-KLV6RLDE.js.map +1 -0
  37. package/dist/{chunk-ZJI5ZZW5.mjs → chunk-Q6J2ICKO.mjs} +3 -3
  38. package/dist/{chunk-4O7R2Z2O.mjs → chunk-XDYILJ2S.mjs} +1749 -1737
  39. package/dist/{chunk-4O7R2Z2O.mjs.map → chunk-XDYILJ2S.mjs.map} +1 -1
  40. package/dist/{chunk-2XCUAHQT.js → chunk-Y3OKEQGL.js} +1126 -1114
  41. package/dist/chunk-Y3OKEQGL.js.map +1 -0
  42. package/dist/client/index.d.mts +6 -5
  43. package/dist/client/index.d.ts +6 -5
  44. package/dist/client/index.js +5 -5
  45. package/dist/client/index.js.map +1 -1
  46. package/dist/client/index.mjs +4 -4
  47. package/dist/components/index.d.mts +17 -7
  48. package/dist/components/index.d.ts +17 -7
  49. package/dist/components/index.js +5 -5
  50. package/dist/components/index.mjs +4 -4
  51. package/dist/{config-B6UB7j69.d.ts → config-3N1LSrcC.d.ts} +1 -1
  52. package/dist/{config-Cd5xTEfZ.d.ts → config-AVtuvfM1.d.ts} +1 -1
  53. package/dist/{config-n-ZpPmOL.d.mts → config-BSKTYDoh.d.mts} +1 -1
  54. package/dist/{config-YyhFHxkU.d.mts → config-BmufyEj6.d.mts} +1 -1
  55. package/dist/contexts/index.d.mts +5 -4
  56. package/dist/contexts/index.d.ts +5 -4
  57. package/dist/contexts/index.js +5 -5
  58. package/dist/contexts/index.mjs +4 -4
  59. package/dist/core/index.d.mts +31 -14
  60. package/dist/core/index.d.ts +31 -14
  61. package/dist/core/index.js +9 -5
  62. package/dist/core/index.js.map +1 -1
  63. package/dist/core/index.mjs +8 -4
  64. package/dist/features/analytics/index.css +30 -0
  65. package/dist/features/analytics/index.css.map +1 -0
  66. package/dist/features/analytics/index.d.mts +508 -0
  67. package/dist/features/analytics/index.d.ts +508 -0
  68. package/dist/features/analytics/index.js +1166 -0
  69. package/dist/features/analytics/index.js.map +1 -0
  70. package/dist/features/analytics/index.mjs +1166 -0
  71. package/dist/features/analytics/index.mjs.map +1 -0
  72. package/dist/features/analytics/server.d.mts +28 -0
  73. package/dist/features/analytics/server.d.ts +28 -0
  74. package/dist/features/analytics/server.js +41 -0
  75. package/dist/features/analytics/server.js.map +1 -0
  76. package/dist/features/analytics/server.mjs +41 -0
  77. package/dist/features/analytics/server.mjs.map +1 -0
  78. package/dist/features/help/index.d.mts +2 -2
  79. package/dist/features/help/index.d.ts +2 -2
  80. package/dist/features/help/index.js +38 -38
  81. package/dist/features/help/index.mjs +4 -4
  82. package/dist/features/tokenusage/index.d.mts +2 -2
  83. package/dist/features/tokenusage/index.d.ts +2 -2
  84. package/dist/features/tokenusage/index.js +139 -221
  85. package/dist/features/tokenusage/index.js.map +1 -1
  86. package/dist/features/tokenusage/index.mjs +26 -108
  87. package/dist/features/tokenusage/index.mjs.map +1 -1
  88. package/dist/index.d.mts +11 -9
  89. package/dist/index.d.ts +11 -9
  90. package/dist/index.js +10 -6
  91. package/dist/index.js.map +1 -1
  92. package/dist/index.mjs +9 -5
  93. package/dist/notification.interface-BNCvnVCW.d.ts +34 -0
  94. package/dist/notification.interface-P_pNfzZQ.d.mts +34 -0
  95. package/dist/{s3.service-Bqr-fQmi.d.ts → s3.service-CbUa6a-8.d.ts} +3 -2
  96. package/dist/{s3.service-Buiy1ncj.d.mts → s3.service-DAp8FbuG.d.mts} +3 -2
  97. package/dist/server/index.d.mts +5 -4
  98. package/dist/server/index.d.ts +5 -4
  99. package/dist/server/index.js +39 -279
  100. package/dist/server/index.js.map +1 -1
  101. package/dist/server/index.mjs +20 -260
  102. package/dist/server/index.mjs.map +1 -1
  103. package/dist/{types-bsm8Fp55.d.ts → types-Cn153-mH.d.ts} +1 -1
  104. package/dist/{types-Bq_UA8Lg.d.mts → types-D0uLYV2P.d.mts} +1 -1
  105. package/dist/{usePageUrlGenerator-J_ECv-oQ.d.ts → usePageUrlGenerator-B-3FRKBD.d.ts} +1 -1
  106. package/dist/{usePageUrlGenerator-uKBjAKqC.d.mts → usePageUrlGenerator-CCJ_cdQy.d.mts} +1 -1
  107. package/dist/{useSocket-BcH7l7B8.d.mts → useSocket-B9gTbuCj.d.mts} +1 -1
  108. package/dist/{useSocket-DCZd_eJk.d.ts → useSocket-BHVIyigI.d.ts} +1 -1
  109. package/dist/{notification.interface-MnRt1Tjm.d.mts → user.interface-CSaGWFbg.d.mts} +1 -31
  110. package/dist/{notification.interface-CjzV_KII.d.ts → user.interface-CrmwJLYW.d.ts} +1 -31
  111. package/package.json +11 -1
  112. package/src/client/index.ts +2 -0
  113. package/src/core/index.ts +4 -0
  114. package/src/core/registry/ModuleRegistry.ts +7 -0
  115. package/src/features/__tests__/i18n-keys.contract.spec.ts +1 -0
  116. package/src/features/analytics/__tests__/server-entry.spec.tsx +64 -0
  117. package/src/features/analytics/analytics.modules.ts +30 -0
  118. package/src/features/analytics/components/AnalyticsAdminContainer.tsx +97 -0
  119. package/src/features/analytics/components/AnalyticsAdminFilterBar.tsx +117 -0
  120. package/src/features/analytics/components/AnalyticsAdminPageContainer.tsx +25 -0
  121. package/src/features/analytics/components/AnalyticsAdminTiles.tsx +191 -0
  122. package/src/features/analytics/components/AnalyticsBreakdownTable.tsx +104 -0
  123. package/src/features/analytics/components/AnalyticsJourneyContainer.tsx +246 -0
  124. package/src/features/analytics/components/AnalyticsJourneyPageContainer.tsx +14 -0
  125. package/src/features/analytics/components/AnalyticsSessionsTable.tsx +107 -0
  126. package/src/features/analytics/components/AnalyticsTimelineChart.tsx +175 -0
  127. package/src/features/analytics/components/AnalyticsTracker.tsx +112 -0
  128. package/src/features/analytics/components/__tests__/AnalyticsAdminTiles.spec.tsx +39 -0
  129. package/src/features/analytics/components/__tests__/AnalyticsBreakdownTable.spec.tsx +39 -0
  130. package/src/features/analytics/components/__tests__/AnalyticsJourneyContainer.spec.tsx +124 -0
  131. package/src/features/analytics/components/__tests__/AnalyticsPageContainers.spec.tsx +117 -0
  132. package/src/features/analytics/components/__tests__/AnalyticsSessionsTable.spec.tsx +121 -0
  133. package/src/features/analytics/components/__tests__/AnalyticsTimelineChart.spec.tsx +35 -0
  134. package/src/features/analytics/components/__tests__/AnalyticsTracker.spec.tsx +248 -0
  135. package/src/features/analytics/components/server/AnalyticsAdminPage.tsx +28 -0
  136. package/src/features/analytics/components/server/AnalyticsJourneyPage.tsx +25 -0
  137. package/src/features/analytics/contexts/AnalyticsAdminContext.tsx +273 -0
  138. package/src/features/analytics/contexts/__tests__/AnalyticsAdminContext.spec.tsx +140 -0
  139. package/src/features/analytics/data/AnalyticsAdminService.ts +91 -0
  140. package/src/features/analytics/data/AnalyticsService.ts +23 -0
  141. package/src/features/analytics/data/__tests__/AnalyticsAdminService.spec.ts +117 -0
  142. package/src/features/analytics/data/__tests__/AnalyticsService.spec.ts +56 -0
  143. package/src/features/analytics/data/__tests__/models.spec.ts +300 -0
  144. package/src/features/analytics/data/analytics-breakdown.interface.ts +9 -0
  145. package/src/features/analytics/data/analytics-breakdown.ts +65 -0
  146. package/src/features/analytics/data/analytics-event.interface.ts +7 -0
  147. package/src/features/analytics/data/analytics-event.ts +63 -0
  148. package/src/features/analytics/data/analytics-page-view.interface.ts +9 -0
  149. package/src/features/analytics/data/analytics-page-view.ts +57 -0
  150. package/src/features/analytics/data/analytics-session.interface.ts +23 -0
  151. package/src/features/analytics/data/analytics-session.ts +144 -0
  152. package/src/features/analytics/data/analytics-summary.interface.ts +11 -0
  153. package/src/features/analytics/data/analytics-summary.ts +79 -0
  154. package/src/features/analytics/data/analytics-timeline.interface.ts +10 -0
  155. package/src/features/analytics/data/analytics-timeline.ts +67 -0
  156. package/src/features/analytics/data/analytics.types.ts +97 -0
  157. package/src/features/analytics/data/index.ts +15 -0
  158. package/src/features/analytics/i18n-keys.ts +76 -0
  159. package/src/features/analytics/index.ts +31 -0
  160. package/src/features/analytics/lib/__tests__/utm.spec.ts +20 -0
  161. package/src/features/analytics/lib/__tests__/visitorCookie.spec.ts +63 -0
  162. package/src/features/analytics/lib/utm.ts +29 -0
  163. package/src/features/analytics/lib/visitorCookie.ts +32 -0
  164. package/src/features/analytics/server-entry.ts +8 -0
  165. package/src/features/waitlist/components/lists/WaitlistList.tsx +68 -121
  166. package/src/features/waitlist/data/WaitlistService.ts +2 -2
  167. package/src/features/waitlist/data/index.ts +1 -0
  168. package/src/features/waitlist/data/waitlist.fields.ts +8 -0
  169. package/src/features/waitlist/hooks/useWaitlistTableStructure.tsx +69 -30
  170. package/dist/JsonApiRequest-G625QAH5.js +0 -26
  171. package/dist/chunk-2XCUAHQT.js.map +0 -1
  172. package/dist/chunk-IBVKPLHT.mjs.map +0 -1
  173. package/dist/chunk-U7QVISQS.js.map +0 -1
  174. /package/dist/{BlockNoteEditor-65HR6ULV.mjs.map → BlockNoteEditor-46O3JC4Z.mjs.map} +0 -0
  175. /package/dist/{BlockNoteViewer-KDKGUIG5.mjs.map → BlockNoteViewer-KRTAFTUX.mjs.map} +0 -0
  176. /package/dist/{JsonApiRequest-RRMNV7YE.mjs.map → JsonApiRequest-UHOWNI3O.mjs.map} +0 -0
  177. /package/dist/{chunk-T76WLDE5.mjs.map → chunk-37DMFVLN.mjs.map} +0 -0
  178. /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 { flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table";
4
- import { RefreshCw, Users } from "lucide-react";
3
+ import { RefreshCw } from "lucide-react";
5
4
  import { useTranslations } from "next-intl";
6
- import { useCallback, useEffect, useMemo, useState } from "react";
7
- import { errorToast } from "../../../../components";
8
- import { SectionHeader } from "../../../../components/typography";
9
- import {
10
- Button,
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
- export function WaitlistList() {
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 loadEntries = useCallback(async () => {
36
- setIsLoading(true);
37
- try {
38
- const result = await WaitlistService.findMany({
39
- status: statusFilter === "all" ? undefined : statusFilter,
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
- loadEntries();
39
+ void data.refresh();
60
40
  } catch (error) {
61
41
  errorToast({ error });
62
42
  }
63
43
  };
64
44
 
65
- const columns = useWaitlistTableStructure({ onInvite: handleInvite });
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 table = useReactTable({
79
- data: entries,
80
- columns,
81
- getCoreRowModel: getCoreRowModel(),
82
- });
83
-
84
- return (
85
- <div className="space-y-4">
86
- {/* Header */}
87
- <div className="flex items-center justify-between">
88
- <div className="flex items-center gap-2">
89
- <Users className="h-5 w-5" />
90
- <SectionHeader level={2}>{t("waitlist.admin.title")}</SectionHeader>
91
- <span className="text-muted-foreground">({t("waitlist.admin.entries_count", { count: total })})</span>
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
- {/* Refresh Button */}
110
- <Button variant="outline" size="icon" onClick={loadEntries} disabled={isLoading}>
111
- <RefreshCw className={`h-4 w-4 ${isLoading ? "animate-spin" : ""}`} />
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
- prev?: PreviousRef;
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?.prev,
58
+ previous: params?.previous,
59
59
  });
60
60
  }
61
61
 
@@ -3,3 +3,4 @@ export * from "./Waitlist";
3
3
  export * from "./WaitlistService";
4
4
  export * from "./waitlist-stats.interface";
5
5
  export * from "./waitlist-stats";
6
+ export * from "./waitlist.fields";
@@ -0,0 +1,8 @@
1
+ export enum WaitlistFields {
2
+ waitlistId = "waitlistId",
3
+ email = "email",
4
+ status = "status",
5
+ createdAt = "createdAt",
6
+ questionnaire = "questionnaire",
7
+ actions = "actions",
8
+ }
@@ -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
- export function useWaitlistTableStructure({
26
- onInvite,
27
- }: UseWaitlistTableStructureProps): ColumnDef<WaitlistInterface>[] {
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
- return [
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 }) => <span className="font-medium">{row.original.email}</span>,
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
- if (!row.original.createdAt) return "-";
58
- return new Date(row.original.createdAt).toLocaleDateString();
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