vintasend 0.5.1 → 0.6.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 (75) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.js +2 -1
  3. package/dist/services/notification-backends/base-notification-backend.d.ts +87 -1
  4. package/dist/services/notification-backends/base-notification-backend.js +7 -0
  5. package/dist/services/notification-service.d.ts +2 -0
  6. package/dist/services/notification-service.js +14 -4
  7. package/dist/tools/vintasend-dashboard/app/components/auth-status.d.ts +1 -0
  8. package/dist/tools/vintasend-dashboard/app/components/auth-status.js +29 -0
  9. package/dist/tools/vintasend-dashboard/app/error.d.ts +6 -0
  10. package/dist/tools/vintasend-dashboard/app/error.js +40 -0
  11. package/dist/tools/vintasend-dashboard/app/layout.d.ts +6 -0
  12. package/dist/tools/vintasend-dashboard/app/layout.js +38 -0
  13. package/dist/tools/vintasend-dashboard/app/notifications/actions.d.ts +51 -0
  14. package/dist/tools/vintasend-dashboard/app/notifications/actions.js +224 -0
  15. package/dist/tools/vintasend-dashboard/app/notifications/components/columns.d.ts +6 -0
  16. package/dist/tools/vintasend-dashboard/app/notifications/components/columns.js +158 -0
  17. package/dist/tools/vintasend-dashboard/app/notifications/components/notifications-filters.d.ts +18 -0
  18. package/dist/tools/vintasend-dashboard/app/notifications/components/notifications-filters.js +162 -0
  19. package/dist/tools/vintasend-dashboard/app/notifications/components/notifications-table.d.ts +23 -0
  20. package/dist/tools/vintasend-dashboard/app/notifications/components/notifications-table.js +132 -0
  21. package/dist/tools/vintasend-dashboard/app/page.d.ts +1 -0
  22. package/dist/tools/vintasend-dashboard/app/page.js +32 -0
  23. package/dist/tools/vintasend-dashboard/app/sign-in/[[...sign-in]]/page.d.ts +1 -0
  24. package/dist/tools/vintasend-dashboard/app/sign-in/[[...sign-in]]/page.js +18 -0
  25. package/dist/tools/vintasend-dashboard/app/sign-out/[[...sign-out]]/page.d.ts +1 -0
  26. package/dist/tools/vintasend-dashboard/app/sign-out/[[...sign-out]]/page.js +16 -0
  27. package/dist/tools/vintasend-dashboard/components/ui/badge.d.ts +9 -0
  28. package/dist/tools/vintasend-dashboard/components/ui/badge.js +61 -0
  29. package/dist/tools/vintasend-dashboard/components/ui/button.d.ts +10 -0
  30. package/dist/tools/vintasend-dashboard/components/ui/button.js +72 -0
  31. package/dist/tools/vintasend-dashboard/components/ui/calendar.d.ts +8 -0
  32. package/dist/tools/vintasend-dashboard/components/ui/calendar.js +116 -0
  33. package/dist/tools/vintasend-dashboard/components/ui/dropdown-menu.d.ts +25 -0
  34. package/dist/tools/vintasend-dashboard/components/ui/dropdown-menu.js +119 -0
  35. package/dist/tools/vintasend-dashboard/components/ui/input.d.ts +3 -0
  36. package/dist/tools/vintasend-dashboard/components/ui/input.js +41 -0
  37. package/dist/tools/vintasend-dashboard/components/ui/pagination.d.ts +13 -0
  38. package/dist/tools/vintasend-dashboard/components/ui/pagination.js +79 -0
  39. package/dist/tools/vintasend-dashboard/components/ui/popover.d.ts +10 -0
  40. package/dist/tools/vintasend-dashboard/components/ui/popover.js +69 -0
  41. package/dist/tools/vintasend-dashboard/components/ui/select.d.ts +15 -0
  42. package/dist/tools/vintasend-dashboard/components/ui/select.js +106 -0
  43. package/dist/tools/vintasend-dashboard/components/ui/skeleton.d.ts +2 -0
  44. package/dist/tools/vintasend-dashboard/components/ui/skeleton.js +7 -0
  45. package/dist/tools/vintasend-dashboard/components/ui/table.d.ts +10 -0
  46. package/dist/tools/vintasend-dashboard/components/ui/table.js +72 -0
  47. package/dist/tools/vintasend-dashboard/lib/auth/auth-context.d.ts +23 -0
  48. package/dist/tools/vintasend-dashboard/lib/auth/auth-context.js +30 -0
  49. package/dist/tools/vintasend-dashboard/lib/auth/index.d.ts +5 -0
  50. package/dist/tools/vintasend-dashboard/lib/auth/index.js +12 -0
  51. package/dist/tools/vintasend-dashboard/lib/auth/resolve-strategy.d.ts +5 -0
  52. package/dist/tools/vintasend-dashboard/lib/auth/resolve-strategy.js +24 -0
  53. package/dist/tools/vintasend-dashboard/lib/auth/strategies/auth0-strategy.d.ts +17 -0
  54. package/dist/tools/vintasend-dashboard/lib/auth/strategies/auth0-strategy.js +76 -0
  55. package/dist/tools/vintasend-dashboard/lib/auth/strategies/clerk-strategy.d.ts +18 -0
  56. package/dist/tools/vintasend-dashboard/lib/auth/strategies/clerk-strategy.js +70 -0
  57. package/dist/tools/vintasend-dashboard/lib/auth/types.d.ts +50 -0
  58. package/dist/tools/vintasend-dashboard/lib/auth/types.js +2 -0
  59. package/dist/tools/vintasend-dashboard/lib/auth/validate-config.d.ts +2 -0
  60. package/dist/tools/vintasend-dashboard/lib/auth/validate-config.js +10 -0
  61. package/dist/tools/vintasend-dashboard/lib/auth0.d.ts +6 -0
  62. package/dist/tools/vintasend-dashboard/lib/auth0.js +14 -0
  63. package/dist/tools/vintasend-dashboard/lib/notifications/get-vintasend-service.d.ts +26 -0
  64. package/dist/tools/vintasend-dashboard/lib/notifications/get-vintasend-service.js +31 -0
  65. package/dist/tools/vintasend-dashboard/lib/notifications/serialize.d.ts +27 -0
  66. package/dist/tools/vintasend-dashboard/lib/notifications/serialize.js +75 -0
  67. package/dist/tools/vintasend-dashboard/lib/notifications/types.d.ts +62 -0
  68. package/dist/tools/vintasend-dashboard/lib/notifications/types.js +6 -0
  69. package/dist/tools/vintasend-dashboard/lib/utils.d.ts +2 -0
  70. package/dist/tools/vintasend-dashboard/lib/utils.js +8 -0
  71. package/dist/tools/vintasend-dashboard/next.config.d.ts +3 -0
  72. package/dist/tools/vintasend-dashboard/next.config.js +6 -0
  73. package/dist/tools/vintasend-dashboard/proxy.d.ts +5 -0
  74. package/dist/tools/vintasend-dashboard/proxy.js +83 -0
  75. package/package.json +2 -1
