Skip to content

Commit 2f8ffbe

Browse files
bug-fix in admin dashboard: In event details page the recipient and content sections are now redered using the provider metadata. In events page, in recipient column, the recipients are obtained from provider metadata.
1 parent 58a5818 commit 2f8ffbe

2 files changed

Lines changed: 111 additions & 56 deletions

File tree

dashboard/app/events/[id]/page.tsx

Lines changed: 87 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222

2323
import { ArrowLeft, RefreshCw, Trash2, Clock, User, Mail, Phone, Link as LinkIcon } from "lucide-react";
2424
import { format } from "date-fns";
25-
import { Notification, NOTIFICATION_STATUS } from "@/lib/types";
25+
import { Notification, NOTIFICATION_STATUS, PluginMetadata, ProviderMetadata } from "@/lib/types";
2626
import Link from "next/link";
2727
import { useState } from "react";
2828
import { toast } from "sonner";
@@ -45,8 +45,24 @@ export default function EventDetailPage({ params }: PageProps) {
4545
fetcher
4646
);
4747

48+
// Fetch plugin metadata for dynamic schema rendering
49+
const { data: plugins } = useSWR<PluginMetadata>('/api/plugins', fetcher);
50+
4851
const [retryDialogOpen, setRetryDialogOpen] = useState(false);
4952

