+
+
+ {isOpen && (
+ <>
+ {/* Backdrop */}
+
setIsOpen(false)}
+ />
+
+ {/* Panel */}
+
+ {/* Header */}
+
+
+
+
+ Visual Payload Builder
+
+
+ Build your payload using form fields
+
+
+
+
+
+
+ {/* Form */}
+
+ {Object.entries(schema.schema.properties).map(([name, property]) => {
+ // Handle nested objects specially
+ if (property.type === 'object' && property.properties) {
+ return (
+
+
+ {name}
+
+ object
+
+
+ {Object.entries(property.properties).map(([childName, childProperty]) => (
+
handleNestedFieldChange(name, childName, value)}
+ />
+ ))}
+
+ );
+ }
+
+ return (
+
handleFieldChange(name, value)}
+ />
+ );
+ })}
+
+
+ {/* Footer */}
+
+
+
+ {schema.schema.properties.timestamp && (
+
+ )}
+
+
+
+
+ >
+ )}
+
+ );
+}
diff --git a/admin/app/webhook-tester/components/PayloadEditor.tsx b/admin/app/webhook-tester/components/PayloadEditor.tsx
index aa0c9852..df4165aa 100644
--- a/admin/app/webhook-tester/components/PayloadEditor.tsx
+++ b/admin/app/webhook-tester/components/PayloadEditor.tsx
@@ -1,8 +1,13 @@
'use client';
-import React, { useRef } from 'react';
+import React, { useRef, useState, useEffect } from 'react';
import { useWebhookTester } from '../context';
import { DEFAULT_PAYLOAD } from '../types';
+import { TemplateSelector } from './TemplateSelector';
+import { SchemaValidator } from './SchemaValidator';
+import { SchemaViewer } from './SchemaViewer';
+import { PayloadBuilder } from './PayloadBuilder';
+import { getSchemaByEventType, type PayloadSchema } from '../schemas';
// Minimal syntax highlighting for JSON in a textarea overlay approach
function highlight(json: string): string {
@@ -36,6 +41,22 @@ export function PayloadEditor() {
} = useWebhookTester();
const textareaRef = useRef
(null);
+ const [selectedSchema, setSelectedSchema] = useState();
+ const [validationErrors, setValidationErrors] = useState([]);
+ const [showValidation, setShowValidation] = useState(true);
+
+ // Auto-detect schema from payload
+ useEffect(() => {
+ try {
+ const parsed = JSON.parse(payload);
+ if (parsed.event_type) {
+ const schema = getSchemaByEventType(parsed.event_type);
+ setSelectedSchema(schema);
+ }
+ } catch {
+ // Invalid JSON, ignore
+ }
+ }, [payload]);
const handleFormat = () => {
try {
@@ -49,6 +70,15 @@ export function PayloadEditor() {
setPayload(DEFAULT_PAYLOAD);
};
+ const handleTemplateSelect = (template: PayloadSchema) => {
+ setPayload(JSON.stringify(template.example, null, 2));
+ setSelectedSchema(template);
+ };
+
+ const handleValidationChange = (valid: boolean, errors: string[]) => {
+ setValidationErrors(errors);
+ };
+
// Sync scroll between textarea and highlight overlay
const handleScroll = (e: React.UIEvent) => {
const pre = e.currentTarget.previousElementSibling as HTMLElement | null;
@@ -64,30 +94,100 @@ export function PayloadEditor() {
- Payload
+ Payload Editor
- {payloadError && (
-
- ✗ {payloadError}
+ {selectedSchema && (
+
+ {selectedSchema.eventType}
)}
- {!payloadError && payload.trim() && (
- ✓ valid JSON
- )}
+
+ {selectedSchema && (
+ <>
+
+
+ >
+ )}
+
+
+
+
+
+ {/* Status bar */}
+
+
+ {payloadError && (
+
+
+ {payloadError}
+
+ )}
+ {!payloadError && payload.trim() && (
+
+
+ Valid JSON
+
+ )}
+ {showValidation && selectedSchema && validationErrors.length === 0 && !payloadError && (
+
+
+ Schema Valid
+
+ )}
+
+
+ {payload.length} characters • {payload.split('\n').length} lines
@@ -110,6 +210,15 @@ export function PayloadEditor() {
/>
+ {/* Schema validation panel */}
+ {showValidation && selectedSchema && (
+
{selectedWebhook && (
diff --git a/admin/app/webhook-tester/components/SchemaValidator.tsx b/admin/app/webhook-tester/components/SchemaValidator.tsx
new file mode 100644
index 00000000..24aff550
--- /dev/null
+++ b/admin/app/webhook-tester/components/SchemaValidator.tsx
@@ -0,0 +1,80 @@
+'use client';
+
+import React from 'react';
+import { validatePayload, type PayloadSchema } from '../schemas';
+
+interface SchemaValidatorProps {
+ payload: string;
+ schema?: PayloadSchema;
+ onValidationChange?: (valid: boolean, errors: string[]) => void;
+}
+
+export function SchemaValidator({ payload, schema, onValidationChange }: SchemaValidatorProps) {
+ const validation = React.useMemo(() => {
+ return validatePayload(payload, schema);
+ }, [payload, schema]);
+
+ React.useEffect(() => {
+ onValidationChange?.(validation.valid, validation.errors);
+ }, [validation, onValidationChange]);
+
+ if (!schema) {
+ return null;
+ }
+
+ return (
+
+
+ {validation.valid ? (
+ <>
+
+
+
+ Valid {schema.name} payload
+
+
+ All required fields present and types match
+
+
+ >
+ ) : (
+ <>
+
+
+
+ {validation.errors.length} validation error{validation.errors.length !== 1 ? 's' : ''}
+
+
+ {validation.errors.map((error, idx) => (
+ -
+ • {error}
+
+ ))}
+
+
+ >
+ )}
+
+
+ );
+}
diff --git a/admin/app/webhook-tester/components/SchemaViewer.tsx b/admin/app/webhook-tester/components/SchemaViewer.tsx
new file mode 100644
index 00000000..36bf49f1
--- /dev/null
+++ b/admin/app/webhook-tester/components/SchemaViewer.tsx
@@ -0,0 +1,186 @@
+'use client';
+
+import React, { useState } from 'react';
+import { type PayloadSchema, type SchemaProperty } from '../schemas';
+
+interface SchemaViewerProps {
+ schema: PayloadSchema;
+}
+
+function PropertyRow({ name, property, level = 0 }: { name: string; property: SchemaProperty; level?: number }) {
+ const [isExpanded, setIsExpanded] = useState(level < 2);
+ const hasChildren = property.type === 'object' && property.properties;
+
+ return (
+
+
hasChildren && setIsExpanded(!isExpanded)}
+ style={{ paddingLeft: `${level * 1 + 0.5}rem` }}
+ >
+ {hasChildren && (
+
+ )}
+ {!hasChildren &&
}
+
+
+
+ {name}
+
+ {property.type}
+
+ {property.required && (
+
+ required
+
+ )}
+ {property.format && (
+
+ {property.format}
+
+ )}
+
+ {property.description && (
+
{property.description}
+ )}
+ {property.example !== undefined && (
+
+ Example:
+
+ {JSON.stringify(property.example)}
+
+
+ )}
+ {property.enum && (
+
+ Values:
+
+ {property.enum.join(' | ')}
+
+
+ )}
+
+
+
+ {hasChildren && isExpanded && property.properties && (
+
+ {Object.entries(property.properties).map(([childName, childProp]) => (
+
+ ))}
+
+ )}
+
+ );
+}
+
+export function SchemaViewer({ schema }: SchemaViewerProps) {
+ const [isOpen, setIsOpen] = useState(false);
+
+ return (
+
+
+
+ {isOpen && (
+ <>
+ {/* Backdrop */}
+
setIsOpen(false)}
+ />
+
+ {/* Panel */}
+
+ {/* Header */}
+
+
+
+
+ {schema.name}
+
+
+ {schema.description}
+
+
+
+
+
+
+ {/* Properties */}
+
+ {Object.entries(schema.schema.properties).map(([name, property]) => (
+
+ ))}
+
+
+ {/* Footer */}
+
+
+
+ {Object.keys(schema.schema.properties).length} properties
+
+
+ {schema.schema.required.length} required
+
+
+
+
+ >
+ )}
+
+ );
+}
diff --git a/admin/app/webhook-tester/components/TemplateSelector.tsx b/admin/app/webhook-tester/components/TemplateSelector.tsx
new file mode 100644
index 00000000..6a374f00
--- /dev/null
+++ b/admin/app/webhook-tester/components/TemplateSelector.tsx
@@ -0,0 +1,131 @@
+'use client';
+
+import React, { useState } from 'react';
+import { WEBHOOK_SCHEMAS, type PayloadSchema } from '../schemas';
+
+interface TemplateSelectorProps {
+ onSelectTemplate: (template: PayloadSchema) => void;
+ currentEventType?: string;
+}
+
+export function TemplateSelector({ onSelectTemplate, currentEventType }: TemplateSelectorProps) {
+ const [isOpen, setIsOpen] = useState(false);
+ const [searchQuery, setSearchQuery] = useState('');
+
+ const filteredSchemas = WEBHOOK_SCHEMAS.filter(
+ (schema) =>
+ schema.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ schema.eventType.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ schema.description.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+
+ const handleSelect = (schema: PayloadSchema) => {
+ onSelectTemplate(schema);
+ setIsOpen(false);
+ setSearchQuery('');
+ };
+
+ return (
+
+
+
+ {isOpen && (
+ <>
+ {/* Backdrop */}
+
setIsOpen(false)}
+ />
+
+ {/* Dropdown */}
+
+ {/* Search */}
+
+ setSearchQuery(e.target.value)}
+ placeholder="Search templates..."
+ className="w-full px-3 py-2 bg-zinc-950 border border-zinc-700 rounded text-sm text-zinc-200 placeholder-zinc-500 focus:outline-none focus:border-blue-500"
+ autoFocus
+ />
+
+
+ {/* Template list */}
+
+ {filteredSchemas.length === 0 ? (
+
+ No templates found
+
+ ) : (
+ filteredSchemas.map((schema) => (
+
+ ))
+ )}
+
+
+ {/* Footer */}
+
+
+ {filteredSchemas.length} template{filteredSchemas.length !== 1 ? 's' : ''} available
+
+
+
+ >
+ )}
+
+ );
+}
diff --git a/admin/app/webhook-tester/schemas.ts b/admin/app/webhook-tester/schemas.ts
new file mode 100644
index 00000000..346c37ad
--- /dev/null
+++ b/admin/app/webhook-tester/schemas.ts
@@ -0,0 +1,379 @@
+// Webhook Payload Schemas and Validation
+
+export interface SchemaProperty {
+ type: string;
+ description?: string;
+ example?: any;
+ required?: boolean;
+ enum?: string[];
+ format?: string;
+ items?: SchemaProperty;
+ properties?: Record
;
+}
+
+export interface PayloadSchema {
+ name: string;
+ description: string;
+ eventType: string;
+ schema: {
+ type: 'object';
+ properties: Record;
+ required: string[];
+ };
+ example: Record;
+}
+
+// Predefined schemas for common event types
+export const WEBHOOK_SCHEMAS: PayloadSchema[] = [
+ {
+ name: 'Test Event',
+ description: 'Simple test event for webhook validation',
+ eventType: 'test',
+ schema: {
+ type: 'object',
+ properties: {
+ event_type: {
+ type: 'string',
+ description: 'Type of the event',
+ example: 'test',
+ },
+ payload: {
+ type: 'object',
+ description: 'Event payload data',
+ properties: {
+ message: {
+ type: 'string',
+ description: 'Test message',
+ example: 'This is a test webhook',
+ },
+ },
+ },
+ contract_id: {
+ type: 'string',
+ description: 'Contract identifier',
+ example: 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM',
+ },
+ timestamp: {
+ type: 'string',
+ format: 'date-time',
+ description: 'Event timestamp',
+ example: new Date().toISOString(),
+ },
+ },
+ required: ['event_type', 'payload', 'contract_id', 'timestamp'],
+ },
+ example: {
+ event_type: 'test',
+ payload: { message: 'This is a test webhook' },
+ contract_id: 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM',
+ timestamp: new Date().toISOString(),
+ },
+ },
+ {
+ name: 'Transfer Event',
+ description: 'Token transfer event',
+ eventType: 'transfer',
+ schema: {
+ type: 'object',
+ properties: {
+ event_type: {
+ type: 'string',
+ description: 'Type of the event',
+ example: 'transfer',
+ },
+ payload: {
+ type: 'object',
+ description: 'Transfer details',
+ properties: {
+ from: {
+ type: 'string',
+ description: 'Sender address',
+ example: 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF',
+ },
+ to: {
+ type: 'string',
+ description: 'Recipient address',
+ example: 'GBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBWHF',
+ },
+ amount: {
+ type: 'string',
+ description: 'Transfer amount',
+ example: '1000000',
+ },
+ asset: {
+ type: 'string',
+ description: 'Asset code',
+ example: 'USDC',
+ },
+ },
+ },
+ contract_id: {
+ type: 'string',
+ description: 'Contract identifier',
+ example: 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM',
+ },
+ timestamp: {
+ type: 'string',
+ format: 'date-time',
+ description: 'Event timestamp',
+ example: new Date().toISOString(),
+ },
+ ledger: {
+ type: 'number',
+ description: 'Ledger sequence number',
+ example: 12345678,
+ },
+ transaction_hash: {
+ type: 'string',
+ description: 'Transaction hash',
+ example: 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6',
+ },
+ },
+ required: ['event_type', 'payload', 'contract_id', 'timestamp'],
+ },
+ example: {
+ event_type: 'transfer',
+ payload: {
+ from: 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF',
+ to: 'GBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBWHF',
+ amount: '1000000',
+ asset: 'USDC',
+ },
+ contract_id: 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM',
+ timestamp: new Date().toISOString(),
+ ledger: 12345678,
+ transaction_hash: 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6',
+ },
+ },
+ {
+ name: 'Mint Event',
+ description: 'Token minting event',
+ eventType: 'mint',
+ schema: {
+ type: 'object',
+ properties: {
+ event_type: {
+ type: 'string',
+ description: 'Type of the event',
+ example: 'mint',
+ },
+ payload: {
+ type: 'object',
+ description: 'Mint details',
+ properties: {
+ to: {
+ type: 'string',
+ description: 'Recipient address',
+ example: 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF',
+ },
+ amount: {
+ type: 'string',
+ description: 'Minted amount',
+ example: '5000000',
+ },
+ asset: {
+ type: 'string',
+ description: 'Asset code',
+ example: 'TOKEN',
+ },
+ },
+ },
+ contract_id: {
+ type: 'string',
+ description: 'Contract identifier',
+ example: 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM',
+ },
+ timestamp: {
+ type: 'string',
+ format: 'date-time',
+ description: 'Event timestamp',
+ example: new Date().toISOString(),
+ },
+ ledger: {
+ type: 'number',
+ description: 'Ledger sequence number',
+ example: 12345678,
+ },
+ },
+ required: ['event_type', 'payload', 'contract_id', 'timestamp'],
+ },
+ example: {
+ event_type: 'mint',
+ payload: {
+ to: 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF',
+ amount: '5000000',
+ asset: 'TOKEN',
+ },
+ contract_id: 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM',
+ timestamp: new Date().toISOString(),
+ ledger: 12345678,
+ },
+ },
+ {
+ name: 'Burn Event',
+ description: 'Token burning event',
+ eventType: 'burn',
+ schema: {
+ type: 'object',
+ properties: {
+ event_type: {
+ type: 'string',
+ description: 'Type of the event',
+ example: 'burn',
+ },
+ payload: {
+ type: 'object',
+ description: 'Burn details',
+ properties: {
+ from: {
+ type: 'string',
+ description: 'Burner address',
+ example: 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF',
+ },
+ amount: {
+ type: 'string',
+ description: 'Burned amount',
+ example: '2000000',
+ },
+ asset: {
+ type: 'string',
+ description: 'Asset code',
+ example: 'TOKEN',
+ },
+ },
+ },
+ contract_id: {
+ type: 'string',
+ description: 'Contract identifier',
+ example: 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM',
+ },
+ timestamp: {
+ type: 'string',
+ format: 'date-time',
+ description: 'Event timestamp',
+ example: new Date().toISOString(),
+ },
+ },
+ required: ['event_type', 'payload', 'contract_id', 'timestamp'],
+ },
+ example: {
+ event_type: 'burn',
+ payload: {
+ from: 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF',
+ amount: '2000000',
+ asset: 'TOKEN',
+ },
+ contract_id: 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM',
+ timestamp: new Date().toISOString(),
+ },
+ },
+ {
+ name: 'Custom Event',
+ description: 'Custom event with flexible structure',
+ eventType: 'custom',
+ schema: {
+ type: 'object',
+ properties: {
+ event_type: {
+ type: 'string',
+ description: 'Type of the event',
+ example: 'custom',
+ },
+ payload: {
+ type: 'object',
+ description: 'Custom payload data',
+ properties: {},
+ },
+ contract_id: {
+ type: 'string',
+ description: 'Contract identifier',
+ example: 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM',
+ },
+ timestamp: {
+ type: 'string',
+ format: 'date-time',
+ description: 'Event timestamp',
+ example: new Date().toISOString(),
+ },
+ },
+ required: ['event_type', 'payload', 'contract_id', 'timestamp'],
+ },
+ example: {
+ event_type: 'custom',
+ payload: {},
+ contract_id: 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM',
+ timestamp: new Date().toISOString(),
+ },
+ },
+];
+
+// Validation function
+export function validatePayload(
+ payload: string,
+ schema?: PayloadSchema
+): { valid: boolean; errors: string[] } {
+ const errors: string[] = [];
+
+ // Parse JSON
+ let parsed: any;
+ try {
+ parsed = JSON.parse(payload);
+ } catch (e) {
+ return { valid: false, errors: ['Invalid JSON syntax'] };
+ }
+
+ // Basic validation
+ if (typeof parsed !== 'object' || parsed === null) {
+ errors.push('Payload must be a JSON object');
+ return { valid: false, errors };
+ }
+
+ // Schema validation if provided
+ if (schema) {
+ const { required, properties } = schema.schema;
+
+ // Check required fields
+ for (const field of required) {
+ if (!(field in parsed)) {
+ errors.push(`Missing required field: ${field}`);
+ }
+ }
+
+ // Validate field types
+ for (const [key, prop] of Object.entries(properties)) {
+ if (key in parsed) {
+ const value = parsed[key];
+ const expectedType = prop.type;
+
+ if (expectedType === 'string' && typeof value !== 'string') {
+ errors.push(`Field "${key}" must be a string`);
+ } else if (expectedType === 'number' && typeof value !== 'number') {
+ errors.push(`Field "${key}" must be a number`);
+ } else if (expectedType === 'boolean' && typeof value !== 'boolean') {
+ errors.push(`Field "${key}" must be a boolean`);
+ } else if (expectedType === 'object' && (typeof value !== 'object' || value === null)) {
+ errors.push(`Field "${key}" must be an object`);
+ } else if (expectedType === 'array' && !Array.isArray(value)) {
+ errors.push(`Field "${key}" must be an array`);
+ }
+
+ // Validate enum values
+ if (prop.enum && !prop.enum.includes(value)) {
+ errors.push(`Field "${key}" must be one of: ${prop.enum.join(', ')}`);
+ }
+ }
+ }
+ }
+
+ return { valid: errors.length === 0, errors };
+}
+
+// Get schema by event type
+export function getSchemaByEventType(eventType: string): PayloadSchema | undefined {
+ return WEBHOOK_SCHEMAS.find((s) => s.eventType === eventType);
+}
+
+// Get all available event types
+export function getAvailableEventTypes(): string[] {
+ return WEBHOOK_SCHEMAS.map((s) => s.eventType);
+}
diff --git a/admin/package-lock.json b/admin/package-lock.json
index aca3e6ea..6bb1434c 100644
--- a/admin/package-lock.json
+++ b/admin/package-lock.json
@@ -10,7 +10,8 @@
"dependencies": {
"next": "16.1.6",
"react": "19.2.3",
- "react-dom": "19.2.3"
+ "react-dom": "19.2.3",
+ "recharts": "^2.12.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
@@ -228,6 +229,15 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@babel/runtime": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
+ "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/template": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
@@ -1524,6 +1534,69 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@types/d3-array": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
+ "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-color": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
+ "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-ease": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
+ "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-interpolate": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+ "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-color": "*"
+ }
+ },
+ "node_modules/@types/d3-path": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
+ "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-scale": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
+ "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-time": "*"
+ }
+ },
+ "node_modules/@types/d3-shape": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz",
+ "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-path": "*"
+ }
+ },
+ "node_modules/@types/d3-time": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
+ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-timer": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
+ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
+ "license": "MIT"
+ },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -2605,6 +2678,15 @@
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -2658,9 +2740,129 @@
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "dev": true,
"license": "MIT"
},
+ "node_modules/d3-array": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
+ "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+ "license": "ISC",
+ "dependencies": {
+ "internmap": "1 - 2"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-color": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
+ "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-ease": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
+ "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-format": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
+ "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-interpolate": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+ "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-path": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+ "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-scale": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
+ "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2.10.0 - 3",
+ "d3-format": "1 - 3",
+ "d3-interpolate": "1.2.0 - 3",
+ "d3-time": "2.1.1 - 3",
+ "d3-time-format": "2 - 4"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-shape": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+ "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-path": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
+ "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time-format": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
+ "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-time": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-timer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
+ "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@@ -2740,6 +2942,12 @@
}
}
},
+ "node_modules/decimal.js-light": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
+ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
+ "license": "MIT"
+ },
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -2806,6 +3014,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -3480,6 +3698,12 @@
"node": ">=0.10.0"
}
},
+ "node_modules/eventemitter3": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
+ "license": "MIT"
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -3487,6 +3711,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fast-equals": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz",
+ "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/fast-glob": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
@@ -3978,6 +4211,15 @@
"node": ">= 0.4"
}
},
+ "node_modules/internmap": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
+ "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -4439,7 +4681,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true,
"license": "MIT"
},
"node_modules/js-yaml": {
@@ -4839,6 +5080,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/lodash": {
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
+ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
+ "license": "MIT"
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -4850,7 +5097,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "dev": true,
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
@@ -5095,7 +5341,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -5394,7 +5639,6 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.4.0",
@@ -5458,7 +5702,76 @@
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/react-smooth": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
+ "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-equals": "^5.0.1",
+ "prop-types": "^15.8.1",
+ "react-transition-group": "^4.4.5"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
+ "node_modules/recharts": {
+ "version": "2.15.4",
+ "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz",
+ "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==",
+ "deprecated": "1.x and 2.x branches are no longer active. Bump to Recharts v3 to receive latest features and bugfixes. See https://github.com/recharts/recharts/wiki/3.0-migration-guide",
+ "license": "MIT",
+ "dependencies": {
+ "clsx": "^2.0.0",
+ "eventemitter3": "^4.0.1",
+ "lodash": "^4.17.21",
+ "react-is": "^18.3.1",
+ "react-smooth": "^4.0.4",
+ "recharts-scale": "^0.4.4",
+ "tiny-invariant": "^1.3.1",
+ "victory-vendor": "^36.6.8"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/recharts-scale": {
+ "version": "0.4.5",
+ "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
+ "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
+ "license": "MIT",
+ "dependencies": {
+ "decimal.js-light": "^2.4.1"
+ }
+ },
+ "node_modules/recharts/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"license": "MIT"
},
"node_modules/reflect.getprototypeof": {
@@ -6094,6 +6407,12 @@
"url": "https://opencollective.com/webpack"
}
},
+ "node_modules/tiny-invariant": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
+ "license": "MIT"
+ },
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@@ -6431,6 +6750,28 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/victory-vendor": {
+ "version": "36.9.2",
+ "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
+ "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
+ "license": "MIT AND ISC",
+ "dependencies": {
+ "@types/d3-array": "^3.0.3",
+ "@types/d3-ease": "^3.0.0",
+ "@types/d3-interpolate": "^3.0.1",
+ "@types/d3-scale": "^4.0.2",
+ "@types/d3-shape": "^3.1.0",
+ "@types/d3-time": "^3.0.0",
+ "@types/d3-timer": "^3.0.0",
+ "d3-array": "^3.1.6",
+ "d3-ease": "^3.0.1",
+ "d3-interpolate": "^3.0.1",
+ "d3-scale": "^4.0.2",
+ "d3-shape": "^3.1.0",
+ "d3-time": "^3.0.0",
+ "d3-timer": "^3.0.1"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",