@carlonicora/nextjs-jsonapi 1.39.2 → 1.40.1

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 (84) hide show
  1. package/dist/{BlockNoteEditor-WQXQPLMX.js → BlockNoteEditor-4G3L3LSF.js} +14 -14
  2. package/dist/{BlockNoteEditor-WQXQPLMX.js.map → BlockNoteEditor-4G3L3LSF.js.map} +1 -1
  3. package/dist/{BlockNoteEditor-CITC7I2Z.mjs → BlockNoteEditor-EKY4AHVK.mjs} +4 -4
  4. package/dist/billing/index.js +346 -346
  5. package/dist/billing/index.mjs +3 -3
  6. package/dist/{chunk-LDH2FGJY.mjs → chunk-BAOP6PTD.mjs} +689 -34
  7. package/dist/chunk-BAOP6PTD.mjs.map +1 -0
  8. package/dist/{chunk-2RBYXY6T.js → chunk-GKY5DAIH.js} +1228 -573
  9. package/dist/chunk-GKY5DAIH.js.map +1 -0
  10. package/dist/{chunk-TQ5GRRTM.mjs → chunk-GVN7XC3U.mjs} +278 -2
  11. package/dist/chunk-GVN7XC3U.mjs.map +1 -0
  12. package/dist/{chunk-XLMJPA4N.mjs → chunk-RRIYLEY6.mjs} +22 -2
  13. package/dist/chunk-RRIYLEY6.mjs.map +1 -0
  14. package/dist/{chunk-2PHWAL6Q.js → chunk-T5YYOT4Z.js} +22 -2
  15. package/dist/chunk-T5YYOT4Z.js.map +1 -0
  16. package/dist/{chunk-3EZX4G2E.js → chunk-ZNGEVB5M.js} +279 -3
  17. package/dist/chunk-ZNGEVB5M.js.map +1 -0
  18. package/dist/client/index.js +4 -4
  19. package/dist/client/index.mjs +3 -3
  20. package/dist/components/index.d.mts +28 -4
  21. package/dist/components/index.d.ts +28 -4
  22. package/dist/components/index.js +16 -4
  23. package/dist/components/index.js.map +1 -1
  24. package/dist/components/index.mjs +15 -3
  25. package/dist/contexts/index.js +4 -4
  26. package/dist/contexts/index.mjs +3 -3
  27. package/dist/core/index.d.mts +127 -3
  28. package/dist/core/index.d.ts +127 -3
  29. package/dist/core/index.js +12 -2
  30. package/dist/core/index.js.map +1 -1
  31. package/dist/core/index.mjs +11 -1
  32. package/dist/index.d.mts +5 -2
  33. package/dist/index.d.ts +5 -2
  34. package/dist/index.js +17 -3
  35. package/dist/index.js.map +1 -1
  36. package/dist/index.mjs +16 -2
  37. package/dist/{s3.service-hnTPVTm2.d.mts → s3.service-BoOF5-ln.d.mts} +1 -0
  38. package/dist/{s3.service-DXkDoMf1.d.ts → s3.service-Mxo-7wQ6.d.ts} +1 -0
  39. package/dist/server/index.d.mts +1 -1
  40. package/dist/server/index.d.ts +1 -1
  41. package/dist/server/index.js +3 -3
  42. package/dist/server/index.mjs +1 -1
  43. package/dist/waitlist.config-kPfjImle.d.mts +26 -0
  44. package/dist/waitlist.config-kPfjImle.d.ts +26 -0
  45. package/package.json +1 -1
  46. package/src/components/forms/FormCheckbox.tsx +1 -1
  47. package/src/components/forms/FormSelect.tsx +1 -1
  48. package/src/components/index.ts +1 -0
  49. package/src/core/index.ts +3 -0
  50. package/src/core/registry/ModuleRegistry.ts +3 -0
  51. package/src/features/auth/components/forms/Register.tsx +180 -1
  52. package/src/features/auth/data/auth.interface.ts +1 -0
  53. package/src/features/auth/data/auth.ts +1 -0
  54. package/src/features/index.ts +1 -0
  55. package/src/features/waitlist/components/forms/WaitlistForm.tsx +186 -0
  56. package/src/features/waitlist/components/forms/WaitlistQuestionnaireRenderer.tsx +110 -0
  57. package/src/features/waitlist/components/forms/index.ts +2 -0
  58. package/src/features/waitlist/components/index.ts +3 -0
  59. package/src/features/waitlist/components/lists/WaitlistList.tsx +145 -0
  60. package/src/features/waitlist/components/lists/index.ts +1 -0
  61. package/src/features/waitlist/components/sections/WaitlistConfirmation.tsx +68 -0
  62. package/src/features/waitlist/components/sections/WaitlistHeroSection.tsx +49 -0
  63. package/src/features/waitlist/components/sections/WaitlistSuccessState.tsx +19 -0
  64. package/src/features/waitlist/components/sections/index.ts +3 -0
  65. package/src/features/waitlist/config/waitlist.config.ts +35 -0
  66. package/src/features/waitlist/data/Waitlist.ts +104 -0
  67. package/src/features/waitlist/data/WaitlistInterface.ts +32 -0
  68. package/src/features/waitlist/data/WaitlistService.ts +153 -0
  69. package/src/features/waitlist/data/index.ts +5 -0
  70. package/src/features/waitlist/data/waitlist-stats.interface.ts +9 -0
  71. package/src/features/waitlist/data/waitlist-stats.ts +47 -0
  72. package/src/features/waitlist/hooks/useWaitlistTableStructure.tsx +121 -0
  73. package/src/features/waitlist/index.ts +28 -0
  74. package/src/features/waitlist/waitlist-stats.module.ts +8 -0
  75. package/src/features/waitlist/waitlist.module.ts +9 -0
  76. package/src/index.ts +9 -0
  77. package/src/login/config.ts +9 -0
  78. package/dist/chunk-2PHWAL6Q.js.map +0 -1
  79. package/dist/chunk-2RBYXY6T.js.map +0 -1
  80. package/dist/chunk-3EZX4G2E.js.map +0 -1
  81. package/dist/chunk-LDH2FGJY.mjs.map +0 -1
  82. package/dist/chunk-TQ5GRRTM.mjs.map +0 -1
  83. package/dist/chunk-XLMJPA4N.mjs.map +0 -1
  84. /package/dist/{BlockNoteEditor-CITC7I2Z.mjs.map → BlockNoteEditor-EKY4AHVK.mjs.map} +0 -0