53+
// Helper to find provider metadata based on channel and provider ID
54+
const getProviderMetadata = (
55+
channel: string,
56+
providerId: string | undefined
57+
): ProviderMetadata | undefined => {
58+
if (!plugins?.channels[channel]) return undefined;
59+
const channelMeta = plugins.channels[channel];
60+
// Find by providerId, or use default, or use first provider
61+
return channelMeta.providers.find(p => p.id === providerId)
62+
|| channelMeta.providers.find(p => p.id === channelMeta.default)
63+
|| channelMeta.providers[0];
64+
};
65+
5066
const handleRetry = async () => {
5167
if (!notification) return;
5268

@@ -289,25 +305,35 @@ export default function EventDetailPage({ params }: PageProps) {
289305
<CardDescription>Recipient details</CardDescription>
290306
</CardHeader>
291307
<CardContent className="space-y-4">
292-
<div className="flex items-center gap-2">
293-
<User className="h-4 w-4 text-muted-foreground" />
294-
<span className="text-sm text-muted-foreground">User ID:</span>
295-
<span className="font-mono text-sm">{String(notification.recipient.user_id || '')}</span>
296-
</div>
297-
{Boolean(notification.recipient.email) && (
298-
<div className="flex items-center gap-2">
299-
<Mail className="h-4 w-4 text-muted-foreground" />
300-
<span className="text-sm text-muted-foreground">Email:</span>
301-
<span className="text-sm">{String(notification.recipient.email)}</span>
302-
</div>
303-
)}
304-
{Boolean(notification.recipient.phone) && (
305-
<div className="flex items-center gap-2">
306-
<Phone className="h-4 w-4 text-muted-foreground" />
307-
<span className="text-sm text-muted-foreground">Phone:</span>
308-
<span className="text-sm">{String(notification.recipient.phone)}</span>
309-
</div>
310-
)}
308+
{(() => {
309+
const providerMeta = getProviderMetadata(notification.channel, notification.provider);
310+
311+
return (
312+
<>
313+
{providerMeta?.recipientFields.map(field => {
314+
const value = notification.recipient[field.name];
315+
if (value === undefined || value === null || value === '') return null;
316+
317+
// Choose icon based on field type
318+
const IconComponent = field.type === 'email' ? Mail
319+
: field.type === 'phone' ? Phone
320+
: User;
321+
322+
return (
323+
<div key={field.name} className="flex items-center gap-2">
324+
<IconComponent className="h-4 w-4 text-muted-foreground" />
325+
<span className="text-sm text-muted-foreground capitalize">
326+
{field.name.replace(/_/g, ' ')}:
327+
</span>
328+
<span className={`text-sm ${field.type === 'email' || field.name === 'user_id' ? 'font-mono' : ''}`}>
329+
{String(value)}
330+
</span>
331+
</div>
332+
);
333+
})}
334+
</>
335+
);
336+
})()}
311337
<Separator />
312338
<div className="flex items-start gap-2">
313339
<LinkIcon className="h-4 w-4 text-muted-foreground mt-0.5" />
@@ -325,41 +351,48 @@ export default function EventDetailPage({ params }: PageProps) {
325351
<CardTitle>Content</CardTitle>
326352
<CardDescription>Message content for this notification</CardDescription>
327353
</CardHeader>
328-
<CardContent>
329-
{notification.channel === "email" && (notification.content as Record<string, { subject?: string; message?: string }>).email && (
330-
<div className="space-y-4">
331-
{(notification.content as Record<string, { subject?: string; message?: string }>).email?.subject && (
332-
<div>
333-
<span className="text-sm font-medium">Subject:</span>
334-
<p className="mt-1 text-sm">{String((notification.content as Record<string, { subject?: string }>).email?.subject)}</p>
354+
<CardContent className="space-y-4">
355+
{(() => {
356+
const providerMeta = getProviderMetadata(notification.channel, notification.provider);
357+
// Content may be nested under channel key or flat
358+
const channelContent = (notification.content as Record<string, unknown>)[notification.channel] as Record<string, unknown> | undefined
359+
|| notification.content as Record<string, unknown>;
360+
361+
// No schema available - fallback to JSON
362+
if (!providerMeta?.contentFields?.length) {
363+
return (
364+
<pre className="p-4 bg-muted rounded-lg text-xs overflow-auto">
365+
{JSON.stringify(notification.content, null, 2)}
366+
</pre>
367+
);
368+
}
369+
370+
// Render fields from schema
371+
return providerMeta.contentFields.map(field => {
372+
const value = channelContent[field.name];
373+
if (value === undefined || value === null || value === '') return null;
374+
375+
const stringValue = String(value);
376+
377+
return (
378+
<div key={field.name}>
379+
<span className="text-sm font-medium capitalize">
380+
{field.name.replace(/_/g, ' ')}:
381+
</span>
382+
{field.type === 'text' ? (
383+
// Long text/HTML content - styled box
384+
<div
385+
className="mt-2 p-4 bg-muted rounded-lg text-sm prose prose-sm max-w-none dark:prose-invert"
386+
dangerouslySetInnerHTML={{ __html: stringValue }}
387+
/>
388+
) : (
389+
// Short string content - inline
390+
<p className="mt-1 text-sm">{stringValue}</p>
391+
)}
335392
</div>
336-
)}
337-
<div>
338-
<span className="text-sm font-medium">Message:</span>
339-
<div
340-
className="mt-2 p-4 bg-muted rounded-lg text-sm"
341-
dangerouslySetInnerHTML={{ __html: String((notification.content as Record<string, { message?: string }>).email?.message || '') }}
342-
/>
343-
</div>
344-
</div>
345-
)}
346-
{notification.channel === "whatsapp" && (notification.content as Record<string, { message?: string }>).whatsapp && (
347-
<div>
348-
<span className="text-sm font-medium">Message:</span>
349-
<p className="mt-2 p-4 bg-muted rounded-lg text-sm whitespace-pre-wrap">
350-
{String((notification.content as Record<string, { message?: string }>).whatsapp?.message || '')}
351-
</p>
352-
</div>
353-
)}
354-
{/* Generic content fallback for other channels */}
355-
{notification.channel !== "email" && notification.channel !== "whatsapp" && (
356-
<div>
357-
<span className="text-sm font-medium">Content:</span>
358-
<pre className="mt-2 p-4 bg-muted rounded-lg text-xs overflow-auto">
359-
{JSON.stringify(notification.content, null, 2)}
360-
</pre>
361-
</div>
362-
)}
393+
);
394+
});
395+
})()}
363396
</CardContent>
364397
</Card>
365398

dashboard/app/events/page.tsx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export default function EventsPage() {
116116
<PageToolbar className="flex-col sm:flex-row gap-3">
117117
<PageToolbarSection className="w-full sm:w-auto sm:flex-1 sm:max-w-md">
118118
<Input
119-
placeholder="Search by request ID, email, phone..."
119+
placeholder="Search by request ID, recipient..."
120120
value={searchInput}
121121
onChange={(e) => setSearchInput(e.target.value)}
122122
onKeyDown={handleKeyDown}
@@ -239,7 +239,29 @@ export default function EventsPage() {
239239
<ChannelBadge channel={notification.channel} />
240240
</TableCell>
241241
<TableCell className="max-w-[200px] truncate">
242-
{String(notification.recipient.email || notification.recipient.phone || notification.recipient.user_id || '')}
242+
{(() => {
243+
// Get provider metadata for this notification's channel
244+
const channelMeta = pluginsData?.channels[notification.channel];
245+
const providerMeta = channelMeta?.providers.find(p => p.id === notification.provider)
246+
|| channelMeta?.providers.find(p => p.id === channelMeta.default)
247+
|| channelMeta?.providers[0];
248+
249+
// Get first meaningful value from schema fields (excluding user_id for display preference)
250+
if (providerMeta?.recipientFields) {
251+
const displayField = providerMeta.recipientFields.find(f =>
252+
f.name !== 'user_id' && notification.recipient[f.name]
253+
) || providerMeta.recipientFields[0];
254+
255+
if (displayField) {
256+
const value = notification.recipient[displayField.name];
257+
if (value !== undefined && value !== null && value !== '') {
258+
return String(value);
259+
}
260+
}
261+
}
262+
// Fallback to user_id
263+
return String(notification.recipient.user_id || '');
264+
})()}
243265
</TableCell>
244266
<TableCell>
245267
<StatusBadge status={notification.status} />

0 commit comments

Comments
 (0)