package/dist/index.d.ts CHANGED
@@ -3,7 +3,8 @@ export type { LocalFileAttachmentManagerConfig } from './services/attachment-man
3
3
  export { LocalFileAttachmentManager } from './services/attachment-manager/local-file-attachment-manager';
4
4
  export { BaseNotificationAdapter, isOneOffNotification, } from './services/notification-adapters/base-notification-adapter';
5
5
  export type { BaseNotificationBackend } from './services/notification-backends/base-notification-backend';
6
- export { supportsAttachments } from './services/notification-backends/base-notification-backend';
6
+ export { supportsAttachments, isFieldFilter } from './services/notification-backends/base-notification-backend';
7
+ export type { NotificationFilter, NotificationFilterFields, DateRange, NotificationFilterCapabilities } from './services/notification-backends/base-notification-backend';
7
8
  export type { BaseNotificationQueueService } from './services/notification-queue-service/base-notification-queue-service';
8
9
  export type { VintaSend } from './services/notification-service';
9
10
  export { VintaSendFactory } from './services/notification-service';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isAttachmentReference = exports.VintaSendFactory = exports.supportsAttachments = exports.isOneOffNotification = exports.BaseNotificationAdapter = exports.LocalFileAttachmentManager = exports.BaseAttachmentManager = void 0;
3
+ exports.isAttachmentReference = exports.VintaSendFactory = exports.isFieldFilter = exports.supportsAttachments = exports.isOneOffNotification = exports.BaseNotificationAdapter = exports.LocalFileAttachmentManager = exports.BaseAttachmentManager = void 0;
4
4
  // Attachment Manager
5
5
  var base_attachment_manager_1 = require("./services/attachment-manager/base-attachment-manager");
6
6
  Object.defineProperty(exports, "BaseAttachmentManager", { enumerable: true, get: function () { return base_attachment_manager_1.BaseAttachmentManager; } });