@@ -0,0 +1,104 @@
1
+ import { AbstractApiData, JsonApiHydratedDataInterface, Modules } from "../../../core";
2
+ import { WaitlistInput, WaitlistInterface, WaitlistStatus } from "./WaitlistInterface";
3
+
4
+ export class Waitlist extends AbstractApiData implements WaitlistInterface {
5
+ private _email?: string;
6
+ private _gdprConsent: boolean = false;
7
+ private _gdprConsentAt?: string;
8
+ private _marketingConsent?: boolean;
9
+ private _marketingConsentAt?: string;
10
+ private _questionnaire?: string;
11
+ private _status: WaitlistStatus = "pending";
12
+ private _confirmedAt?: string;
13
+ private _invitedAt?: string;
14
+ private _registeredAt?: string;
15
+
16
+ get email(): string {
17
+ return this._email ?? "";
18
+ }
19
+
20
+ get gdprConsent(): boolean {
21
+ return this._gdprConsent;
22
+ }
23
+
24
+ get gdprConsentAt(): string {
25
+ return this._gdprConsentAt ?? "";
26
+ }
27
+
28
+ get marketingConsent(): boolean | undefined {
29
+ return this._marketingConsent;
30
+ }
31
+
32
+ get marketingConsentAt(): string | undefined {
33
+ return this._marketingConsentAt;
34
+ }
35
+
36
+ get questionnaire(): string | undefined {
37
+ return this._questionnaire;
38
+ }
39
+
40
+ get status(): WaitlistStatus {
41
+ return this._status;
42
+ }
43
+
44
+ get confirmedAt(): string | undefined {
45
+ return this._confirmedAt;
46
+ }
47
+
48
+ get invitedAt(): string | undefined {
49
+ return this._invitedAt;
50
+ }
51
+
52
+ get registeredAt(): string | undefined {
53
+ return this._registeredAt;
54
+ }
55
+
56
+ rehydrate(data: JsonApiHydratedDataInterface): this {
57
+ super.rehydrate(data);
58
+
59
+ this._email = data.jsonApi.attributes.email;
60
+ this._gdprConsent = data.jsonApi.attributes.gdprConsent;
61
+ this._gdprConsentAt = data.jsonApi.attributes.gdprConsentAt;
62
+ this._marketingConsent = data.jsonApi.attributes.marketingConsent;
63
+ this._marketingConsentAt = data.jsonApi.attributes.marketingConsentAt;
64
+ this._questionnaire = data.jsonApi.attributes.questionnaire;
65
+ this._status = data.jsonApi.attributes.status;
66
+ this._confirmedAt = data.jsonApi.attributes.confirmedAt;
67
+ this._invitedAt = data.jsonApi.attributes.invitedAt;
68
+ this._registeredAt = data.jsonApi.attributes.registeredAt;
69
+
70
+ return this;
71
+ }
72
+
73
+ createJsonApi(data: WaitlistInput) {
74
+ const response: any = {
75
+ data: {
76
+ type: Modules.Waitlist.name,
77
+ id: data.id,
78
+ attributes: {
79
+ email: data.email,
80
+ gdprConsent: data.gdprConsent,
81
+ gdprConsentAt: data.gdprConsentAt,
82
+ },
83
+ meta: {},
84
+ relationships: {},
85
+ },
86
+ included: [],
87
+ };
88
+
89
+ if (data.marketingConsent !== undefined) {
90
+ response.data.attributes.marketingConsent = data.marketingConsent;
91
+ }
92
+
93
+ if (data.marketingConsentAt !== undefined) {
94
+ response.data.attributes.marketingConsentAt = data.marketingConsentAt;
95
+ }
96
+
97
+ // JSON.stringify questionnaire for backend storage
98
+ if (data.questionnaire !== undefined) {
99
+ response.data.attributes.questionnaire = JSON.stringify(data.questionnaire);
100
+ }
101
+
102
+ return response;
103
+ }
104
+ }
@@ -0,0 +1,32 @@
1
+ import { ApiDataInterface } from "../../../core";
2
+
3
+ export type WaitlistStatus = "pending" | "confirmed" | "invited" | "registered";
4
+
5
+ export type WaitlistInput = {
6
+ id: string;
7
+ email: string;
8
+ gdprConsent: boolean;
9
+ gdprConsentAt: string; // ISO timestamp when consent was given
10
+ marketingConsent?: boolean;
11
+ marketingConsentAt?: string; // ISO timestamp when marketing consent was given
12
+ questionnaire?: Record<string, any>; // Object for form handling, will be JSON.stringify'd by service
13
+ };
14
+
15
+ export interface WaitlistInterface extends ApiDataInterface {
16
+ get email(): string;
17
+ get gdprConsent(): boolean;
18
+ get gdprConsentAt(): string;
19
+ get marketingConsent(): boolean | undefined;
20
+ get marketingConsentAt(): string | undefined;
21
+ get questionnaire(): string | undefined; // JSON string from backend
22
+ get status(): WaitlistStatus;
23
+ get confirmedAt(): string | undefined;
24
+ get invitedAt(): string | undefined;
25
+ get registeredAt(): string | undefined;
26
+ // createdAt and updatedAt inherited from ApiDataInterface
27
+ }
28
+
29
+ export interface InviteValidation {
30
+ email: string;
31
+ valid: boolean;
32
+ }
@@ -0,0 +1,153 @@
1
+ import { AbstractService, EndpointCreator, HttpMethod, Modules, NextRef, PreviousRef } from "../../../core";
2
+ import { InviteValidation, WaitlistInput, WaitlistInterface } from "./WaitlistInterface";
3
+ import { WaitlistStatsInterface } from "./waitlist-stats.interface";
4
+
5
+ export class WaitlistService extends AbstractService {
6
+ /**
7
+ * Submit to waitlist (public)
8
+ * Uses Waitlist.createJsonApi() to transform WaitlistInput to JSON:API format
9
+ */
10
+ static async submit(params: WaitlistInput): Promise<WaitlistInterface> {
11
+ return this.callApi<WaitlistInterface>({
12
+ type: Modules.Waitlist,
13
+ method: HttpMethod.POST,
14
+ endpoint: new EndpointCreator({ endpoint: Modules.Waitlist }).generate(),
15
+ input: params,
16
+ });
17
+ }
18
+
19
+ /**
20
+ * Confirm email (public)
21
+ */
22
+ static async confirm(code: string): Promise<WaitlistInterface> {
23
+ const endpoint = new EndpointCreator({
24
+ endpoint: Modules.Waitlist,
25
+ childEndpoint: "confirm",
26
+ childId: code,
27
+ });
28
+
29
+ return this.callApi<WaitlistInterface>({
30
+ type: Modules.Waitlist,
31
+ method: HttpMethod.GET,
32
+ endpoint: endpoint.generate(),
33
+ });
34
+ }
35
+
36
+ /**
37
+ * List all waitlist entries (admin)
38
+ * Uses cursor-based pagination with NextRef/PreviousRef
39
+ */
40
+ static async findMany(params?: {
41
+ status?: string;
42
+ search?: string;
43
+ fetchAll?: boolean;
44
+ next?: NextRef;
45
+ prev?: PreviousRef;
46
+ }): Promise<WaitlistInterface[]> {
47
+ const endpoint = new EndpointCreator({ endpoint: Modules.Waitlist });
48
+
49
+ if (params?.status) endpoint.addAdditionalParam("status", params.status);
50
+ if (params?.search) endpoint.addAdditionalParam("search", params.search);
51
+ if (params?.fetchAll) endpoint.addAdditionalParam("fetchAll", "true");
52
+
53
+ return this.callApi<WaitlistInterface[]>({
54
+ type: Modules.Waitlist,
55
+ method: HttpMethod.GET,
56
+ endpoint: endpoint.generate(),
57
+ next: params?.next,
58
+ previous: params?.prev,
59
+ });
60
+ }
61
+
62
+ /**
63
+ * Send invite (admin)
64
+ */
65
+ static async invite(id: string): Promise<WaitlistInterface> {
66
+ const endpoint = new EndpointCreator({
67
+ endpoint: Modules.Waitlist,
68
+ id,
69
+ childEndpoint: "invite",
70
+ });
71
+
72
+ return this.callApi<WaitlistInterface>({
73
+ type: Modules.Waitlist,
74
+ method: HttpMethod.POST,
75
+ endpoint: endpoint.generate(),
76
+ });
77
+ }
78
+
79
+ /**
80
+ * Batch invite (admin)
81
+ * Non-standard batch operation - uses custom JSON:API format
82
+ */
83
+ static async inviteBatch(ids: string[]): Promise<{ invited: number; failed: number }> {
84
+ const endpoint = new EndpointCreator({
85
+ endpoint: Modules.Waitlist,
86
+ childEndpoint: "invite-batch",
87
+ });
88
+
89
+ return this.callApi({
90
+ type: Modules.Waitlist,
91
+ method: HttpMethod.POST,
92
+ endpoint: endpoint.generate(),
93
+ input: {
94
+ data: {
95
+ type: "waitlist-batch-invites",
96
+ attributes: { ids },
97
+ },
98
+ },
99
+ overridesJsonApiCreation: true,
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Get statistics (admin)
105
+ */
106
+ static async getStats(): Promise<WaitlistStatsInterface> {
107
+ const endpoint = new EndpointCreator({
108
+ endpoint: Modules.WaitlistStats,
109
+ });
110
+
111
+ return this.callApi<WaitlistStatsInterface>({
112
+ type: Modules.WaitlistStats,
113
+ method: HttpMethod.GET,
114
+ endpoint: endpoint.generate(),
115
+ });
116
+ }
117
+
118
+ /**
119
+ * Validate invite code (public) - calls auth endpoint
120
+ */
121
+ static async validateInvite(code: string): Promise<InviteValidation | null> {
122
+ console.log("[WaitlistService.validateInvite] Starting validation for code:", code);
123
+ try {
124
+ const endpoint = new EndpointCreator({
125
+ endpoint: Modules.Waitlist,
126
+ childEndpoint: "invite",
127
+ childId: code,
128
+ });
129
+
130
+ console.log("[WaitlistService.validateInvite] Calling endpoint:", endpoint.generate());
131
+
132
+ const response = await this.callApiWithMeta<any>({
133
+ type: Modules.Auth,
134
+ method: HttpMethod.GET,
135
+ endpoint: endpoint.generate(),
136
+ });
137
+
138
+ console.log("[WaitlistService.validateInvite] Response:", JSON.stringify(response));
139
+
140
+ // Response structure: data._jsonApi.attributes contains the actual values
141
+ const attributes = response.data?._jsonApi?.attributes;
142
+ console.log("[WaitlistService.validateInvite] Parsed attributes:", JSON.stringify(attributes));
143
+
144
+ return {
145
+ email: attributes?.email,
146
+ valid: attributes?.valid ?? false,
147
+ };
148
+ } catch (error) {
149
+ console.error("[WaitlistService.validateInvite] Error:", error);
150
+ return null;
151
+ }
152
+ }
153
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./WaitlistInterface";
2
+ export * from "./Waitlist";
3
+ export * from "./WaitlistService";
4
+ export * from "./waitlist-stats.interface";
5
+ export * from "./waitlist-stats";
@@ -0,0 +1,9 @@
1
+ import { ApiDataInterface } from "../../../core";
2
+
3
+ export interface WaitlistStatsInterface extends ApiDataInterface {
4
+ get pending(): number;
5
+ get confirmed(): number;
6
+ get invited(): number;
7
+ get registered(): number;
8
+ get total(): number;
9
+ }
@@ -0,0 +1,47 @@
1
+ import { AbstractApiData, JsonApiHydratedDataInterface } from "../../../core";
2
+ import { WaitlistStatsInterface } from "./waitlist-stats.interface";
3
+
4
+ export class WaitlistStats extends AbstractApiData implements WaitlistStatsInterface {
5
+ private _pending: number = 0;
6
+ private _confirmed: number = 0;
7
+ private _invited: number = 0;
8
+ private _registered: number = 0;
9
+ private _total: number = 0;
10
+
11
+ get pending(): number {
12
+ return this._pending;
13
+ }
14
+
15
+ get confirmed(): number {
16
+ return this._confirmed;
17
+ }
18
+
19
+ get invited(): number {
20
+ return this._invited;
21
+ }
22
+
23
+ get registered(): number {
24
+ return this._registered;
25
+ }
26
+
27
+ get total(): number {
28
+ return this._total;
29
+ }
30
+
31
+ rehydrate(data: JsonApiHydratedDataInterface): this {
32
+ super.rehydrate(data);
33
+
34
+ const attrs = data.jsonApi.attributes;
35
+ this._pending = attrs.pending ?? 0;
36
+ this._confirmed = attrs.confirmed ?? 0;
37
+ this._invited = attrs.invited ?? 0;
38
+ this._registered = attrs.registered ?? 0;
39
+ this._total = attrs.total ?? 0;
40
+
41
+ return this;
42
+ }
43
+
44
+ createJsonApi(_data?: any): any {
45
+ throw new Error("WaitlistStats is read-only and cannot be created");
46
+ }
47
+ }
@@ -0,0 +1,121 @@
1
+ "use client";
2
+
3
+ import { ColumnDef } from "@tanstack/react-table";
4
+ import { Send } from "lucide-react";
5
+ import { useTranslations } from "next-intl";
6
+ import { Badge, Button } from "../../../shadcnui";
7
+ import { WaitlistInterface, WaitlistStatus } from "../data/WaitlistInterface";
8
+
9
+ interface UseWaitlistTableStructureProps {
10
+ onInvite: (entry: WaitlistInterface) => void;
11
+ }
12
+
13
+ /**
14
+ * Parse questionnaire JSON string safely
15
+ */
16
+ function parseQuestionnaire(questionnaire: string | undefined): Record<string, any> | null {
17
+ if (!questionnaire) return null;
18
+ try {
19
+ return JSON.parse(questionnaire);
20
+ } catch {
21
+ return null;
22
+ }
23
+ }
24
+
25
+ export function useWaitlistTableStructure({
26
+ onInvite,
27
+ }: UseWaitlistTableStructureProps): ColumnDef<WaitlistInterface>[] {
28
+ const t = useTranslations();
29
+
30
+ const getStatusBadge = (status: WaitlistStatus) => {
31
+ const variants: Record<WaitlistStatus, { variant: "default" | "secondary" | "outline" | "destructive" }> = {
32
+ pending: { variant: "secondary" },
33
+ confirmed: { variant: "default" },
34
+ invited: { variant: "outline" },
35
+ registered: { variant: "default" },
36
+ };
37
+
38
+ const config = variants[status];
39
+ return <Badge variant={config.variant}>{t(`waitlist.admin.status.${status}`)}</Badge>;
40
+ };
41
+
42
+ return [
43
+ {
44
+ accessorKey: "email",
45
+ header: t("waitlist.admin.columns.email"),
46
+ cell: ({ row }) => <span className="font-medium">{row.original.email}</span>,
47
+ },
48
+ {
49
+ accessorKey: "status",
50
+ header: t("waitlist.admin.columns.status"),
51
+ cell: ({ row }) => getStatusBadge(row.original.status),
52
+ },
53
+ {
54
+ accessorKey: "createdAt",
55
+ header: t("waitlist.admin.columns.submitted"),
56
+ cell: ({ row }) => {
57
+ if (!row.original.createdAt) return "-";
58
+ return new Date(row.original.createdAt).toLocaleDateString();
59
+ },
60
+ },
61
+ {
62
+ accessorKey: "questionnaire",
63
+ header: t("waitlist.admin.columns.questionnaire"),
64
+ cell: ({ row }) => {
65
+ // IMPORTANT: Parse JSON string from backend
66
+ const questionnaire = parseQuestionnaire(row.original.questionnaire);
67
+ if (!questionnaire || Object.keys(questionnaire).length === 0) {
68
+ return <span className="text-muted-foreground">-</span>;
69
+ }
70
+
71
+ return (
72
+ <details className="cursor-pointer">
73
+ <summary className="text-primary text-sm">{t("waitlist.admin.questionnaire.view_answers")}</summary>
74
+ <div className="bg-muted mt-2 rounded p-2 text-sm">
75
+ {Object.entries(questionnaire).map(([key, value]) => (
76
+ <div key={key} className="mb-1">
77
+ <span className="font-medium">{key}:</span>{" "}
78
+ <span className="text-muted-foreground">
79
+ {Array.isArray(value) ? value.join(", ") : String(value)}
80
+ </span>
81
+ </div>
82
+ ))}
83
+ </div>
84
+ </details>
85
+ );
86
+ },
87
+ },
88
+ {
89
+ id: "actions",
90
+ header: t("waitlist.admin.columns.actions"),
91
+ cell: ({ row }) => {
92
+ const entry = row.original;
93
+
94
+ if (entry.status === "confirmed") {
95
+ return (
96
+ <Button size="sm" variant="outline" onClick={() => onInvite(entry)}>
97
+ <Send className="mr-2 h-4 w-4" />
98
+ {t("waitlist.admin.actions.invite")}
99
+ </Button>
100
+ );
101
+ }
102
+
103
+ if (entry.status === "invited" && entry.invitedAt) {
104
+ return (
105
+ <span className="text-muted-foreground text-sm">
106
+ {t("waitlist.admin.actions.invited_on", { date: new Date(entry.invitedAt).toLocaleDateString() })}
107
+ </span>
108
+ );
109
+ }
110
+
111
+ if (entry.status === "registered") {
112
+ return <span className="text-muted-foreground text-sm">{t("waitlist.admin.actions.registered")}</span>;
113
+ }
114
+
115
+ return (
116
+ <span className="text-muted-foreground text-sm">{t("waitlist.admin.actions.awaiting_confirmation")}</span>
117
+ );
118
+ },
119
+ },
120
+ ];
121
+ }
@@ -0,0 +1,28 @@
1
+ // Configuration
2
+ export { configureWaitlist, getWaitlistConfig } from "./config/waitlist.config";
3
+ export type {
4
+ WaitlistConfig,
5
+ QuestionnaireField,
6
+ QuestionnaireFieldType,
7
+ QuestionnaireOption,
8
+ } from "./config/waitlist.config";
9
+
10
+ // Components - Forms
11
+ export { WaitlistForm } from "./components/forms/WaitlistForm";
12
+ export { WaitlistQuestionnaireRenderer } from "./components/forms/WaitlistQuestionnaireRenderer";
13
+
14
+ // Components - Sections
15
+ export { WaitlistHeroSection } from "./components/sections/WaitlistHeroSection";
16
+ export { WaitlistSuccessState } from "./components/sections/WaitlistSuccessState";
17
+ export { WaitlistConfirmation } from "./components/sections/WaitlistConfirmation";
18
+
19
+ // Components - Lists
20
+ export { WaitlistList } from "./components/lists/WaitlistList";
21
+
22
+ // Data
23
+ export { WaitlistService } from "./data/WaitlistService";
24
+ export type { WaitlistInterface, WaitlistStatus, WaitlistInput, InviteValidation } from "./data/WaitlistInterface";
25
+ export type { WaitlistStatsInterface } from "./data/waitlist-stats.interface";
26
+
27
+ // Hooks
28
+ export { useWaitlistTableStructure } from "./hooks/useWaitlistTableStructure";
@@ -0,0 +1,8 @@
1
+ import { ModuleFactory } from "../../permissions";
2
+ import { WaitlistStats } from "./data/waitlist-stats";
3
+
4
+ export const WaitlistStatsModule = (factory: ModuleFactory) =>
5
+ factory({
6
+ name: "waitlist-stats",
7
+ model: WaitlistStats,
8
+ });
@@ -0,0 +1,9 @@
1
+ import { ModuleFactory } from "../../permissions";
2
+ import { Waitlist } from "./data/Waitlist";
3
+
4
+ export const WaitlistModule = (factory: ModuleFactory) =>
5
+ factory({
6
+ name: "waitlists",
7
+ pageUrl: "/waitlists",
8
+ model: Waitlist,
9
+ });
package/src/index.ts CHANGED
@@ -27,5 +27,14 @@ export type { RoleIdConfig } from "./roles";
27
27
  export { configureAuth, getTokenHandler } from "./features/auth/config";
28
28
  export type { TokenHandler, TokenParams } from "./features/auth/config";
29
29
 
30
+ // Waitlist configuration
31
+ export { configureWaitlist, getWaitlistConfig } from "./features/waitlist/config/waitlist.config";
32
+ export type {
33
+ WaitlistConfig,
34
+ QuestionnaireField,
35
+ QuestionnaireFieldType,
36
+ QuestionnaireOption,
37
+ } from "./features/waitlist/config/waitlist.config";
38
+
30
39
  // Toast utilities
31
40
  export { showToast, showError, dismissToast, showCustomToast, type ToastOptions } from "./utils/toast";
@@ -2,12 +2,16 @@ let _useDiscordAuth: boolean = false;
2
2
  let _useGoogleAuth: boolean = false;
3
3
  let _useInternalAuth: boolean = true;
4
4
  let _allowRegistration: boolean = true;
5
+ let _registrationMode: "open" | "closed" | "waitlist" = "open";
6
+
7
+ export type RegistrationMode = "open" | "closed" | "waitlist";
5
8
 
6
9
  export interface LoginConfig {
7
10
  discordClientId?: string;
8
11
  googleClientId?: string;
9
12
  useInternalAuth?: boolean;
10
13
  allowRegistration?: boolean;
14
+ registrationMode?: RegistrationMode;
11
15
  }
12
16
 
13
17
  export function configureLogin(params: LoginConfig): void {
@@ -15,6 +19,7 @@ export function configureLogin(params: LoginConfig): void {
15
19
  _useGoogleAuth = !!params.googleClientId;
16
20
  _useInternalAuth = params.useInternalAuth ?? true;
17
21
  _allowRegistration = params.allowRegistration ?? true;
22
+ _registrationMode = params.registrationMode ?? "open";
18
23
  }
19
24
 
20
25
  export function isDiscordAuthEnabled(): boolean {
@@ -32,3 +37,7 @@ export function isInternalAuthEnabled(): boolean {
32
37
  export function isRegistrationAllowed(): boolean {
33
38
  return _allowRegistration;
34
39
  }
40
+
41
+ export function getRegistrationMode(): RegistrationMode {
42
+ return _registrationMode;
43
+ }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/chunk-2PHWAL6Q.js","../src/client/config.ts","../src/i18n/config.ts","../src/login/config.ts","../src/roles/config.ts"],"names":[],"mappings":"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","file":"/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/chunk-2PHWAL6Q.js","sourcesContent":[null,"\"use client\";\n\nimport { ModuleWithPermissions } from \"../permissions/types\";\nimport { setBootstrapper } from \"../core/registry/bootstrapStore\";\n\n// Config storage for client-side contexts\nlet _clientConfig: {\n apiUrl: string;\n appUrl?: string;\n trackablePages?: ModuleWithPermissions[];\n bootstrapper?: () => void;\n additionalHeaders?: Record<string, string>;\n stripePublishableKey?: string;\n} | null = null;\n\n/**\n * Configure the JSON:API client. This is the main configuration function.\n * This is typically called during app initialization.\n */\nexport function configureJsonApi(config: {\n apiUrl: string;\n appUrl?: string;\n trackablePages?: ModuleWithPermissions[];\n bootstrapper?: () => void;\n additionalHeaders?: Record<string, string>;\n stripePublishableKey?: string;\n}): void {\n _clientConfig = config;\n // Register and call bootstrapper to register all modules\n if (config.bootstrapper) {\n setBootstrapper(config.bootstrapper);\n config.bootstrapper();\n }\n}\n\n/**\n * Configure the client config. This is typically called during app initialization.\n * @deprecated Use configureJsonApi instead\n */\nexport function configureClientConfig(config: {\n apiUrl: string;\n appUrl?: string;\n trackablePages?: ModuleWithPermissions[];\n}): void {\n _clientConfig = config;\n}\n\n/**\n * Get the configured API URL.\n */\nexport function getApiUrl(): string {\n if (_clientConfig?.apiUrl) {\n return _clientConfig.apiUrl;\n }\n if (typeof process !== \"undefined\" && process.env?.NEXT_PUBLIC_API_URL) {\n return process.env.NEXT_PUBLIC_API_URL;\n }\n return \"\";\n}\n\n/**\n * Get the configured app URL.\n */\nexport function getAppUrl(): string {\n if (_clientConfig?.appUrl) {\n return _clientConfig.appUrl;\n }\n if (typeof process !== \"undefined\" && process.env?.NEXT_PUBLIC_APP_URL) {\n return process.env.NEXT_PUBLIC_APP_URL;\n }\n if (typeof window !== \"undefined\") {\n return window.location.origin;\n }\n return \"\";\n}\n\n/**\n * Get the configured trackable pages.\n */\nexport function getTrackablePages(): ModuleWithPermissions[] {\n return _clientConfig?.trackablePages ?? [];\n}\n\n/**\n * Get the configured Stripe publishable key.\n */\nexport function getStripePublishableKey(): string | undefined {\n return _clientConfig?.stripePublishableKey;\n}\n","import { ComponentType } from \"react\";\n\n// Types for injected hooks\nexport interface I18nRouter {\n push: (href: string) => void;\n replace: (href: string) => void;\n back: () => void;\n forward: () => void;\n refresh: () => void;\n prefetch: (href: string) => void;\n}\n\nexport type UseRouterHook = () => I18nRouter;\nexport type UseTranslationsHook = (namespace?: string) => (key: string, values?: Record<string, any>) => string;\nexport type UseLocaleHook = () => string;\n\nexport type UseDateFnsLocaleHook = () => any; // date-fns Locale type\nexport type LinkComponent = ComponentType<{ href: string; children: React.ReactNode; [key: string]: any }>;\n\nexport interface I18nConfig {\n useRouter: UseRouterHook;\n useTranslations: UseTranslationsHook;\n useLocale?: UseLocaleHook;\n useDateFnsLocale?: UseDateFnsLocaleHook;\n Link: LinkComponent;\n usePathname: () => string;\n}\n\n// Private storage\nlet _config: I18nConfig | null = null;\n\n// Configuration function (called by app at startup)\nexport function configureI18n(config: I18nConfig): void {\n _config = config;\n}\n\n// Hooks for library components to use\nexport function useI18nRouter(): I18nRouter {\n if (!_config?.useRouter) {\n throw new Error(\"i18n not configured. Call configureI18n() at app startup.\");\n }\n return _config.useRouter();\n}\n\nexport function useI18nTranslations(namespace?: string): (key: string, values?: Record<string, any>) => string {\n if (!_config?.useTranslations) {\n // Fallback: return key as-is (safe for server/client)\n return (key: string) => key;\n }\n return _config.useTranslations(namespace);\n}\n\nexport function getI18nLink(): LinkComponent {\n if (!_config?.Link) {\n throw new Error(\"i18n not configured. Call configureI18n() at app startup.\");\n }\n return _config.Link;\n}\n\nexport function useI18nLocale(): string {\n if (_config?.useLocale) {\n return _config.useLocale();\n }\n // Fallback to English (safe for server/client)\n return \"en\";\n}\n\nexport function useI18nDateFnsLocale(): any {\n if (_config?.useDateFnsLocale) {\n return _config.useDateFnsLocale();\n }\n // Fallback to undefined (Calendar will use default)\n return undefined;\n}\n","let _useDiscordAuth: boolean = false;\nlet _useGoogleAuth: boolean = false;\nlet _useInternalAuth: boolean = true;\nlet _allowRegistration: boolean = true;\n\nexport interface LoginConfig {\n discordClientId?: string;\n googleClientId?: string;\n useInternalAuth?: boolean;\n allowRegistration?: boolean;\n}\n\nexport function configureLogin(params: LoginConfig): void {\n _useDiscordAuth = !!params.discordClientId;\n _useGoogleAuth = !!params.googleClientId;\n _useInternalAuth = params.useInternalAuth ?? true;\n _allowRegistration = params.allowRegistration ?? true;\n}\n\nexport function isDiscordAuthEnabled(): boolean {\n return _useDiscordAuth;\n}\n\nexport function isGoogleAuthEnabled(): boolean {\n return _useGoogleAuth;\n}\n\nexport function isInternalAuthEnabled(): boolean {\n return _useInternalAuth;\n}\n\nexport function isRegistrationAllowed(): boolean {\n return _allowRegistration;\n}\n","/**\n * Role ID configuration interface\n * Apps provide their role IDs via configureRoles()\n */\nexport interface RoleIdConfig {\n Administrator: string;\n CompanyAdministrator: string;\n [key: string]: string; // Allow additional roles\n}\n\n// Private storage for the injected role IDs\nlet _roleId: RoleIdConfig | null = null;\n\n/**\n * Configure role IDs for the library\n * Call this at app startup to provide role ID constants\n *\n * @example\n * ```typescript\n * import { configureRoles } from \"@carlonicora/nextjs-jsonapi\";\n * import { RoleId } from \"@phlow/shared\";\n *\n * configureRoles(RoleId);\n * ```\n */\nexport function configureRoles(roleId: RoleIdConfig): void {\n _roleId = roleId;\n}\n\n/**\n * Get configured role IDs\n * @throws Error if roles not configured\n */\nexport function getRoleId(): RoleIdConfig {\n if (!_roleId) {\n throw new Error(\"Roles not configured. Call configureRoles() at app startup.\");\n }\n return _roleId;\n}\n\n/**\n * Check if roles have been configured\n */\nexport function isRolesConfigured(): boolean {\n return _roleId !== null;\n}\n"]}