@@ -6,7 +6,7 @@ import { Button } from '@/components/ui';
66import { Input } from '@/components/ui' ;
77import { Skeleton } from '@/components/ui' ;
88import { NetworkTooltip } from '@/components/ui' ;
9- import { Copy , Eye , EyeOff , Plus , RefreshCcw , Key , Globe , BookOpen , Zap , CheckCircle2 , AlertCircle , Code2 , Terminal , Trash2 } from 'lucide-react' ;
9+ import { Copy , Eye , EyeOff , Plus , Key , Globe , BookOpen , Code2 , Terminal , Trash2 } from 'lucide-react' ;
1010import { useNotify } from '@/lib/hooks/useNotify' ;
1111import {
1212 Select ,
@@ -19,6 +19,7 @@ import { useOfflineStore } from '@/lib/store/offlineStore';
1919import { Dialog , DialogContent , DialogHeader , DialogTitle , DialogDescription , DialogFooter } from '@/components/ui' ;
2020import { Label } from '@/components/ui' ;
2121import { RateLimitDisplay } from '@/components/developers/RateLimitDisplay' ;
22+ import { WebhookTester } from '@/components/developers/WebhookTester' ;
2223
2324const CodeExample = dynamic ( ( ) => import ( '@/components/developers/CodeExample' ) . then ( m => ( { default : m . CodeExample } ) ) , {
2425 loading : ( ) => < Skeleton className = "h-64 rounded-xl" /> ,
@@ -29,61 +30,9 @@ const initialKeys = [
2930 { id : 'key_02' , name : 'Sandbox Key' , prefix : 'bp_test_' , suffix : '...c2d8' , created : '2026-01-05' , lastUsed : '5 days ago' , type : 'test' , scope : 'read_write' } ,
3031] ;
3132
32- const EVENT_TYPES = [
33- { value : 'payment.received' , label : 'payment.received' } ,
34- { value : 'settlement.completed' , label : 'settlement.completed' } ,
35- { value : 'payment.failed' , label : 'payment.failed' } ,
36- ] ;
37-
38- type JsonValue = string | number | boolean | null | JsonValue [ ] | { [ key : string ] : JsonValue } ;
39-
40- const SAMPLE_PAYLOADS : Record < string , JsonValue > = {
41- 'payment.received' : {
42- "id" : "evt_123456" ,
43- "type" : "payment.received" ,
44- "data" : {
45- "payment_id" : "pay_987654" ,
46- "amount" : 5000 ,
47- "currency" : "USDC" ,
48- "status" : "completed" ,
49- "customer" : {
50- "email" : "customer@example.com"
51- }
52- } ,
53- "created_at" : "2026-07-23T12:00:00Z"
54- } ,
55- 'settlement.completed' : {
56- "id" : "evt_234567" ,
57- "type" : "settlement.completed" ,
58- "data" : {
59- "settlement_id" : "set_112233" ,
60- "amount" : 4900 ,
61- "currency" : "USDC" ,
62- "fee" : 100 ,
63- "status" : "processed"
64- } ,
65- "created_at" : "2026-07-23T13:00:00Z"
66- } ,
67- 'payment.failed' : {
68- "id" : "evt_345678" ,
69- "type" : "payment.failed" ,
70- "data" : {
71- "payment_id" : "pay_failed_111" ,
72- "amount" : 5000 ,
73- "currency" : "USDC" ,
74- "status" : "failed" ,
75- "reason" : "insufficient_funds"
76- } ,
77- "created_at" : "2026-07-23T14:00:00Z"
78- }
79- } ;
80-
8133export default function DevelopersPage ( ) {
8234 const [ keys , setKeys ] = useState ( initialKeys ) ;
8335 const [ showKey , setShowKey ] = useState < string | null > ( null ) ;
84- const [ selectedEvent , setSelectedEvent ] = useState < string > ( 'payment.received' ) ;
85- const [ isSimulating , setIsSimulating ] = useState ( false ) ;
86- const [ simulationResult , setSimulationResult ] = useState < { status : number ; message : string } | null > ( null ) ;
8736 const isOnline = useOfflineStore ( ( s ) => s . isOnline ) ;
8837 const notify = useNotify ( ) ;
8938
@@ -123,20 +72,6 @@ export default function DevelopersPage() {
12372 notify . info ( 'API key revoked' ) ;
12473 } ;
12574
126- const handleSendTest = ( ) => {
127- setIsSimulating ( true ) ;
128- setSimulationResult ( null ) ;
129-
130- setTimeout ( ( ) => {
131- setIsSimulating ( false ) ;
132- setSimulationResult ( {
133- status : 200 ,
134- message : 'Webhook delivered successfully with signature verification pass'
135- } ) ;
136- notify . success ( 'Test webhook delivered' ) ;
137- } , 1200 ) ;
138- } ;
139-
14075 return (
14176 < div className = "space-y-8 pb-8" >
14277 < div >
@@ -269,87 +204,7 @@ export default function DevelopersPage() {
269204 </ CardContent >
270205 </ Card >
271206
272- { /* Test Webhook Simulator Section */ }
273- < Card className = "border border-border bg-card shadow-sm" >
274- < CardHeader >
275- < CardTitle className = "text-base font-semibold text-foreground flex items-center gap-2" >
276- < Zap className = "w-4 h-4 text-primary" /> Test Webhook Simulator
277- </ CardTitle >
278- < CardDescription >
279- Simulate webhook events to verify signature verification and payload handlers.
280- </ CardDescription >
281- </ CardHeader >
282- < CardContent className = "space-y-6" >
283- < div className = "flex flex-col sm:flex-row gap-4 items-end" >
284- < div className = "flex-1 space-y-2" >
285- < label className = "text-xs font-semibold text-foreground" > Event Type</ label >
286- < Select value = { selectedEvent } onValueChange = { ( value ) => value && setSelectedEvent ( value ) } >
287- < SelectTrigger className = "w-full h-10 border-border rounded-xl bg-muted" >
288- < SelectValue placeholder = "Select event type" />
289- </ SelectTrigger >
290- < SelectContent >
291- { EVENT_TYPES . map ( ( type ) => (
292- < SelectItem key = { type . value } value = { type . value } >
293- { type . label }
294- </ SelectItem >
295- ) ) }
296- </ SelectContent >
297- </ Select >
298- </ div >
299- < Button
300- onClick = { handleSendTest }
301- disabled = { isSimulating }
302- className = "bg-foreground hover:bg-foreground/90 text-background rounded-xl h-10 px-6 text-sm font-semibold min-w-[140px]"
303- >
304- { isSimulating ? (
305- < >
306- < RefreshCcw className = "w-3.5 h-3.5 mr-2 animate-spin" />
307- Sending...
308- </ >
309- ) : (
310- 'Send Test'
311- ) }
312- </ Button >
313- </ div >
314-
315- < div className = "space-y-2" >
316- < div className = "flex items-center justify-between" >
317- < label className = "text-xs font-semibold text-foreground" > Simulated Payload</ label >
318- < Button
319- variant = "ghost"
320- size = "sm"
321- className = "min-h-[44px] text-xs text-muted-foreground hover:text-foreground"
322- onClick = { ( ) => handleCopy ( JSON . stringify ( SAMPLE_PAYLOADS [ selectedEvent ] , null , 2 ) ) }
323- >
324- < Copy className = "w-3 h-3 mr-1" /> Copy JSON
325- </ Button >
326- </ div >
327- < div className = "bg-foreground rounded-xl p-4 overflow-x-auto border border-border" >
328- < pre className = "text-xs text-emerald-400 font-mono leading-relaxed" >
329- { JSON . stringify ( SAMPLE_PAYLOADS [ selectedEvent ] , null , 2 ) }
330- </ pre >
331- </ div >
332- </ div >
333-
334- { simulationResult && (
335- < div className = { `p-4 rounded-xl border flex items-start gap-3 animate-in fade-in slide-in-from-top-2 duration-300 ${
336- simulationResult . status === 200
337- ? 'bg-success/10 border-success/20 text-success'
338- : 'bg-destructive/10 border-destructive/20 text-destructive'
339- } `} >
340- { simulationResult . status === 200 ? (
341- < CheckCircle2 className = "w-5 h-5 text-success shrink-0 mt-0.5" />
342- ) : (
343- < AlertCircle className = "w-5 h-5 text-destructive shrink-0 mt-0.5" />
344- ) }
345- < div >
346- < p className = "text-sm font-semibold" > Response Status: { simulationResult . status } OK</ p >
347- < p className = "text-xs opacity-80" > { simulationResult . message } </ p >
348- </ div >
349- </ div >
350- ) }
351- </ CardContent >
352- </ Card >
207+ < WebhookTester />
353208
354209 { /* New API Key Dialog */ }
355210 < Dialog open = { isCreateKeyOpen } onOpenChange = { setIsCreateKeyOpen } >
0 commit comments