Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 7 additions & 55 deletions src/components/email/records/email-status.tsx
Original file line number Diff line number Diff line change
@@ -1,66 +1,18 @@
'use client';

import { useEffect, useState } from 'react';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { RefreshCw } from 'lucide-react';
import { fetchEmailStatus } from '@/lib/api/email';

interface EmailStatusProps {
emailId: string;
}

export function EmailStatus({ emailId }: Readonly<EmailStatusProps>) {
const [status, setStatus] = useState<any | null>(null);
const [loading, setLoading] = useState(false);

const fetchStatus = async () => {
setLoading(true);
try {
const statusData = await fetchEmailStatus(emailId);
setStatus(statusData.statusInfo);
} catch (error) {
console.error('Failed to fetch email status:', error);
} finally {
setLoading(false);
}
};

useEffect(() => {
fetchStatus();
}, [emailId]);

const getStatusBadge = () => {
if (!status) return <Badge variant="outline">Unknown</Badge>;

switch (status.status) {
case 'delivered':
return <Badge className="bg-green-500">Delivered</Badge>;
case 'failed':
return <Badge className="bg-red-500">Failed</Badge>;
case 'pending':
return (
<Badge variant="outline" className="bg-yellow-100 text-yellow-800">
Pending
</Badge>
);
default:
return <Badge variant="outline">Unknown</Badge>;
}
};

export function EmailStatus(_props: Readonly<EmailStatusProps>) {
return (
<div className="flex items-center space-x-2">
{getStatusBadge()}
<Button
variant="ghost"
size="icon"
onClick={fetchStatus}
disabled={loading}
className="h-8 w-8"
>
<RefreshCw className={`h-4 w-4 ${loading ? 'animate-spin' : ''}`} />
</Button>
</div>
<Badge
variant="outline"
title="Delivery status is not available via the admin API"
>
Unavailable
</Badge>
);
}
55 changes: 32 additions & 23 deletions src/lib/api/email/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,7 @@ export const createTemplate = async (data: {
};

export const deleteTemplates = async (ids: string[]) => {
return (await getApiClient())
.delete<string>('/email/templates', { params: { ids } })
.then(res => res.data);
await Promise.all(ids.map(id => deleteTemplate(id)));
};

export const deleteTemplate = async (id: string) => {
Expand Down Expand Up @@ -107,7 +105,7 @@ export const getExternalTemplates = async (args: {
count: number;
};
return (await getApiClient())
.get<Response>('/email/externalTemplates', { params: args })
.get<Response>('/email/templates/external', { params: args })
.then(res => res.data);
};

Expand All @@ -117,17 +115,14 @@ export const syncTemplates = async () => {
count: number;
};
return (await getApiClient())
.patch<Response>('/email/syncExternalTemplates')
.post<Response>('/email/templates/external/sync')
.then(res => res.data);
};

export const uploadTemplate = async (id: string) => {
return (await getApiClient())
.post('/email/templates/upload', { _id: id })
.then(res => res.data)
.catch(err => {
throw new Error(err.response?.data.message ?? err.message);
});
export const uploadTemplate = async (_id: string) => {
throw new Error(
'Template upload to the email provider is not available on the unified communications module'
);
};

export const sendEmail = async (data: EmailPayload) => {
Expand All @@ -136,16 +131,21 @@ export const sendEmail = async (data: EmailPayload) => {

export const reSendEmail = async (emailRecordId: string) => {
return (await getApiClient())
.post(`/email/resend`, { emailRecordId })
.post(`/email/resend`, { id: emailRecordId })
.then(res => res.data);
};

export const fetchEmailStatus = async (messageId: string) => {
return (await getApiClient())
.get<{
statusInfo: any;
}>(`/email/status`, { params: { messageId } })
.then(res => res.data);
const resolveEmailListSearch = (args: {
messageId?: string;
templateId?: string;
receiver?: string;
sender?: string;
}): string | undefined => {
if (args.receiver) return args.receiver;
if (args.messageId?.match(/^[a-fA-F\d]{24}$/)) return args.messageId;
if (args.templateId?.match(/^[a-fA-F\d]{24}$/)) return args.templateId;
if (args.sender) return args.sender;
return args.messageId;
};

export const fetchRecords = async (args: {
Expand All @@ -162,10 +162,19 @@ export const fetchRecords = async (args: {
sort?: string;
}) => {
type Response = {
records: EmailRecord[];
emailDocuments: EmailRecord[];
count: number;
};
return (await getApiClient())
.get<Response>('/email/record', { params: args })
.then(res => res.data);
const search = resolveEmailListSearch(args);
const res = await (
await getApiClient()
).get<Response>('/email/emails', {
params: {
skip: args.skip,
limit: args.limit,
sort: args.sort,
...(search ? { search } : {}),
},
});
return { records: res.data.emailDocuments, count: res.data.count };
};
79 changes: 57 additions & 22 deletions src/lib/api/notifications/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,22 @@ import {

type ConfigResponse = { config: NotificationSettings };

type PushSendParams = {
userId: string;
title: string;
body?: string;
data?: Record<string, unknown>;
platform?: string;
doNotStore?: boolean;
isSilent?: boolean;
};

const postPushSend = async (params: PushSendParams) => {
const client = await getApiClient();
const res = await client.post('/push/send', params);
return res.data;
};

export const getNotificationSettings = async (): Promise<ConfigResponse> => {
const res = await (
await getApiClient()
Expand All @@ -37,6 +53,7 @@ export const patchNotificationSettings = async (

return afterPatchServing(options);
};

export const getTokens = async (
skip: number,
limit: number,
Expand All @@ -48,7 +65,7 @@ export const getTokens = async (
): Promise<{ tokens: NotificationToken[]; count: number }> => {
const res = await (
await getApiClient()
).get(`/pushNotifications/token`, {
).get(`/push/tokens`, {
params: {
skip,
limit,
Expand All @@ -64,50 +81,68 @@ export const getTokenById = async (
): Promise<NotificationToken> => {
const res = await (
await getApiClient()
).get(`/pushNotifications/token/${id}`, {
).get<{ tokenDocuments: NotificationToken }>(`/push/tokens/${id}`, {
params: {
populate,
},
});
return res.data;
return res.data.tokenDocuments;
};

export const sendNotifications = async (params: {
userIds: string[];
title: string;
body?: string;
data?: Record<string, any>;
data?: Record<string, unknown>;
isSilent?: boolean;
platform?: string;
doNotStore?: boolean;
}): Promise<NotificationToken> => {
const res = await (
await getApiClient()
).post(`/pushNotifications/sendToManyDevices`, {
...params,
});
return res.data;
}): Promise<void> => {
const { userIds, title, body, data, isSilent, platform, doNotStore } = params;
const results = await Promise.allSettled(
userIds.map(userId =>
postPushSend({
userId,
title,
body,
data,
isSilent,
platform,
doNotStore,
})
)
);
const failures = results.filter(r => r.status === 'rejected');
if (failures.length > 0) {
throw new Error(
`Failed to send to ${failures.length} of ${userIds.length} recipient(s)`
);
}
};

export const sendPushNotification = async (data: Record<string, unknown>) => {
const res = await (
await getApiClient()
).post('/pushNotifications/send', data);
return res.data;
return postPushSend(data as PushSendParams);
};

export const sendManyPushNotifications = async (
data: Record<string, unknown>
notifications: PushSendParams[]
) => {
const res = await (
await getApiClient()
).post('/pushNotifications/sendMany', data);
return res.data;
const results = await Promise.allSettled(
notifications.map(notification => postPushSend(notification))
);
const failures = results.filter(r => r.status === 'rejected');
if (failures.length > 0) {
throw new Error(
`Failed to send ${failures.length} of ${notifications.length} notification(s)`
);
}
};

export const getPushTokensForUser = async (userId: string) => {
const res = await (
await getApiClient()
).get(`/pushNotifications/token/user/${userId}`);
return res.data;
).get<{ tokens: NotificationToken[]; count: number }>(`/push/tokens`, {
params: { search: userId },
});
return { tokens: res.data.tokens };
};
Loading