PlatformWebhooksDeliveryDetailsSheet.tsx204 lines · main
1import { Copy, RotateCcw } from 'lucide-react'
2import {
3 Badge,
4 Button,
5 Card,
6 CardContent,
7 Separator,
8 Sheet,
9 SheetContent,
10 SheetFooter,
11 SheetHeader,
12 SheetSection,
13 SheetTitle,
14 Tabs_Shadcn_ as Tabs,
15 TabsContent_Shadcn_ as TabsContent,
16 TabsList_Shadcn_ as TabsList,
17 TabsTrigger_Shadcn_ as TabsTrigger,
18} from 'ui'
19import { TimestampInfo } from 'ui-patterns'
20
21import type { WebhookDelivery } from './PlatformWebhooks.types'
22import { formatDeliveryStatus, statusBadgeVariant } from './PlatformWebhooksView.utils'
23import { getStatusLevel } from '@/components/interfaces/UnifiedLogs/UnifiedLogs.utils'
24import { DataTableColumnStatusCode } from '@/components/ui/DataTable/DataTableColumn/DataTableColumnStatusCode'
25
26interface PlatformWebhooksDeliveryDetailsSheetProps {
27 deliveryAttempt: number | null
28 deliveryDetailsTab: 'event' | 'response'
29 deliveryEventPayload: string
30 deliveryResponsePayload: string
31 open: boolean
32 selectedDelivery: WebhookDelivery | null
33 onCopy: (value: string, label: string) => void
34 onOpenChange: (open: boolean) => void
35 onRetryDelivery: (deliveryId: string) => void
36 onTabChange: (tab: 'event' | 'response') => void
37}
38
39export const PlatformWebhooksDeliveryDetailsSheet = ({
40 deliveryAttempt,
41 deliveryDetailsTab,
42 deliveryEventPayload,
43 deliveryResponsePayload,
44 open,
45 selectedDelivery,
46 onCopy,
47 onOpenChange,
48 onRetryDelivery,
49 onTabChange,
50}: PlatformWebhooksDeliveryDetailsSheetProps) => {
51 const retryableDelivery =
52 selectedDelivery && selectedDelivery.status !== 'success' ? selectedDelivery : null
53
54 return (
55 <Sheet open={open} onOpenChange={onOpenChange}>
56 <SheetContent size="default" className="flex flex-col gap-0">
57 <SheetHeader>
58 <div className="flex items-center gap-2">
59 <SheetTitle>Delivery details</SheetTitle>
60 {selectedDelivery && (
61 <Badge variant={statusBadgeVariant[selectedDelivery.status]}>
62 {formatDeliveryStatus(selectedDelivery.status)}
63 </Badge>
64 )}
65 </div>
66 </SheetHeader>
67 <Separator />
68
69 {selectedDelivery && (
70 <SheetSection className="overflow-auto grow px-0 py-0">
71 <div className="space-y-6 p-5">
72 <Card>
73 <CardContent className="grid grid-cols-1 gap-4 p-4 @md:grid-cols-2">
74 <div className="space-y-1">
75 <p className="text-sm text-foreground-light">Delivery ID</p>
76 <code className="text-code-inline">{selectedDelivery.id}</code>
77 </div>
78 <div className="space-y-1">
79 <p className="text-sm text-foreground-light">Attempt</p>
80 <p className="text-sm text-foreground">
81 {deliveryAttempt ? `#${deliveryAttempt}` : '-'}
82 </p>
83 </div>
84 <div className="space-y-1">
85 <p className="text-sm text-foreground-light">Timestamp</p>
86 <TimestampInfo className="text-sm" utcTimestamp={selectedDelivery.attemptAt} />
87 </div>
88 <div className="space-y-1">
89 <p className="text-sm text-foreground-light">Response code</p>
90 {selectedDelivery.responseCode ? (
91 <DataTableColumnStatusCode
92 value={selectedDelivery.responseCode}
93 level={getStatusLevel(selectedDelivery.responseCode)}
94 className="text-xs"
95 />
96 ) : (
97 <span className="text-xs text-foreground-muted">–</span>
98 )}
99 </div>
100 </CardContent>
101 </Card>
102
103 <Tabs
104 value={deliveryDetailsTab}
105 onValueChange={(value) => onTabChange(value as 'event' | 'response')}
106 >
107 <TabsList className="w-full">
108 <TabsTrigger value="event" className="flex-1">
109 Event
110 </TabsTrigger>
111 <TabsTrigger value="response" className="flex-1">
112 Response
113 </TabsTrigger>
114 </TabsList>
115
116 <TabsContent value="event" className="space-y-4 pt-4">
117 <div className="space-y-1">
118 <p className="text-sm text-foreground-light">Event type</p>
119 <code className="text-code-inline">{selectedDelivery.eventType}</code>
120 </div>
121 <div className="space-y-1">
122 <p className="text-sm text-foreground-light">Event ID</p>
123 <code className="text-code-inline">{selectedDelivery.id}</code>
124 </div>
125 <div className="space-y-1">
126 <p className="text-sm text-foreground-light">Event timestamp</p>
127 <TimestampInfo className="text-sm" utcTimestamp={selectedDelivery.attemptAt} />
128 </div>
129 <div className="space-y-2">
130 <div className="flex items-center justify-between gap-2">
131 <p className="text-sm text-foreground-light">Payload</p>
132 <Button
133 type="text"
134 icon={<Copy size={14} />}
135 onClick={() => onCopy(deliveryEventPayload, 'event payload')}
136 >
137 Copy
138 </Button>
139 </div>
140 <div className="rounded-md border border-default bg-surface-200 p-3">
141 <pre className="whitespace-pre-wrap text-xs text-foreground">
142 {deliveryEventPayload}
143 </pre>
144 </div>
145 </div>
146 </TabsContent>
147
148 <TabsContent value="response" className="space-y-4 pt-4">
149 <div className="space-y-1">
150 <p className="text-sm text-foreground-light">Status</p>
151 <Badge variant={statusBadgeVariant[selectedDelivery.status]}>
152 {formatDeliveryStatus(selectedDelivery.status)}
153 </Badge>
154 </div>
155 <div className="space-y-1">
156 <p className="text-sm text-foreground-light">Response code</p>
157 {selectedDelivery.responseCode ? (
158 <DataTableColumnStatusCode
159 value={selectedDelivery.responseCode}
160 level={getStatusLevel(selectedDelivery.responseCode)}
161 className="text-xs"
162 />
163 ) : (
164 <span className="text-xs text-foreground-muted">–</span>
165 )}
166 </div>
167 <div className="space-y-2">
168 <div className="flex items-center justify-between gap-2">
169 <p className="text-sm text-foreground-light">Response payload</p>
170 <Button
171 type="text"
172 icon={<Copy size={14} />}
173 onClick={() => onCopy(deliveryResponsePayload, 'response payload')}
174 >
175 Copy
176 </Button>
177 </div>
178 <div className="rounded-md border border-default bg-surface-200 p-3">
179 <pre className="whitespace-pre-wrap text-xs text-foreground">
180 {deliveryResponsePayload}
181 </pre>
182 </div>
183 </div>
184 </TabsContent>
185 </Tabs>
186 </div>
187 </SheetSection>
188 )}
189
190 {retryableDelivery && (
191 <SheetFooter className="shrink-0">
192 <Button
193 type="default"
194 icon={<RotateCcw />}
195 onClick={() => onRetryDelivery(retryableDelivery.id)}
196 >
197 Retry delivery
198 </Button>
199 </SheetFooter>
200 )}
201 </SheetContent>
202 </Sheet>
203 )
204}