3import { useState, useEffect } from 'react';
4import Link from 'next/link';
7 f100: string; // API Key
8 f110: string; // Comments/Description
9 f120?: string; // Status
10 f130?: string; // Created date
11 f140?: string; // Last used
14export default function ApiUsersPage() {
15 const [users, setUsers] = useState<ApiUser[]>([]);
16 const [filteredUsers, setFilteredUsers] = useState<ApiUser[]>([]);
17 const [searchQuery, setSearchQuery] = useState('');
18 const [loading, setLoading] = useState(true);
19 const [error, setError] = useState('');
22 const [showNewUserModal, setShowNewUserModal] = useState(false);
23 const [showEditModal, setShowEditModal] = useState(false);
24 const [selectedUser, setSelectedUser] = useState<ApiUser | null>(null);
27 const [newUserForm, setNewUserForm] = useState({
29 permissions: [] as string[],
38 }, [searchQuery, users]);
40 const loadApiUsers = async () => {
42 const response = await fetch('/api/v1/messaging/api-users');
44 if (!response.ok || !response.headers.get("content-type")?.includes("application/json")) {
45 setError('⚠️ API Users API not yet implemented - UI demonstration mode');
48 f100: 'fpk_live_1234567890abcdef',
49 f110: 'Production API - Main Store Integration',
55 f100: 'fpk_test_abcdefghijklmnop',
56 f110: 'Test Environment - Development',
62 f100: 'fpk_live_qrstuvwxyz123456',
63 f110: 'Mobile App Integration',
73 const data = await response.json();
74 setUsers(data.users || []);
77 console.error('Error loading API users:', err);
78 setError('Failed to load API users. Using demo mode.');
81 f100: 'fpk_live_1234567890abcdef',
82 f110: 'Production API - Main Store Integration',
93 const filterUsers = () => {
94 if (!searchQuery.trim()) {
95 setFilteredUsers(users);
99 const query = searchQuery.toLowerCase();
100 const filtered = users.filter(user =>
101 user.f100.toLowerCase().includes(query) ||
102 user.f110.toLowerCase().includes(query)
104 setFilteredUsers(filtered);
107 const createNewUser = async () => {
109 const response = await fetch('/api/v1/messaging/api-users', {
111 headers: { 'Content-Type': 'application/json' },
112 body: JSON.stringify({
113 comments: newUserForm.comments,
114 permissions: newUserForm.permissions,
118 if (!response.ok || !response.headers.get("content-type")?.includes("application/json")) {
119 alert('⚠️ API User creation not yet implemented. In production, this would create a new API key.');
121 const data = await response.json();
122 alert(`New API Key created: ${data.apiKey}`);
126 setTimeout(loadApiUsers, 100);
128 console.error('Error creating API user:', err);
129 alert('Failed to create API user');
133 const revokeApiKey = async (user: ApiUser) => {
134 if (!confirm(`Are you sure you want to revoke this API key?\n\n${user.f100}\n\nThis action cannot be undone.`)) {
139 const response = await fetch(`/api/v1/messaging/api-users/${user.f100}`, {
143 if (!response.ok || !response.headers.get("content-type")?.includes("application/json")) {
144 alert('⚠️ API Key revocation not yet implemented. In production, this would revoke the key.');
146 alert('API Key revoked successfully');
149 setTimeout(loadApiUsers, 100);
151 console.error('Error revoking API key:', err);
152 alert('Failed to revoke API key');
156 const copyToClipboard = (text: string) => {
157 navigator.clipboard.writeText(text).then(() => {
158 alert('API Key copied to clipboard');
160 alert('Failed to copy to clipboard');
164 const closeAllModals = () => {
165 setShowNewUserModal(false);
166 setShowEditModal(false);
167 setSelectedUser(null);
176 <div className="min-h-screen bg-gray-50 p-8">
177 <div className="max-w-7xl mx-auto">
178 <div className="text-center py-12">
179 <div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-[#00946b]"></div>
180 <p className="mt-2 text-gray-600">Loading API users...</p>
188 <div className="min-h-screen bg-gray-50 p-8">
189 <div className="max-w-7xl mx-auto">
191 <div className="mb-8">
192 <div className="flex items-center justify-between mb-4">
194 <Link href="/marketing/messaging" className="text-[#00946b] hover:text-[#007055] mb-2 inline-block">
197 <h1 className="text-3xl font-bold text-gray-900">🔑 RetailAPI User Management</h1>
200 onClick={() => setShowNewUserModal(true)}
201 className="bg-[#00946b] text-white px-6 py-3 rounded-lg hover:bg-[#007055] font-medium transition-colors"
208 <div className="bg-yellow-50 border-l-4 border-yellow-400 p-4 mb-4">
209 <p className="text-yellow-700">{error}</p>
213 <div className="bg-blue-50 border-l-4 border-blue-400 p-4 mb-4">
214 <p className="text-blue-700">
215 <strong>Note:</strong> API keys provide programmatic access to your POS system. Keep these keys secure and never share them publicly.
221 <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4 mb-6">
222 <div className="flex gap-4">
223 <div className="flex-1">
227 onChange={(e) => setSearchQuery(e.target.value)}
228 placeholder="Search by API key or description..."
229 className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-[#00946b]"
233 onClick={filterUsers}
234 className="px-6 py-2 bg-[#00946b] text-white rounded-md hover:bg-[#007055] font-medium transition-colors"
239 <div className="mt-2 text-sm text-gray-600">
240 Displaying {filteredUsers.length} of {users.length} API keys
244 {/* API Users Table */}
245 <div className="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
246 <div className="px-6 py-4 border-b border-gray-200">
247 <h2 className="text-xl font-semibold text-gray-900">API Keys</h2>
250 <div className="overflow-x-auto">
251 <table className="min-w-full divide-y divide-gray-200">
252 <thead className="bg-gray-50">
254 <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">API Key</th>
255 <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Description</th>
256 <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
257 <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Created</th>
258 <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Last Used</th>
259 <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
262 <tbody className="bg-white divide-y divide-gray-200">
263 {filteredUsers.length === 0 ? (
265 <td colSpan={6} className="px-6 py-8 text-center text-gray-500">
266 {searchQuery ? 'No API keys match your search.' : 'No API keys configured. Click "Create New API Key" to add one.'}
270 filteredUsers.map((user) => (
271 <tr key={user.f100} className="hover:bg-gray-50">
272 <td className="px-6 py-4 whitespace-nowrap">
273 <div className="flex items-center gap-2">
274 <code className="text-sm font-mono text-gray-900 bg-gray-100 px-2 py-1 rounded">
278 onClick={() => copyToClipboard(user.f100)}
279 className="text-gray-500 hover:text-gray-700"
280 title="Copy to clipboard"
282 <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
283 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
288 <td className="px-6 py-4">
289 <div className="text-sm text-gray-900">{user.f110}</div>
291 <td className="px-6 py-4 whitespace-nowrap">
293 className={`px-3 py-1 inline-flex text-xs leading-5 font-semibold rounded-full ${
294 user.f120 === 'Active'
295 ? 'bg-green-100 text-green-800'
296 : 'bg-gray-100 text-gray-800'
299 {user.f120 || 'Active'}
302 <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-700">
305 <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-700">
308 <td className="px-6 py-4 whitespace-nowrap text-sm">
309 <div className="flex gap-2">
312 setSelectedUser(user);
313 setShowEditModal(true);
315 className="text-blue-600 hover:text-blue-800 font-medium"
320 onClick={() => revokeApiKey(user)}
321 className="text-red-600 hover:text-red-800 font-medium"
336 {/* New API Key Modal */}
337 {showNewUserModal && (
338 <div className="modal-overlay" onClick={closeAllModals}>
339 <div className="bg-white rounded-lg shadow-xl max-w-2xl w-full" onClick={(e) => e.stopPropagation()}>
340 <div className="bg-[#00946b] text-white px-6 py-4 rounded-t-lg flex justify-between items-center">
341 <h2 className="text-xl font-semibold">Create New API Key</h2>
342 <button onClick={closeAllModals} className="text-white hover:text-gray-200 text-2xl">×</button>
345 <div className="p-6">
346 <div className="space-y-4">
348 <label className="block text-sm font-medium text-gray-700 mb-1">Description / Purpose</label>
351 value={newUserForm.comments}
352 onChange={(e) => setNewUserForm({ ...newUserForm, comments: e.target.value })}
353 className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-[#00946b]"
354 placeholder="e.g., Production API - Main Store"
359 <label className="block text-sm font-medium text-gray-700 mb-2">Permissions</label>
360 <div className="space-y-2">
361 {['Read Products', 'Write Products', 'Read Sales', 'Write Sales', 'Read Customers', 'Write Customers'].map((perm) => (
362 <label key={perm} className="flex items-center gap-2">
365 checked={newUserForm.permissions.includes(perm)}
367 if (e.target.checked) {
368 setNewUserForm({ ...newUserForm, permissions: [...newUserForm.permissions, perm] });
370 setNewUserForm({ ...newUserForm, permissions: newUserForm.permissions.filter(p => p !== perm) });
373 className="rounded border-gray-300 text-[#00946b] focus:ring-[#00946b]"
375 <span className="text-sm text-gray-700">{perm}</span>
381 <div className="bg-yellow-50 border-l-4 border-yellow-400 p-4">
382 <p className="text-sm text-yellow-700">
383 <strong>Important:</strong> The API key will only be shown once. Make sure to copy it and store it securely.
388 <div className="mt-6 flex justify-end gap-3">
390 onClick={closeAllModals}
391 className="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"
396 onClick={createNewUser}
397 disabled={!newUserForm.comments}
398 className="px-4 py-2 bg-[#00946b] text-white rounded-md hover:bg-[#007055] disabled:bg-gray-400 disabled:cursor-not-allowed"
409 {showEditModal && selectedUser && (
410 <div className="modal-overlay" onClick={closeAllModals}>
411 <div className="bg-white rounded-lg shadow-xl max-w-2xl w-full" onClick={(e) => e.stopPropagation()}>
412 <div className="bg-[#00946b] text-white px-6 py-4 rounded-t-lg flex justify-between items-center">
413 <h2 className="text-xl font-semibold">Edit API Key</h2>
414 <button onClick={closeAllModals} className="text-white hover:text-gray-200 text-2xl">×</button>
417 <div className="p-6">
418 <div className="space-y-4">
420 <label className="block text-sm font-medium text-gray-700 mb-1">API Key</label>
421 <code className="block w-full px-3 py-2 bg-gray-100 border border-gray-300 rounded-md text-sm font-mono">
427 <label className="block text-sm font-medium text-gray-700 mb-1">Description</label>
430 defaultValue={selectedUser.f110}
431 className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-[#00946b]"
436 <label className="block text-sm font-medium text-gray-700 mb-1">Status</label>
437 <select className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-[#00946b]">
438 <option value="Active">Active</option>
439 <option value="Inactive">Inactive</option>
444 <div className="mt-6 flex justify-end gap-3">
446 onClick={closeAllModals}
447 className="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"
453 alert('⚠️ API Key update not yet implemented');
456 className="px-4 py-2 bg-[#00946b] text-white rounded-md hover:bg-[#007055]"