Skip to content

Commit 0a092c8

Browse files
Merge pull request #1012 from soundsng/feat/fe-26-fe-34-developer-portal-qr-labels
feat: add Developer Portal and QR label printing pages [FE-26, FE-34]
2 parents adb881f + 5cb5926 commit 0a092c8

2 files changed

Lines changed: 117 additions & 0 deletions

File tree

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
'use client';
2+
3+
import { useState } from 'react';
4+
import { Plus, Copy, Trash2, Eye, EyeOff, Webhook } from 'lucide-react';
5+
import { Button } from '@/components/ui/button';
6+
7+
interface APIKey { id: string; name: string; key: string; created: string; lastUsed: string }
8+
interface WebhookConfig { id: string; url: string; events: string[]; active: boolean }
9+
10+
const MOCK_KEYS: APIKey[] = [
11+
{ id:'1', name:'Production App', key:'sk_live_xxxx...abc1', created:'2024-01-01', lastUsed:'2024-01-22' },
12+
{ id:'2', name:'Staging', key:'sk_test_xxxx...def2', created:'2024-01-10', lastUsed:'2024-01-20' },
13+
];
14+
const MOCK_HOOKS: WebhookConfig[] = [
15+
{ id:'1', url:'https://myapp.io/webhook', events:['asset.created','asset.updated'], active:true },
16+
];
17+
18+
export default function DeveloperPortalPage() {
19+
const [keys] = useState<APIKey[]>(MOCK_KEYS);
20+
const [hooks] = useState<WebhookConfig[]>(MOCK_HOOKS);
21+
const [revealed, setRevealed] = useState<Set<string>>(new Set());
22+
23+
const toggle = (id: string) => setRevealed(prev => { const s = new Set(prev); s.has(id) ? s.delete(id) : s.add(id); return s; });
24+
25+
return (
26+
<div>
27+
<div className="mb-6"><h1 className="text-2xl font-bold text-gray-900">Developer Portal</h1><p className="text-sm text-gray-500 mt-1">API key management and webhook configuration</p></div>
28+
<div className="space-y-6">
29+
<div className="bg-white rounded-xl border border-gray-200 overflow-hidden">
30+
<div className="flex items-center justify-between px-5 py-4 border-b border-gray-100">
31+
<h2 className="text-sm font-semibold text-gray-900">API Keys</h2>
32+
<Button size="sm"><Plus size={14} className="mr-1"/>New Key</Button>
33+
</div>
34+
<table className="w-full text-sm">
35+
<thead><tr className="border-b border-gray-200 bg-gray-50">{["Name","Key","Created","Last Used","Actions"].map(h=><th key={h} className="text-left px-4 py-3 font-medium text-gray-500">{h}</th>)}</tr></thead>
36+
<tbody>{keys.map(k=>(
37+
<tr key={k.id} className="border-b border-gray-100 hover:bg-gray-50">
38+
<td className="px-4 py-3 font-medium text-gray-900">{k.name}</td>
39+
<td className="px-4 py-3 font-mono text-xs text-gray-600">{revealed.has(k.id) ? k.key : '••••••••••••••••'}</td>
40+
<td className="px-4 py-3 text-gray-500">{k.created}</td>
41+
<td className="px-4 py-3 text-gray-500">{k.lastUsed}</td>
42+
<td className="px-4 py-3 flex items-center gap-2">
43+
<button onClick={() => toggle(k.id)} className="text-gray-400 hover:text-gray-700">{revealed.has(k.id) ? <EyeOff size={14}/> : <Eye size={14}/>}</button>
44+
<button className="text-gray-400 hover:text-gray-700"><Copy size={14}/></button>
45+
<button className="text-red-400 hover:text-red-600"><Trash2 size={14}/></button>
46+
</td>
47+
</tr>
48+
))}</tbody>
49+
</table>
50+
</div>
51+
<div className="bg-white rounded-xl border border-gray-200 overflow-hidden">
52+
<div className="flex items-center justify-between px-5 py-4 border-b border-gray-100">
53+
<h2 className="text-sm font-semibold text-gray-900 flex items-center gap-2"><Webhook size={16}/>Webhooks</h2>
54+
<Button size="sm"><Plus size={14} className="mr-1"/>Add Webhook</Button>
55+
</div>
56+
{hooks.map(w=>(
57+
<div key={w.id} className="px-5 py-4 border-b border-gray-100 flex items-start justify-between">
58+
<div><p className="text-sm font-medium text-gray-900">{w.url}</p><p className="text-xs text-gray-500 mt-0.5">{w.events.join(', ')}</p></div>
59+
<span className={px-2 py-0.5 rounded-full text-xs font-medium }>{w.active?'Active':'Inactive'}</span>
60+
</div>
61+
))}
62+
</div>
63+
</div>
64+
</div>
65+
);
66+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
'use client';
2+
3+
import { useState } from 'react';
4+
import { Printer, CheckSquare } from 'lucide-react';
5+
import { Button } from '@/components/ui/button';
6+
7+
interface PrintAsset { id: string; assetId: string; name: string; category: string; department: string }
8+
9+
const MOCK: PrintAsset[] = [
10+
{ id:'1', assetId:'AST-001', name:'Dell Laptop #1', category:'IT', department:'Engineering' },
11+
{ id:'2', assetId:'AST-002', name:'HP Monitor', category:'IT', department:'Design' },
12+
{ id:'3', assetId:'AST-003', name:'Office Chair', category:'Furniture', department:'HR' },
13+
{ id:'4', assetId:'AST-004', name:'Canon Printer', category:'IT', department:'Admin' },
14+
];
15+
16+
export default function QRLabelPage() {
17+
const [selected, setSelected] = useState<Set<string>>(new Set());
18+
const toggleAll = () => setSelected(prev => prev.size === MOCK.length ? new Set() : new Set(MOCK.map(a=>a.id)));
19+
const toggle = (id: string) => setSelected(prev => { const s = new Set(prev); s.has(id) ? s.delete(id) : s.add(id); return s; });
20+
21+
return (
22+
<div>
23+
<div className="flex items-center justify-between mb-6">
24+
<div><h1 className="text-2xl font-bold text-gray-900">Label &amp; QR Code Printing</h1><p className="text-sm text-gray-500 mt-1">Select assets and print asset labels</p></div>
25+
<Button disabled={selected.size === 0} onClick={() => window.print()}>
26+
<Printer size={16} className="mr-1.5"/>Print {selected.size > 0 ? ${selected.size} Label : 'Labels'}
27+
</Button>
28+
</div>
29+
<div className="bg-white rounded-xl border border-gray-200 overflow-hidden">
30+
<table className="w-full text-sm">
31+
<thead><tr className="border-b border-gray-200 bg-gray-50">
32+
<th className="px-4 py-3 w-8"><input type="checkbox" checked={selected.size===MOCK.length} onChange={toggleAll} className="rounded"/></th>
33+
{["Asset ID","Name","Category","Department"].map(h=><th key={h} className="text-left px-4 py-3 font-medium text-gray-500">{h}</th>)}
34+
</tr></thead>
35+
<tbody>
36+
{MOCK.map(a=>(
37+
<tr key={a.id} className={order-b border-gray-100 hover:bg-gray-50 cursor-pointer } onClick={()=>toggle(a.id)}>
38+
<td className="px-4 py-3"><input type="checkbox" checked={selected.has(a.id)} onChange={()=>toggle(a.id)} className="rounded" onClick={e=>e.stopPropagation()}/></td>
39+
<td className="px-4 py-3 font-mono text-xs text-gray-500">{a.assetId}</td>
40+
<td className="px-4 py-3 font-medium text-gray-900">{a.name}</td>
41+
<td className="px-4 py-3 text-gray-600">{a.category}</td>
42+
<td className="px-4 py-3 text-gray-600">{a.department}</td>
43+
</tr>
44+
))}
45+
</tbody>
46+
</table>
47+
</div>
48+
{selected.size > 0 && <p className="mt-2 text-xs text-gray-400 flex items-center gap-1"><CheckSquare size={12}/>{selected.size} asset{selected.size>1?'s':''} selected for printing</p>}
49+
</div>
50+
);
51+
}

0 commit comments

Comments
 (0)