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.
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/services/notification-backends/base-notification-backend.d.ts +87 -1
- package/dist/services/notification-backends/base-notification-backend.js +7 -0
- package/dist/services/notification-service.d.ts +2 -0
- package/dist/services/notification-service.js +14 -4
- package/dist/tools/vintasend-dashboard/app/components/auth-status.d.ts +1 -0
- package/dist/tools/vintasend-dashboard/app/components/auth-status.js +29 -0
- package/dist/tools/vintasend-dashboard/app/error.d.ts +6 -0
- package/dist/tools/vintasend-dashboard/app/error.js +40 -0
- package/dist/tools/vintasend-dashboard/app/layout.d.ts +6 -0
- package/dist/tools/vintasend-dashboard/app/layout.js +38 -0
- package/dist/tools/vintasend-dashboard/app/notifications/actions.d.ts +51 -0
- package/dist/tools/vintasend-dashboard/app/notifications/actions.js +224 -0
- package/dist/tools/vintasend-dashboard/app/notifications/components/columns.d.ts +6 -0
- package/dist/tools/vintasend-dashboard/app/notifications/components/columns.js +158 -0
- package/dist/tools/vintasend-dashboard/app/notifications/components/notifications-filters.d.ts +18 -0
- package/dist/tools/vintasend-dashboard/app/notifications/components/notifications-filters.js +162 -0
- package/dist/tools/vintasend-dashboard/app/notifications/components/notifications-table.d.ts +23 -0
- package/dist/tools/vintasend-dashboard/app/notifications/components/notifications-table.js +132 -0
- package/dist/tools/vintasend-dashboard/app/page.d.ts +1 -0
- package/dist/tools/vintasend-dashboard/app/page.js +32 -0
- package/dist/tools/vintasend-dashboard/app/sign-in/[[...sign-in]]/page.d.ts +1 -0
- package/dist/tools/vintasend-dashboard/app/sign-in/[[...sign-in]]/page.js +18 -0
- package/dist/tools/vintasend-dashboard/app/sign-out/[[...sign-out]]/page.d.ts +1 -0
- package/dist/tools/vintasend-dashboard/app/sign-out/[[...sign-out]]/page.js +16 -0
- package/dist/tools/vintasend-dashboard/components/ui/badge.d.ts +9 -0
- package/dist/tools/vintasend-dashboard/components/ui/badge.js +61 -0
- package/dist/tools/vintasend-dashboard/components/ui/button.d.ts +10 -0
- package/dist/tools/vintasend-dashboard/components/ui/button.js +72 -0
- package/dist/tools/vintasend-dashboard/components/ui/calendar.d.ts +8 -0
- package/dist/tools/vintasend-dashboard/components/ui/calendar.js +116 -0
- package/dist/tools/vintasend-dashboard/components/ui/dropdown-menu.d.ts +25 -0
- package/dist/tools/vintasend-dashboard/components/ui/dropdown-menu.js +119 -0
- package/dist/tools/vintasend-dashboard/components/ui/input.d.ts +3 -0
- package/dist/tools/vintasend-dashboard/components/ui/input.js +41 -0
- package/dist/tools/vintasend-dashboard/components/ui/pagination.d.ts +13 -0
- package/dist/tools/vintasend-dashboard/components/ui/pagination.js +79 -0
- package/dist/tools/vintasend-dashboard/components/ui/popover.d.ts +10 -0
- package/dist/tools/vintasend-dashboard/components/ui/popover.js +69 -0
- package/dist/tools/vintasend-dashboard/components/ui/select.d.ts +15 -0
- package/dist/tools/vintasend-dashboard/components/ui/select.js +106 -0
- package/dist/tools/vintasend-dashboard/components/ui/skeleton.d.ts +2 -0
- package/dist/tools/vintasend-dashboard/components/ui/skeleton.js +7 -0
- package/dist/tools/vintasend-dashboard/components/ui/table.d.ts +10 -0
- package/dist/tools/vintasend-dashboard/components/ui/table.js +72 -0
- package/dist/tools/vintasend-dashboard/lib/auth/auth-context.d.ts +23 -0
- package/dist/tools/vintasend-dashboard/lib/auth/auth-context.js +30 -0
- package/dist/tools/vintasend-dashboard/lib/auth/index.d.ts +5 -0
- package/dist/tools/vintasend-dashboard/lib/auth/index.js +12 -0
- package/dist/tools/vintasend-dashboard/lib/auth/resolve-strategy.d.ts +5 -0
- package/dist/tools/vintasend-dashboard/lib/auth/resolve-strategy.js +24 -0
- package/dist/tools/vintasend-dashboard/lib/auth/strategies/auth0-strategy.d.ts +17 -0
- package/dist/tools/vintasend-dashboard/lib/auth/strategies/auth0-strategy.js +76 -0
- package/dist/tools/vintasend-dashboard/lib/auth/strategies/clerk-strategy.d.ts +18 -0
- package/dist/tools/vintasend-dashboard/lib/auth/strategies/clerk-strategy.js +70 -0
- package/dist/tools/vintasend-dashboard/lib/auth/types.d.ts +50 -0
- package/dist/tools/vintasend-dashboard/lib/auth/types.js +2 -0
- package/dist/tools/vintasend-dashboard/lib/auth/validate-config.d.ts +2 -0
- package/dist/tools/vintasend-dashboard/lib/auth/validate-config.js +10 -0
- package/dist/tools/vintasend-dashboard/lib/auth0.d.ts +6 -0
- package/dist/tools/vintasend-dashboard/lib/auth0.js +14 -0
- package/dist/tools/vintasend-dashboard/lib/notifications/get-vintasend-service.d.ts +26 -0
- package/dist/tools/vintasend-dashboard/lib/notifications/get-vintasend-service.js +31 -0
- package/dist/tools/vintasend-dashboard/lib/notifications/serialize.d.ts +27 -0
- package/dist/tools/vintasend-dashboard/lib/notifications/serialize.js +75 -0
- package/dist/tools/vintasend-dashboard/lib/notifications/types.d.ts +62 -0
- package/dist/tools/vintasend-dashboard/lib/notifications/types.js +6 -0
- package/dist/tools/vintasend-dashboard/lib/utils.d.ts +2 -0
- package/dist/tools/vintasend-dashboard/lib/utils.js +8 -0
- package/dist/tools/vintasend-dashboard/next.config.d.ts +3 -0
- package/dist/tools/vintasend-dashboard/next.config.js +6 -0
- package/dist/tools/vintasend-dashboard/proxy.d.ts +5 -0
- package/dist/tools/vintasend-dashboard/proxy.js +83 -0
- 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
|
-
|
|
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.
|
|
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.
|
|
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,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,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>[];
|