dashboard design changes
This commit is contained in:
380
src/features/settings/ApiWebhooksManager.tsx
Normal file
380
src/features/settings/ApiWebhooksManager.tsx
Normal file
@@ -0,0 +1,380 @@
|
||||
'use client';
|
||||
|
||||
import {useState} from 'react';
|
||||
import {proportional, pixel} from '@astryxdesign/core/Table';
|
||||
import {ResponsiveTable} from '@/components/patterns/ResponsiveTable';
|
||||
import type {TableColumn} from '@astryxdesign/core/Table';
|
||||
import {VStack, HStack} from '@astryxdesign/core/Layout';
|
||||
import {Text} from '@astryxdesign/core/Text';
|
||||
import {Badge} from '@astryxdesign/core/Badge';
|
||||
import {Button} from '@astryxdesign/core/Button';
|
||||
import {TextInput} from '@astryxdesign/core/TextInput';
|
||||
import {useToast} from '@astryxdesign/core/Toast';
|
||||
import {StaticPanel} from '@/components/patterns/PanelCard';
|
||||
|
||||
export interface ApiKeyItem extends Record<string, unknown> {
|
||||
id: string;
|
||||
name: string;
|
||||
keyPrefix: string;
|
||||
created: string;
|
||||
lastUsed: string;
|
||||
status: 'active' | 'revoked';
|
||||
}
|
||||
|
||||
export interface WebhookEndpointItem extends Record<string, unknown> {
|
||||
id: string;
|
||||
url: string;
|
||||
events: string;
|
||||
secret: string;
|
||||
status: 'healthy' | 'failing';
|
||||
}
|
||||
|
||||
export interface WebhookLogItem extends Record<string, unknown> {
|
||||
id: string;
|
||||
event: string;
|
||||
url: string;
|
||||
code: number;
|
||||
latency: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
const INITIAL_KEYS: ApiKeyItem[] = [
|
||||
{
|
||||
id: 'key-1',
|
||||
name: 'Production POS Key',
|
||||
keyPrefix: 'lmer_live_9f8a••••••••4b12',
|
||||
created: '10 Jan 2026',
|
||||
lastUsed: 'Just now',
|
||||
status: 'active',
|
||||
},
|
||||
{
|
||||
id: 'key-2',
|
||||
name: 'Staging Integration Key',
|
||||
keyPrefix: 'lmer_test_3d2c••••••••8e91',
|
||||
created: '15 Jun 2026',
|
||||
lastUsed: 'Yesterday',
|
||||
status: 'active',
|
||||
},
|
||||
];
|
||||
|
||||
const INITIAL_WEBHOOKS: WebhookEndpointItem[] = [
|
||||
{
|
||||
id: 'wh-1',
|
||||
url: 'https://api.nearle.in/webhooks/loyaly',
|
||||
events: 'order.created, lyt.redeemed',
|
||||
secret: 'whsec_8841••••••••9932',
|
||||
status: 'healthy',
|
||||
},
|
||||
{
|
||||
id: 'wh-2',
|
||||
url: 'https://hooks.zapier.com/hooks/catch/19284',
|
||||
events: 'staff.checkin, store.updated',
|
||||
secret: 'whsec_7712••••••••1102',
|
||||
status: 'healthy',
|
||||
},
|
||||
];
|
||||
|
||||
const WEBHOOK_LOGS: WebhookLogItem[] = [
|
||||
{
|
||||
id: 'log-101',
|
||||
event: 'lyt.redeemed',
|
||||
url: 'https://api.nearle.in/webhooks/loyaly',
|
||||
code: 200,
|
||||
latency: '142ms',
|
||||
timestamp: 'Today, 15:42:01',
|
||||
},
|
||||
{
|
||||
id: 'log-102',
|
||||
event: 'order.created',
|
||||
url: 'https://api.nearle.in/webhooks/loyaly',
|
||||
code: 200,
|
||||
latency: '185ms',
|
||||
timestamp: 'Today, 15:38:12',
|
||||
},
|
||||
{
|
||||
id: 'log-103',
|
||||
event: 'staff.checkin',
|
||||
url: 'https://hooks.zapier.com/hooks/catch/19284',
|
||||
code: 200,
|
||||
latency: '95ms',
|
||||
timestamp: 'Today, 09:01:44',
|
||||
},
|
||||
];
|
||||
|
||||
export function ApiWebhooksManager() {
|
||||
const toast = useToast();
|
||||
const [keys, setKeys] = useState<ApiKeyItem[]>(INITIAL_KEYS);
|
||||
const [webhooks, setWebhooks] = useState<WebhookEndpointItem[]>(INITIAL_WEBHOOKS);
|
||||
const [newKeyName, setNewKeyName] = useState('');
|
||||
const [isGenerating, setIsGenerating] = useState(false);
|
||||
const [newWebhookUrl, setNewWebhookUrl] = useState('');
|
||||
|
||||
const handleGenerateKey = () => {
|
||||
if (!newKeyName.trim()) {
|
||||
toast({type: 'error', body: 'Key name is required'});
|
||||
return;
|
||||
}
|
||||
const createdKey: ApiKeyItem = {
|
||||
id: `key-${Date.now()}`,
|
||||
name: newKeyName,
|
||||
keyPrefix: `lmer_live_${Math.random().toString(36).substring(2, 6)}••••••••${Math.random().toString(36).substring(2, 6)}`,
|
||||
created: 'Just now',
|
||||
lastUsed: 'Never',
|
||||
status: 'active',
|
||||
};
|
||||
setKeys((prev) => [createdKey, ...prev]);
|
||||
setNewKeyName('');
|
||||
setIsGenerating(false);
|
||||
toast({body: `API Key "${newKeyName}" generated successfully`});
|
||||
};
|
||||
|
||||
const handleRevokeKey = (id: string, name: string) => {
|
||||
setKeys((prev) => prev.filter((k) => k.id !== id));
|
||||
toast({body: `Revoked API Key "${name}"`});
|
||||
};
|
||||
|
||||
const handleAddWebhook = () => {
|
||||
if (!newWebhookUrl.trim()) {
|
||||
toast({type: 'error', body: 'Webhook URL is required'});
|
||||
return;
|
||||
}
|
||||
const newWh: WebhookEndpointItem = {
|
||||
id: `wh-${Date.now()}`,
|
||||
url: newWebhookUrl,
|
||||
events: 'order.created, lyt.redeemed, staff.checkin',
|
||||
secret: `whsec_${Math.random().toString(36).substring(2, 8)}••••`,
|
||||
status: 'healthy',
|
||||
};
|
||||
setWebhooks((prev) => [newWh, ...prev]);
|
||||
setNewWebhookUrl('');
|
||||
toast({body: 'Webhook endpoint registered successfully'});
|
||||
};
|
||||
|
||||
const keyColumns: TableColumn<ApiKeyItem>[] = [
|
||||
{
|
||||
key: 'name',
|
||||
header: 'Key Name',
|
||||
width: proportional(1.5),
|
||||
renderCell: (row) => (
|
||||
<VStack gap={0}>
|
||||
<Text size="sm" weight="medium">
|
||||
{row.name}
|
||||
</Text>
|
||||
<Text size="sm" color="secondary">
|
||||
{row.keyPrefix}
|
||||
</Text>
|
||||
</VStack>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'created',
|
||||
header: 'Created Date',
|
||||
width: proportional(1.2),
|
||||
renderCell: (row) => <Text size="sm">{row.created}</Text>,
|
||||
},
|
||||
{
|
||||
key: 'lastUsed',
|
||||
header: 'Last Active',
|
||||
width: proportional(1.2),
|
||||
renderCell: (row) => (
|
||||
<Text size="sm" color="secondary">
|
||||
{row.lastUsed}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
header: 'Status',
|
||||
width: pixel(100),
|
||||
renderCell: (row) => (
|
||||
<Badge
|
||||
variant={row.status === 'active' ? 'success' : 'error'}
|
||||
label={row.status}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
header: 'Action',
|
||||
width: pixel(100),
|
||||
renderCell: (row) => (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
label="Revoke"
|
||||
onClick={() => handleRevokeKey(row.id, row.name)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const webhookColumns: TableColumn<WebhookEndpointItem>[] = [
|
||||
{
|
||||
key: 'url',
|
||||
header: 'Endpoint URL',
|
||||
width: proportional(2),
|
||||
renderCell: (row) => (
|
||||
<VStack gap={0}>
|
||||
<Text size="sm" weight="medium">
|
||||
{row.url}
|
||||
</Text>
|
||||
<Text size="sm" color="secondary">
|
||||
Events: {row.events}
|
||||
</Text>
|
||||
</VStack>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'secret',
|
||||
header: 'Signing Secret',
|
||||
width: proportional(1.2),
|
||||
renderCell: (row) => (
|
||||
<Text size="sm" color="secondary">
|
||||
{row.secret}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
header: 'Health',
|
||||
width: pixel(100),
|
||||
renderCell: (row) => (
|
||||
<Badge
|
||||
variant={row.status === 'healthy' ? 'success' : 'error'}
|
||||
label={row.status}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const logColumns: TableColumn<WebhookLogItem>[] = [
|
||||
{
|
||||
key: 'event',
|
||||
header: 'Event',
|
||||
width: proportional(1.2),
|
||||
renderCell: (row) => (
|
||||
<Text size="sm" weight="medium">
|
||||
{row.event}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'url',
|
||||
header: 'Target URL',
|
||||
width: proportional(2),
|
||||
renderCell: (row) => (
|
||||
<Text size="sm" color="secondary">
|
||||
{row.url}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'code',
|
||||
header: 'HTTP Code',
|
||||
width: pixel(100),
|
||||
renderCell: (row) => (
|
||||
<Badge
|
||||
variant={row.code === 200 ? 'success' : 'error'}
|
||||
label={`${row.code} OK`}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'latency',
|
||||
header: 'Latency',
|
||||
width: pixel(90),
|
||||
renderCell: (row) => <Text size="sm">{row.latency}</Text>,
|
||||
},
|
||||
{
|
||||
key: 'timestamp',
|
||||
header: 'Timestamp',
|
||||
width: proportional(1.2),
|
||||
renderCell: (row) => (
|
||||
<Text size="sm" color="secondary">
|
||||
{row.timestamp}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<VStack gap={5}>
|
||||
<StaticPanel
|
||||
title="Developer API Keys"
|
||||
subtitle="Manage authentication tokens for custom POS clients and server SDKs."
|
||||
actions={
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
label={isGenerating ? 'Cancel' : 'Generate Secret Key'}
|
||||
onClick={() => setIsGenerating(!isGenerating)}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<VStack gap={4}>
|
||||
{isGenerating ? (
|
||||
<HStack gap={3} vAlign="end" wrap="wrap">
|
||||
<TextInput
|
||||
label="API Key Description"
|
||||
value={newKeyName}
|
||||
onChange={setNewKeyName}
|
||||
placeholder="e.g. Indiranagar Counter 2 POS"
|
||||
/>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
label="Generate Token"
|
||||
onClick={handleGenerateKey}
|
||||
/>
|
||||
</HStack>
|
||||
) : null}
|
||||
|
||||
<ResponsiveTable
|
||||
columns={keyColumns}
|
||||
data={keys}
|
||||
primaryKey="name"
|
||||
/>
|
||||
</VStack>
|
||||
</StaticPanel>
|
||||
|
||||
<StaticPanel
|
||||
title="Webhook Subscriptions & Endpoints"
|
||||
subtitle="Real-time HTTP POST callbacks for order, redemption and staff events."
|
||||
>
|
||||
<VStack gap={4}>
|
||||
<HStack gap={3} vAlign="end" wrap="wrap">
|
||||
<TextInput
|
||||
label="Add Webhook Endpoint URL"
|
||||
value={newWebhookUrl}
|
||||
onChange={setNewWebhookUrl}
|
||||
placeholder="https://yourdomain.com/webhooks/loyaly"
|
||||
/>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
label="Add Endpoint"
|
||||
onClick={handleAddWebhook}
|
||||
/>
|
||||
</HStack>
|
||||
|
||||
<ResponsiveTable
|
||||
columns={webhookColumns}
|
||||
data={webhooks}
|
||||
primaryKey="url"
|
||||
/>
|
||||
</VStack>
|
||||
</StaticPanel>
|
||||
|
||||
<StaticPanel
|
||||
title="Webhook Delivery Logs"
|
||||
subtitle="Recent HTTP delivery attempts, latency and status response codes."
|
||||
>
|
||||
<ResponsiveTable
|
||||
columns={logColumns}
|
||||
data={WEBHOOK_LOGS}
|
||||
primaryKey="event"
|
||||
/>
|
||||
</StaticPanel>
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user