@@ -11,6 +11,7 @@ Object.defineProperty(exports, "BaseNotificationAdapter", { enumerable: true, ge
11
11
  Object.defineProperty(exports, "isOneOffNotification", { enumerable: true, get: function () { return base_notification_adapter_1.isOneOffNotification; } });
12
12
  var base_notification_backend_1 = require("./services/notification-backends/base-notification-backend");
13
13
  Object.defineProperty(exports, "supportsAttachments", { enumerable: true, get: function () { return base_notification_backend_1.supportsAttachments; } });
14
+ Object.defineProperty(exports, "isFieldFilter", { enumerable: true, get: function () { return base_notification_backend_1.isFieldFilter; } });
14
15
  var notification_service_1 = require("./services/notification-service");
15
16
  Object.defineProperty(exports, "VintaSendFactory", { enumerable: true, get: function () { return notification_service_1.VintaSendFactory; } });
16
17
  var attachment_1 = require("./types/attachment");
@@ -1,8 +1,62 @@
1
1
  import type { AttachmentFileRecord, StoredAttachment } from '../../types/attachment';
2
2
  import type { InputJsonValue } from '../../types/json-values';
3
+ import type { NotificationStatus } from '../../types/notification-status';
4
+ import type { NotificationType } from '../../types/notification-type';
3
5
  import type { AnyDatabaseNotification, AnyNotification, DatabaseNotification, DatabaseOneOffNotification, Notification, OneOffNotificationInput } from '../../types/notification';
4
6
  import type { BaseNotificationTypeConfig } from '../../types/notification-type-config';
5
7
  import type { BaseLogger } from '../loggers/base-logger';
8
+ /**
9
+ * Date range filter with optional lower and upper bounds.
10
+ */
11
+ export type DateRange = {
12
+ from?: Date;
13
+ to?: Date;
14
+ };
15
+ /**
16
+ * Flat dotted key capability map describing which filter features a backend supports.
17
+ * Use flat dotted keys for logical operators, fields, and negations:
18
+ * - `logical.*`: Operator support (and, or, not, notNested)
19
+ * - `fields.*`: Field filtering support
20
+ * - `negation.*`: Negation support for specific fields
21
+ *
22
+ * When a backend implements getFilterCapabilities(), missing keys default to true (supported),
23
+ * ensuring forward compatibility when new capabilities are added.
24
+ * Backends that don't implement getFilterCapabilities() are treated as supporting all features.
25
+ */
26
+ export type NotificationFilterCapabilities = {
27
+ [key: string]: boolean;
28
+ };
29
+ /**
30
+ * Leaf-level filter conditions for notification fields.
31
+ * All specified fields are combined with implicit AND.
32
+ */
33
+ export type NotificationFilterFields<Config extends BaseNotificationTypeConfig> = {
34
+ status?: NotificationStatus | NotificationStatus[];
35
+ notificationType?: NotificationType | NotificationType[];
36
+ adapterUsed?: string | string[];
37
+ userId?: Config['UserIdType'];
38
+ bodyTemplate?: string;
39
+ subjectTemplate?: string;
40
+ contextName?: string;
41
+ sendAfterRange?: DateRange;
42
+ createdAtRange?: DateRange;
43
+ sentAtRange?: DateRange;
44
+ };
45
+ /**
46
+ * Composable notification filter supporting logical operators.
47
+ *
48
+ * - A plain field filter applies all conditions with implicit AND.
49
+ * - `{ and: [...] }` requires all sub-filters to match.
50
+ * - `{ or: [...] }` requires at least one sub-filter to match.
51
+ * - `{ not: filter }` inverts the sub-filter.
52
+ */
53
+ export type NotificationFilter<Config extends BaseNotificationTypeConfig> = NotificationFilterFields<Config> | {
54
+ and: NotificationFilter<Config>[];
55
+ } | {
56
+ or: NotificationFilter<Config>[];
57
+ } | {
58
+ not: NotificationFilter<Config>;
59
+ };
6
60
  export interface BaseNotificationBackend<Config extends BaseNotificationTypeConfig> {
7
61
  getAllPendingNotifications(): Promise<AnyDatabaseNotification<Config>[]>;
8
62
  getPendingNotifications(page: number, pageSize: number): Promise<AnyDatabaseNotification<Config>[]>;
@@ -23,12 +77,40 @@ export interface BaseNotificationBackend<Config extends BaseNotificationTypeConf
23
77
  filterAllInAppUnreadNotifications(userId: Config['UserIdType']): Promise<DatabaseNotification<Config>[]>;
24
78
  filterInAppUnreadNotifications(userId: Config['UserIdType'], page: number, pageSize: number): Promise<DatabaseNotification<Config>[]>;
25
79
  getUserEmailFromNotification(notificationId: Config['NotificationIdType']): Promise<string | undefined>;
26
- storeContextUsed(notificationId: Config['NotificationIdType'], context: InputJsonValue): Promise<void>;
80
+ storeAdapterAndContextUsed(notificationId: Config['NotificationIdType'], adapterKey: string, context: InputJsonValue): Promise<void>;
27
81
  persistOneOffNotification(notification: Omit<OneOffNotificationInput<Config>, 'id'>): Promise<DatabaseOneOffNotification<Config>>;
28
82
  persistOneOffNotificationUpdate(notificationId: Config['NotificationIdType'], notification: Partial<Omit<OneOffNotificationInput<Config>, 'id'>>): Promise<DatabaseOneOffNotification<Config>>;
29
83
  getOneOffNotification(notificationId: Config['NotificationIdType'], forUpdate: boolean): Promise<DatabaseOneOffNotification<Config> | null>;
30
84
  getAllOneOffNotifications(): Promise<DatabaseOneOffNotification<Config>[]>;
31
85
  getOneOffNotifications(page: number, pageSize: number): Promise<DatabaseOneOffNotification<Config>[]>;
86
+ /**
87
+ * Filter notifications using composable query filters.
88
+ * Supports filtering by status, notification type, adapter, recipient,
89
+ * body/subject templates, context, and date ranges (sendAfter, created, sent).
90
+ * Filters can be combined with logical operators (and, or, not).
91
+ *
92
+ * @param filter - Composable filter expression
93
+ * @param page - Page number (1-indexed) for pagination
94
+ * @param pageSize - Number of results per page
95
+ * @returns Matching notifications
96
+ */
97
+ filterNotifications(filter: NotificationFilter<Config>, page: number, pageSize: number): Promise<AnyDatabaseNotification<Config>[]>;
98
+ /**
99
+ * Get the filter capabilities supported by this backend.
100
+ * Returns an object with flat dotted keys indicating which filtering features are supported.
101
+ *
102
+ * Example capability names:
103
+ * - `logical.and`, `logical.or`, `logical.not`, `logical.notNested`
104
+ * - `fields.status`, `fields.notificationType`, `fields.adapterUsed`, `fields.userId`,
105
+ * `fields.bodyTemplate`, `fields.subjectTemplate`, `fields.contextName`,
106
+ * `fields.sendAfterRange`, `fields.createdAtRange`, `fields.sentAtRange`
107
+ * - `negation.sendAfterRange`, `negation.createdAtRange`, `negation.sentAtRange`
108
+ *
109
+ * If this method is not implemented, all features are assumed to be supported.
110
+ * If this method is implemented, missing keys default to true (supported) for forward compatibility.
111
+ * Only explicitly set keys to false to indicate unsupported features.
112
+ */
113
+ getFilterCapabilities?(): NotificationFilterCapabilities;
32
114
  /**
33
115
  * Inject logger into backend for debugging and monitoring
34
116
  */
@@ -73,6 +155,10 @@ export interface BaseNotificationBackend<Config extends BaseNotificationTypeConf
73
155
  */
74
156
  deleteNotificationAttachment?(notificationId: Config['NotificationIdType'], attachmentId: string): Promise<void>;
75
157
  }
158
+ /**
159
+ * Type guard to check if a filter is a field filter (leaf node).
160
+ */
161
+ export declare function isFieldFilter<Config extends BaseNotificationTypeConfig>(filter: NotificationFilter<Config>): filter is NotificationFilterFields<Config>;
76
162
  /**
77
163
  * Type guard to check if backend supports attachment operations
78
164
  */
@@ -1,6 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isFieldFilter = isFieldFilter;
3
4
  exports.supportsAttachments = supportsAttachments;
5
+ /**
6
+ * Type guard to check if a filter is a field filter (leaf node).
7
+ */
8
+ function isFieldFilter(filter) {
9
+ return !('and' in filter) && !('or' in filter) && !('not' in filter);
10
+ }
4
11
  /**
5
12
  * Type guard to check if backend supports attachment operations
6
13
  */
@@ -83,6 +83,8 @@ export declare class VintaSend<Config extends BaseNotificationTypeConfig, Adapte
83
83
  getNotificationContext<ContextName extends string & keyof Config['ContextMap']>(contextName: ContextName, parameters: Parameters<ReturnType<typeof this.contextGeneratorsMap.getContextGenerator<ContextName>>['generate']>[0]): Promise<JsonObject>;
84
84
  sendPendingNotifications(): Promise<void>;
85
85
  getPendingNotifications(page: number, pageSize: number): Promise<AnyDatabaseNotification<Config>[]>;
86
+ getNotifications(page: number, pageSize: number): Promise<AnyDatabaseNotification<Config>[]>;
87
+ getOneOffNotifications(page: number, pageSize: number): Promise<DatabaseOneOffNotification<Config>[]>;
86
88
  getNotification(notificationId: Config['NotificationIdType'], forUpdate?: boolean): Promise<AnyDatabaseNotification<Config> | null>;
87
89
  /**
88
90
  * Gets a one-off notification by ID.
@@ -77,6 +77,7 @@ class VintaSend {
77
77
  this.queueService = queueService;
78
78
  }
79
79
  async send(notification) {
80
+ var _a;
80
81
  const adaptersOfType = this.adapters.filter((adapter) => adapter.notificationType === notification.notificationType);
81
82
  if (adaptersOfType.length === 0) {
82
83
  this.logger.error(`No adapter found for notification type ${notification.notificationType}`);
@@ -144,10 +145,10 @@ class VintaSend {
144
145
  this.logger.error(`Error marking notification ${notification.id} as sent: ${markSentError}`);
145
146
  }
146
147
  try {
147
- await this.backend.storeContextUsed(notification.id, context !== null && context !== void 0 ? context : {});
148
+ await this.backend.storeAdapterAndContextUsed(notification.id, (_a = adapter.key) !== null && _a !== void 0 ? _a : 'unknown', context !== null && context !== void 0 ? context : {});
148
149
  }
149
150
  catch (storeContextError) {
150
- this.logger.error(`Error storing context for notification ${notification.id}: ${storeContextError}`);
151
+ this.logger.error(`Error storing adapter and context for notification ${notification.id}: ${storeContextError}`);
151
152
  }
152
153
  }
153
154
  }
@@ -254,6 +255,12 @@ class VintaSend {
254
255
  async getPendingNotifications(page, pageSize) {
255
256
  return this.backend.getPendingNotifications(page, pageSize);
256
257
  }
258
+ async getNotifications(page, pageSize) {
259
+ return this.backend.getNotifications(page, pageSize);
260
+ }
261
+ async getOneOffNotifications(page, pageSize) {
262
+ return this.backend.getOneOffNotifications(page, pageSize);
263
+ }
257
264
  async getNotification(notificationId, forUpdate = false) {
258
265
  return this.backend.getNotification(notificationId, forUpdate);
259
266
  }
@@ -341,6 +348,7 @@ class VintaSend {
341
348
  return createdNotification;
342
349
  }
343
350
  async delayedSend(notificationId) {
351
+ var _a;
344
352
  const notification = await this.getNotification(notificationId, false);
345
353
  if (!notification) {
346
354
  this.logger.error(`Notification ${notificationId} not found`);
@@ -358,7 +366,9 @@ class VintaSend {
358
366
  return;
359
367
  }
360
368
  const context = await this.getNotificationContext(notification.contextName, notification.contextParameters);
369
+ let lastAdapterKey = 'unknown';
361
370
  for (const adapter of enqueueNotificationsAdapters) {
371
+ lastAdapterKey = (_a = adapter.key) !== null && _a !== void 0 ? _a : 'unknown';
362
372
  try {
363
373
  await adapter.send(notification, context);
364
374
  }
@@ -379,10 +389,10 @@ class VintaSend {
379
389
  }
380
390
  }
381
391
  try {
382
- await this.backend.storeContextUsed(notification.id, context);
392
+ await this.backend.storeAdapterAndContextUsed(notification.id, lastAdapterKey, context);
383
393
  }
384
394
  catch (storeContextError) {
385
- this.logger.error(`Error storing context for notification ${notification.id}: ${storeContextError}`);
395
+ this.logger.error(`Error storing adapter and context for notification ${notification.id}: ${storeContextError}`);
386
396
  }
387
397
  }
388
398
  async bulkPersistNotifications(notifications) {
@@ -0,0 +1 @@
1
+ export declare function AuthStatus(): import("react").JSX.Element;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ "use client";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.AuthStatus = AuthStatus;
8
+ const image_1 = __importDefault(require("next/image"));
9
+ const auth_context_1 = require("@/lib/auth/auth-context");
10
+ function AuthStatus() {
11
+ const { user, isAuthenticated, signOutUrl } = (0, auth_context_1.useAuth)();
12
+ if (!isAuthenticated || !user) {
13
+ return (<a href={`/sign-in`} className="text-sm text-blue-600 hover:text-blue-800 underline">
14
+ Sign In
15
+ </a>);
16
+ }
17
+ return (<div className="flex items-center gap-3">
18
+ {user.imageUrl && (<image_1.default src={user.imageUrl} alt={user.name || "User avatar"} className="w-8 h-8 rounded-full" width={32} height={32}/>)}
19
+ <div className="flex flex-col">
20
+ {user.name && (<span className="text-sm font-medium text-gray-900">
21
+ {user.name}
22
+ </span>)}
23
+ {user.email && (<span className="text-xs text-gray-500">{user.email}</span>)}
24
+ </div>
25
+ <a href={signOutUrl} className="text-sm text-red-600 hover:text-red-800 underline ml-2">
26
+ Sign Out
27
+ </a>
28
+ </div>);
29
+ }
@@ -0,0 +1,6 @@
1
+ export default function ErrorPage({ error, reset, }: {
2
+ error: Error & {
3
+ digest?: string;
4
+ };
5
+ reset: () => void;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ "use client";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.default = ErrorPage;
8
+ const react_1 = require("react");
9
+ const link_1 = __importDefault(require("next/link"));
10
+ const isConfigurationError = (message) => {
11
+ return (message.includes("AUTH_PROVIDER env var is required") ||
12
+ message.includes("Unsupported auth provider") ||
13
+ message.includes("Missing required auth configuration"));
14
+ };
15
+ function ErrorPage({ error, reset, }) {
16
+ (0, react_1.useEffect)(() => {
17
+ console.error(error);
18
+ }, [error]);
19
+ const isDev = process.env.NODE_ENV === "development";
20
+ const showConfigDetails = isDev && isConfigurationError(error.message);
21
+ return (<main className="mx-auto flex min-h-screen max-w-2xl flex-col gap-4 px-6 py-16">
22
+ <h1 className="text-3xl font-semibold">Dashboard configuration error</h1>
23
+ <p className="text-base text-slate-700">
24
+ The dashboard could not start because authentication settings are
25
+ missing or invalid.
26
+ </p>
27
+ {showConfigDetails && (<div className="rounded-md border border-amber-200 bg-amber-50 p-4 text-sm text-amber-900">
28
+ <p className="font-medium">Details</p>
29
+ <p className="mt-2 break-words">{error.message}</p>
30
+ </div>)}
31
+ <div className="flex gap-3">
32
+ <button type="button" onClick={() => reset()} className="rounded-md border border-slate-300 px-3 py-2 text-sm">
33
+ Retry
34
+ </button>
35
+ <link_1.default className="rounded-md bg-slate-900 px-3 py-2 text-sm text-white" href="/">
36
+ Go to home
37
+ </link_1.default>
38
+ </div>
39
+ </main>);
40
+ }
@@ -0,0 +1,6 @@
1
+ import type { Metadata } from "next";
2
+ import "./globals.css";
3
+ export declare const metadata: Metadata;
4
+ export default function RootLayout({ children, }: Readonly<{
5
+ children: React.ReactNode;
6
+ }>): Promise<import("react").JSX.Element>;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.metadata = void 0;
4
+ exports.default = RootLayout;
5
+ const google_1 = require("next/font/google");
6
+ require("./globals.css");
7
+ const auth_1 = require("@/lib/auth");
8
+ const auth_context_1 = require("@/lib/auth/auth-context");
9
+ const validate_config_1 = require("@/lib/auth/validate-config");
10
+ const geistSans = (0, google_1.Geist)({
11
+ variable: "--font-geist-sans",
12
+ subsets: ["latin"],
13
+ });
14
+ const geistMono = (0, google_1.Geist_Mono)({
15
+ variable: "--font-geist-mono",
16
+ subsets: ["latin"],
17
+ });
18
+ exports.metadata = {
19
+ title: "Vintasend Dashboard",
20
+ description: "Authentication-enabled dashboard for Vintasend",
21
+ };
22
+ async function RootLayout({ children, }) {
23
+ const strategy = (0, auth_1.resolveAuthStrategy)();
24
+ (0, validate_config_1.assertValidAuthConfig)(strategy);
25
+ const ProviderComponent = strategy.getProviderComponent();
26
+ const currentUser = await strategy.getCurrentUser();
27
+ const signInUrl = strategy.getSignInUrl();
28
+ const signOutUrl = strategy.getSignOutUrl();
29
+ return (<html lang="en">
30
+ <body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
31
+ <ProviderComponent>
32
+ <auth_context_1.AuthProvider initialUser={currentUser} signInUrl={signInUrl} signOutUrl={signOutUrl}>
33
+ {children}
34
+ </auth_context_1.AuthProvider>
35
+ </ProviderComponent>
36
+ </body>
37
+ </html>);
38
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Server actions for the notifications page.
3
+ * Handles data fetching and real-time updates without page reloads.
4
+ */
5
+ import type { AnyDashboardNotification, AnyDashboardNotificationDetail, NotificationFilters, PaginatedResult } from '@/lib/notifications/types';
6
+ /**
7
+ * Fetches notifications with optional filtering and pagination.
8
+ * Returns mock data in Phase 2; will call real backend in Phase 5.
9
+ *
10
+ * @param filters - Filter criteria (status, notificationType, search)
11
+ * @param page - Page number (1-indexed)
12
+ * @param pageSize - Number of items per page
13
+ * @returns Paginated result of dashboard notifications
14
+ */
15
+ export declare function fetchNotifications(filters: NotificationFilters, page: number, pageSize: number): Promise<PaginatedResult<AnyDashboardNotification>>;
16
+ /**
17
+ * Fetches a single notification with full details (contextUsed, extraParams, etc.).
18
+ * Used by the detail panel/modal.
19
+ *
20
+ * @param id - Notification ID
21
+ * @returns Full notification detail
22
+ */
23
+ export declare function fetchNotificationDetail(id: string): Promise<AnyDashboardNotificationDetail>;
24
+ /**
25
+ * Fetches pending notifications (status = PENDING_SEND).
26
+ * Convenience method that calls fetchNotifications with a pre-set filter.
27
+ *
28
+ * @param page - Page number (1-indexed)
29
+ * @param pageSize - Number of items per page
30
+ * @returns Paginated result of pending notifications
31
+ */
32
+ export declare function fetchPendingNotifications(page: number, pageSize: number): Promise<PaginatedResult<AnyDashboardNotification>>;
33
+ /**
34
+ * Fetches future notifications (sendAfter > now).
35
+ * Convenience method that calls fetchNotifications.
36
+ * In Phase 5, will delegate to backend's getFutureNotifications method.
37
+ *
38
+ * @param page - Page number (1-indexed)
39
+ * @param pageSize - Number of items per page
40
+ * @returns Paginated result of scheduled notifications
41
+ */
42
+ export declare function fetchFutureNotifications(page: number, pageSize: number): Promise<PaginatedResult<AnyDashboardNotification>>;
43
+ /**
44
+ * Fetches one-off notifications only.
45
+ * In Phase 5, will call backend's getOneOffNotifications method.
46
+ *
47
+ * @param page - Page number (1-indexed)
48
+ * @param pageSize - Number of items per page
49
+ * @returns Paginated result of one-off notifications
50
+ */
51
+ export declare function fetchOneOffNotifications(page: number, pageSize: number): Promise<PaginatedResult<AnyDashboardNotification>>;
@@ -0,0 +1,224 @@
1
+ "use strict";
2
+ 'use server';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.fetchNotifications = fetchNotifications;
5
+ exports.fetchNotificationDetail = fetchNotificationDetail;
6
+ exports.fetchPendingNotifications = fetchPendingNotifications;
7
+ exports.fetchFutureNotifications = fetchFutureNotifications;
8
+ exports.fetchOneOffNotifications = fetchOneOffNotifications;
9
+ /**
10
+ * Mock data generators for Phase 2 development.
11
+ * Will be replaced with real backend calls in Phase 5.
12
+ */
13
+ function generateMockNotification(id) {
14
+ const types = ['EMAIL', 'SMS', 'PUSH', 'IN_APP'];
15
+ const statuses = ['PENDING_SEND', 'SENT', 'FAILED', 'READ', 'CANCELLED'];
16
+ const notificationType = types[Math.floor(Math.random() * types.length)];
17
+ const status = statuses[Math.floor(Math.random() * statuses.length)];
18
+ const sendAfter = Math.random() > 0.7 ? new Date(Date.now() + Math.random() * 86400000).toISOString() : null;
19
+ const sentAt = status !== 'PENDING_SEND' && status !== 'CANCELLED'
20
+ ? new Date(Date.now() - Math.random() * 604800000).toISOString()
21
+ : null;
22
+ const readAt = status === 'READ' ? new Date(Date.now() - 3600000).toISOString() : null;
23
+ const createdAt = new Date(Date.now() - Math.random() * 2592000000).toISOString();
24
+ const bodyTemplate = `This is a mock notification body for notification ${id}`;
25
+ const subjectTemplate = `Notification ${id}`;
26
+ if (Math.random() > 0.5) {
27
+ // One-off notification
28
+ const oneOffNotification = {
29
+ id: `oneoff-${id}`,
30
+ emailOrPhone: `user${id}@example.com`,
31
+ firstName: 'User',
32
+ lastName: `${id}`,
33
+ notificationType,
34
+ title: Math.random() > 0.3 ? `One-off notification ${id}` : null,
35
+ contextName: 'defaultContext',
36
+ status,
37
+ sendAfter,
38
+ sentAt,
39
+ readAt,
40
+ createdAt,
41
+ adapterUsed: notificationType === 'EMAIL' ? 'nodemailer' : null,
42
+ bodyTemplate,
43
+ subjectTemplate,
44
+ };
45
+ return oneOffNotification;
46
+ }
47
+ // Regular notification
48
+ const regularNotification = {
49
+ id: `notif-${id}`,
50
+ userId: `user-${Math.floor(Math.random() * 100)}`,
51
+ notificationType,
52
+ title: Math.random() > 0.3 ? `Notification ${id}` : null,
53
+ contextName: 'defaultContext',
54
+ status,
55
+ sendAfter,
56
+ sentAt,
57
+ readAt,
58
+ createdAt,
59
+ adapterUsed: notificationType === 'EMAIL' ? 'nodemailer' : null,
60
+ bodyTemplate,
61
+ subjectTemplate,
62
+ };
63
+ return regularNotification;
64
+ }
65
+ /**
66
+ * Fetches notifications with optional filtering and pagination.
67
+ * Returns mock data in Phase 2; will call real backend in Phase 5.
68
+ *
69
+ * @param filters - Filter criteria (status, notificationType, search)
70
+ * @param page - Page number (1-indexed)
71
+ * @param pageSize - Number of items per page
72
+ * @returns Paginated result of dashboard notifications
73
+ */
74
+ async function fetchNotifications(filters, page, pageSize) {
75
+ // Phase 2: Generate mock data
76
+ // In Phase 5, replace with:
77
+ // const service = await getVintaSendService();
78
+ // const result = await service.getNotifications(page, pageSize);
79
+ const mockTotal = 127;
80
+ const allMockData = Array.from({ length: mockTotal }, (_, i) => generateMockNotification(i + 1));
81
+ // Apply filters
82
+ let filtered = allMockData;
83
+ if (filters.status) {
84
+ filtered = filtered.filter((item) => item.status === filters.status);
85
+ }
86
+ if (filters.notificationType) {
87
+ filtered = filtered.filter((item) => item.notificationType === filters.notificationType);
88
+ }
89
+ if (filters.search) {
90
+ const searchLower = filters.search.toLowerCase();
91
+ filtered = filtered.filter((item) => {
92
+ var _a, _b;
93
+ return ((_b = (_a = item.title) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(searchLower)) !== null && _b !== void 0 ? _b : false) ||
94
+ item.id.toLowerCase().includes(searchLower) ||
95
+ ('emailOrPhone' in item && item.emailOrPhone.toLowerCase().includes(searchLower));
96
+ });
97
+ }
98
+ // Paginate
99
+ const startIdx = (page - 1) * pageSize;
100
+ const endIdx = startIdx + pageSize;
101
+ const paginatedData = filtered.slice(startIdx, endIdx);
102
+ return {
103
+ data: paginatedData,
104
+ page,
105
+ pageSize,
106
+ total: filtered.length,
107
+ };
108
+ }
109
+ /**
110
+ * Fetches a single notification with full details (contextUsed, extraParams, etc.).
111
+ * Used by the detail panel/modal.
112
+ *
113
+ * @param id - Notification ID
114
+ * @returns Full notification detail
115
+ */
116
+ async function fetchNotificationDetail(id) {
117
+ // Phase 2: Generate mock detail
118
+ // In Phase 5, replace with:
119
+ // const service = await getVintaSendService();
120
+ // const notification = await service.getNotification(id);
121
+ const mockNotification = Math.random() > 0.5
122
+ ? {
123
+ id: `oneoff-${id}`,
124
+ emailOrPhone: `user@example.com`,
125
+ firstName: 'John',
126
+ lastName: 'Doe',
127
+ notificationType: 'EMAIL',
128
+ title: `Notification ${id}`,
129
+ contextName: 'defaultContext',
130
+ status: 'SENT',
131
+ sendAfter: null,
132
+ sentAt: new Date().toISOString(),
133
+ readAt: null,
134
+ createdAt: new Date().toISOString(),
135
+ adapterUsed: 'nodemailer',
136
+ bodyTemplate: 'This is the full body template with dynamic content.',
137
+ subjectTemplate: `Subject for ${id}`,
138
+ contextUsed: {
139
+ userName: 'John Doe',
140
+ accountBalance: 1500.0,
141
+ },
142
+ contextParameters: {
143
+ userId: 'user-123',
144
+ userRole: 'customer',
145
+ },
146
+ extraParams: {
147
+ campaignId: 'campaign-123',
148
+ trackingCode: 'track-abc',
149
+ },
150
+ }
151
+ : {
152
+ id: `notif-${id}`,
153
+ userId: 'user-42',
154
+ notificationType: 'EMAIL',
155
+ title: `Notification ${id}`,
156
+ contextName: 'defaultContext',
157
+ status: 'SENT',
158
+ sendAfter: null,
159
+ sentAt: new Date().toISOString(),
160
+ readAt: null,
161
+ createdAt: new Date().toISOString(),
162
+ adapterUsed: 'nodemailer',
163
+ bodyTemplate: 'This is the full body template with dynamic content.',
164
+ subjectTemplate: `Subject for ${id}`,
165
+ contextUsed: {
166
+ userName: 'Test User',
167
+ accountBalance: 2000.0,
168
+ },
169
+ contextParameters: {
170
+ userId: 'user-42',
171
+ userRole: 'admin',
172
+ },
173
+ extraParams: {
174
+ campaignId: 'campaign-456',
175
+ trackingCode: 'track-def',
176
+ },
177
+ };
178
+ return mockNotification;
179
+ }
180
+ /**
181
+ * Fetches pending notifications (status = PENDING_SEND).
182
+ * Convenience method that calls fetchNotifications with a pre-set filter.
183
+ *
184
+ * @param page - Page number (1-indexed)
185
+ * @param pageSize - Number of items per page
186
+ * @returns Paginated result of pending notifications
187
+ */
188
+ async function fetchPendingNotifications(page, pageSize) {
189
+ return fetchNotifications({ status: 'PENDING_SEND' }, page, pageSize);
190
+ }
191
+ /**
192
+ * Fetches future notifications (sendAfter > now).
193
+ * Convenience method that calls fetchNotifications.
194
+ * In Phase 5, will delegate to backend's getFutureNotifications method.
195
+ *
196
+ * @param page - Page number (1-indexed)
197
+ * @param pageSize - Number of items per page
198
+ * @returns Paginated result of scheduled notifications
199
+ */
200
+ async function fetchFutureNotifications(page, pageSize) {
201
+ const result = await fetchNotifications({}, page, pageSize);
202
+ // Filter to only future notifications (in real implementation, backend would do this)
203
+ const now = new Date();
204
+ return {
205
+ ...result,
206
+ data: result.data.filter((n) => n.sendAfter && new Date(n.sendAfter) > now),
207
+ };
208
+ }
209
+ /**
210
+ * Fetches one-off notifications only.
211
+ * In Phase 5, will call backend's getOneOffNotifications method.
212
+ *
213
+ * @param page - Page number (1-indexed)
214
+ * @param pageSize - Number of items per page
215
+ * @returns Paginated result of one-off notifications
216
+ */
217
+ async function fetchOneOffNotifications(page, pageSize) {
218
+ const result = await fetchNotifications({}, page, pageSize);
219
+ // Filter to only one-off notifications
220
+ return {
221
+ ...result,
222
+ data: result.data.filter((n) => n.id.startsWith('oneoff-')),
223
+ };
224
+ }
@@ -0,0 +1,6 @@
1
+ import { ColumnDef } from '@tanstack/react-table';
2
+ import type { AnyDashboardNotification } from '@/lib/notifications/types';
3
+ /**
4
+ * TanStack column definitions for the notifications table.
5
+ */
6
+ export declare const columns: ColumnDef<AnyDashboardNotification>[];