import { apiFetch } from '../api'; import React, { useState, useEffect } from 'react'; import { motion } from 'framer-motion'; import { Monitor, Plus, MapPin, ShieldCheck, Trash2, Search, ExternalLink, Info, Key, Wifi, WifiOff, LinkIcon, Unlink, Smartphone } from 'lucide-react'; import AddTerminalModal from '../components/AddTerminalModal.tsx'; import PinVerificationModal from '../components/PinVerificationModal.tsx'; import LinkDeviceModal from '../components/LinkDeviceModal.tsx'; interface Terminal { id: number; name: string; location: string; apiKey: string; paired: boolean; deviceId: string | null; pairedAt: string | null; } const Terminals = () => { const [terminals, setTerminals] = useState([]); const [loading, setLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(''); const [isAddModalOpen, setIsAddModalOpen] = useState(false); const [isPinModalOpen, setIsPinModalOpen] = useState(false); const [isLinkModalOpen, setIsLinkModalOpen] = useState(false); const [selectedTerminal, setSelectedTerminal] = useState(null); const fetchTerminals = async () => { setLoading(true); try { const response = await apiFetch('/api/terminals'); if (response.ok) { const data = await response.json(); setTerminals(data); } } catch (error) { console.error('Failed to fetch terminals:', error); } finally { setLoading(false); } }; useEffect(() => { fetchTerminals(); }, []); const handleDelete = async (id: number, e: React.MouseEvent) => { e.stopPropagation(); if (window.confirm('Are you sure you want to remove this terminal?')) { try { const response = await apiFetch(`/api/terminals/${id}`, { method: 'DELETE' }); if (response.ok) fetchTerminals(); } catch (error) { console.error('Failed to delete terminal:', error); } } }; const handleUnpair = async (id: number, e: React.MouseEvent) => { e.stopPropagation(); if (window.confirm('Unpair this device? It will need to be re-paired on next boot.')) { try { const response = await apiFetch(`/api/terminals/${id}/unpair`, { method: 'POST' }); if (response.ok) fetchTerminals(); } catch (error) { console.error('Failed to unpair device:', error); } } }; const pairedCount = terminals.filter(t => t.paired).length; const filteredTerminals = terminals.filter(t => { const query = (searchQuery || '').toLowerCase(); return (t.name || '').toLowerCase().includes(query) || (t.location || '').toLowerCase().includes(query); }); return (
{/* Header Section */}

Physical Terminals

Manage counter devices and device authentication keys

setIsAddModalOpen(true)} className="flex items-center justify-center gap-2 bg-[#231651] text-white px-6 py-3.5 rounded-2xl font-bold shadow-lg shadow-[#231651]/20 hover:bg-[#2d1d66] transition-all" > Add Terminal
{/* Stats/Info Banner */}

Registered Devices

{terminals.length}

0 ? 'bg-green-50 text-green-600' : 'bg-gray-50 text-gray-400'}`}>

Paired Devices

{pairedCount} / {terminals.length}

Power on a device and enter its 6-digit OTP here to pair it with a terminal.

{/* Search and Filters */}
setSearchQuery(e.target.value)} className="w-full h-12 bg-transparent outline-none text-sm font-medium text-gray-700 placeholder:text-gray-400" />
{/* Grid of Terminals */} {loading ? (
{[1,2,3].map(i => (
))}
) : filteredTerminals.length > 0 ? (
{filteredTerminals.map((terminal) => ( { setSelectedTerminal(terminal); setIsPinModalOpen(true); }} className="bg-white p-6 rounded-[2.5rem] border border-gray-100 shadow-sm hover:shadow-xl hover:shadow-[#231651]/5 transition-all cursor-pointer group relative overflow-hidden" > {/* Card Background Pattern */}
{terminal.paired && ( )}

{terminal.name}

{terminal.location}
{/* Device ID for paired terminals */} {terminal.paired && terminal.deviceId && (
{terminal.deviceId}
)}
{terminal.paired ? ( <>
Paired ) : ( <>
Unpaired )}
{terminal.paired ? (
View API Key
) : ( )}
))}
) : (

No terminals found

Start by adding your first counter device terminal to manage device authentication.

)} {/* Modals */} setIsAddModalOpen(false)} onSuccess={fetchTerminals} /> { setIsPinModalOpen(false); setSelectedTerminal(null); }} terminalId={selectedTerminal?.id || null} terminalName={selectedTerminal?.name || ''} /> { setIsLinkModalOpen(false); setSelectedTerminal(null); }} onSuccess={fetchTerminals} terminalId={selectedTerminal?.id || null} terminalName={selectedTerminal?.name || ''} />