vintasend 0.5.0 → 0.5.2
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/services/notification-service.d.ts +2 -0
- package/dist/services/notification-service.js +6 -0
- 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 +1 -1
|
@@ -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.
|
|
@@ -254,6 +254,12 @@ class VintaSend {
|
|
|
254
254
|
async getPendingNotifications(page, pageSize) {
|
|
255
255
|
return this.backend.getPendingNotifications(page, pageSize);
|
|
256
256
|
}
|
|
257
|
+
async getNotifications(page, pageSize) {
|
|
258
|
+
return this.backend.getNotifications(page, pageSize);
|
|
259
|
+
}
|
|
260
|
+
async getOneOffNotifications(page, pageSize) {
|
|
261
|
+
return this.backend.getOneOffNotifications(page, pageSize);
|
|
262
|
+
}
|
|
257
263
|
async getNotification(notificationId, forUpdate = false) {
|
|
258
264
|
return this.backend.getNotification(notificationId, forUpdate);
|
|
259
265
|
}
|
|
@@ -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>[];
|
|
@@ -0,0 +1,158 @@
|
|
|
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.columns = void 0;
|
|
8
|
+
const react_1 = __importDefault(require("react"));
|
|
9
|
+
const date_fns_1 = require("date-fns");
|
|
10
|
+
const badge_1 = require("@/components/ui/badge");
|
|
11
|
+
const button_1 = require("@/components/ui/button");
|
|
12
|
+
const dropdown_menu_1 = require("@/components/ui/dropdown-menu");
|
|
13
|
+
const lucide_react_1 = require("lucide-react");
|
|
14
|
+
/**
|
|
15
|
+
* Maps notification status to badge variant colors.
|
|
16
|
+
*/
|
|
17
|
+
const statusVariantMap = {
|
|
18
|
+
PENDING_SEND: 'default',
|
|
19
|
+
SENT: 'secondary',
|
|
20
|
+
FAILED: 'destructive',
|
|
21
|
+
READ: 'outline',
|
|
22
|
+
CANCELLED: 'secondary',
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Maps notification type to badge variant colors.
|
|
26
|
+
*/
|
|
27
|
+
const typeVariantMap = {
|
|
28
|
+
EMAIL: 'default',
|
|
29
|
+
SMS: 'secondary',
|
|
30
|
+
PUSH: 'outline',
|
|
31
|
+
IN_APP: 'default',
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Formats a date string (ISO format) for display.
|
|
35
|
+
* Returns empty string if date is null/undefined.
|
|
36
|
+
*/
|
|
37
|
+
function formatDate(dateString) {
|
|
38
|
+
if (!dateString)
|
|
39
|
+
return '—';
|
|
40
|
+
try {
|
|
41
|
+
return (0, date_fns_1.format)(new Date(dateString), 'MMM dd, yyyy HH:mm');
|
|
42
|
+
}
|
|
43
|
+
catch (_a) {
|
|
44
|
+
return '—';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Determines if a notification is one-off or regular.
|
|
49
|
+
*/
|
|
50
|
+
function isOneOff(notification) {
|
|
51
|
+
return 'emailOrPhone' in notification;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Gets the recipient identifier for display.
|
|
55
|
+
*/
|
|
56
|
+
function getRecipient(notification) {
|
|
57
|
+
if (isOneOff(notification)) {
|
|
58
|
+
return notification.emailOrPhone || '—';
|
|
59
|
+
}
|
|
60
|
+
// For regular notifications, access userId (DashboardNotification has userId field)
|
|
61
|
+
const regularNotification = notification;
|
|
62
|
+
return regularNotification.userId || '—';
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* TanStack column definitions for the notifications table.
|
|
66
|
+
*/
|
|
67
|
+
exports.columns = [
|
|
68
|
+
{
|
|
69
|
+
accessorKey: 'id',
|
|
70
|
+
header: 'ID',
|
|
71
|
+
cell: ({ row }) => <span className="font-mono text-sm">{row.original.id}</span>,
|
|
72
|
+
size: 120,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
accessorKey: 'title',
|
|
76
|
+
header: 'Title',
|
|
77
|
+
cell: ({ row }) => row.original.title || '—',
|
|
78
|
+
size: 180,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
accessorKey: 'notificationType',
|
|
82
|
+
header: 'Type',
|
|
83
|
+
cell: ({ row }) => {
|
|
84
|
+
const type = row.original.notificationType;
|
|
85
|
+
return <badge_1.Badge variant={typeVariantMap[type]}>{type}</badge_1.Badge>;
|
|
86
|
+
},
|
|
87
|
+
size: 100,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
accessorKey: 'status',
|
|
91
|
+
header: 'Status',
|
|
92
|
+
cell: ({ row }) => {
|
|
93
|
+
const status = row.original.status;
|
|
94
|
+
return <badge_1.Badge variant={statusVariantMap[status]}>{status}</badge_1.Badge>;
|
|
95
|
+
},
|
|
96
|
+
size: 120,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
accessorKey: 'contextName',
|
|
100
|
+
header: 'Context',
|
|
101
|
+
cell: ({ row }) => row.original.contextName || '—',
|
|
102
|
+
size: 150,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
accessorKey: 'recipient',
|
|
106
|
+
header: 'Recipient',
|
|
107
|
+
cell: ({ row }) => getRecipient(row.original),
|
|
108
|
+
size: 180,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
accessorKey: 'sendAfter',
|
|
112
|
+
header: 'Send After',
|
|
113
|
+
cell: ({ row }) => formatDate(row.original.sendAfter),
|
|
114
|
+
size: 160,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
accessorKey: 'sentAt',
|
|
118
|
+
header: 'Sent At',
|
|
119
|
+
cell: ({ row }) => formatDate(row.original.sentAt),
|
|
120
|
+
size: 160,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
accessorKey: 'createdAt',
|
|
124
|
+
header: ({ column }) => (<button_1.Button variant="ghost" onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')} className="h-8">
|
|
125
|
+
Created At
|
|
126
|
+
<lucide_react_1.ArrowUpDown className="ml-2 h-4 w-4"/>
|
|
127
|
+
</button_1.Button>),
|
|
128
|
+
cell: ({ row }) => formatDate(row.original.createdAt),
|
|
129
|
+
size: 160,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
accessorKey: 'adapterUsed',
|
|
133
|
+
header: 'Adapter',
|
|
134
|
+
cell: ({ row }) => row.original.adapterUsed || '—',
|
|
135
|
+
size: 120,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: 'actions',
|
|
139
|
+
header: 'Actions',
|
|
140
|
+
cell: ({ row }) => (<dropdown_menu_1.DropdownMenu>
|
|
141
|
+
<dropdown_menu_1.DropdownMenuTrigger asChild>
|
|
142
|
+
<button_1.Button variant="ghost" className="h-8 w-8 p-0">
|
|
143
|
+
<span className="sr-only">Open menu</span>
|
|
144
|
+
<lucide_react_1.ChevronDown className="h-4 w-4"/>
|
|
145
|
+
</button_1.Button>
|
|
146
|
+
</dropdown_menu_1.DropdownMenuTrigger>
|
|
147
|
+
<dropdown_menu_1.DropdownMenuContent align="end">
|
|
148
|
+
<dropdown_menu_1.DropdownMenuLabel>Actions</dropdown_menu_1.DropdownMenuLabel>
|
|
149
|
+
<dropdown_menu_1.DropdownMenuSeparator />
|
|
150
|
+
<dropdown_menu_1.DropdownMenuItem onClick={() => console.log('View details:', row.original.id)}>
|
|
151
|
+
View Details
|
|
152
|
+
</dropdown_menu_1.DropdownMenuItem>
|
|
153
|
+
<dropdown_menu_1.DropdownMenuItem disabled>Copy ID</dropdown_menu_1.DropdownMenuItem>
|
|
154
|
+
</dropdown_menu_1.DropdownMenuContent>
|
|
155
|
+
</dropdown_menu_1.DropdownMenu>),
|
|
156
|
+
size: 80,
|
|
157
|
+
},
|
|
158
|
+
];
|
package/dist/tools/vintasend-dashboard/app/notifications/components/notifications-filters.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { NotificationFilters } from '@/lib/notifications/types';
|
|
3
|
+
interface NotificationsFiltersProps {
|
|
4
|
+
onFiltersChange?: (filters: NotificationFilters) => void;
|
|
5
|
+
isLoading?: boolean;
|
|
6
|
+
initialFilters?: NotificationFilters;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Filter bar for notifications list.
|
|
10
|
+
* Allows filtering by status, type, and free-text search.
|
|
11
|
+
* Search input is debounced (300ms) before calling the change callback.
|
|
12
|
+
*
|
|
13
|
+
* @param onFiltersChange - Callback invoked when filters change (after debounce for search)
|
|
14
|
+
* @param isLoading - Whether data is currently loading
|
|
15
|
+
* @param initialFilters - Initial filter values
|
|
16
|
+
*/
|
|
17
|
+
export declare function NotificationsFilters({ onFiltersChange, isLoading, initialFilters, }: NotificationsFiltersProps): React.JSX.Element;
|
|
18
|
+
export {};
|