EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
page.tsx
Go to the documentation of this file.
1'use client';
2
3import { useState, useEffect } from 'react';
4import Link from 'next/link';
5
6interface ApiUser {
7 f100: string; // API Key
8 f110: string; // Comments/Description
9 f120?: string; // Status
10 f130?: string; // Created date
11 f140?: string; // Last used
12}
13
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('');
20
21 // Modal states
22 const [showNewUserModal, setShowNewUserModal] = useState(false);
23 const [showEditModal, setShowEditModal] = useState(false);
24 const [selectedUser, setSelectedUser] = useState<ApiUser | null>(null);
25
26 // New user form
27 const [newUserForm, setNewUserForm] = useState({
28 comments: '',
29 permissions: [] as string[],
30 });
31
32 useEffect(() => {
33 loadApiUsers();
34 }, []);
35
36 useEffect(() => {
37 filterUsers();
38 }, [searchQuery, users]);
39
40 const loadApiUsers = async () => {
41 try {
42 const response = await fetch('/api/v1/messaging/api-users');
43
44 if (!response.ok || !response.headers.get("content-type")?.includes("application/json")) {
45 setError('⚠️ API Users API not yet implemented - UI demonstration mode');
46 setUsers([
47 {
48 f100: 'fpk_live_1234567890abcdef',
49 f110: 'Production API - Main Store Integration',
50 f120: 'Active',
51 f130: '2024-01-15',
52 f140: '2024-12-28'
53 },
54 {
55 f100: 'fpk_test_abcdefghijklmnop',
56 f110: 'Test Environment - Development',
57 f120: 'Active',
58 f130: '2024-03-20',
59 f140: '2024-12-27'
60 },
61 {
62 f100: 'fpk_live_qrstuvwxyz123456',
63 f110: 'Mobile App Integration',
64 f120: 'Inactive',
65 f130: '2024-06-10',
66 f140: '2024-11-15'
67 }
68 ]);
69 setLoading(false);
70 return;
71 }
72
73 const data = await response.json();
74 setUsers(data.users || []);
75 setError('');
76 } catch (err) {
77 console.error('Error loading API users:', err);
78 setError('Failed to load API users. Using demo mode.');
79 setUsers([
80 {
81 f100: 'fpk_live_1234567890abcdef',
82 f110: 'Production API - Main Store Integration',
83 f120: 'Active',
84 f130: '2024-01-15',
85 f140: '2024-12-28'
86 }
87 ]);
88 } finally {
89 setLoading(false);
90 }
91 };
92
93 const filterUsers = () => {
94 if (!searchQuery.trim()) {
95 setFilteredUsers(users);
96 return;
97 }
98
99 const query = searchQuery.toLowerCase();
100 const filtered = users.filter(user =>
101 user.f100.toLowerCase().includes(query) ||
102 user.f110.toLowerCase().includes(query)
103 );
104 setFilteredUsers(filtered);
105 };
106
107 const createNewUser = async () => {
108 try {
109 const response = await fetch('/api/v1/messaging/api-users', {
110 method: 'POST',
111 headers: { 'Content-Type': 'application/json' },
112 body: JSON.stringify({
113 comments: newUserForm.comments,
114 permissions: newUserForm.permissions,
115 }),
116 });
117
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.');
120 } else {
121 const data = await response.json();
122 alert(`New API Key created: ${data.apiKey}`);
123 }
124
125 closeAllModals();
126 setTimeout(loadApiUsers, 100);
127 } catch (err) {
128 console.error('Error creating API user:', err);
129 alert('Failed to create API user');
130 }
131 };
132
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.`)) {
135 return;
136 }
137
138 try {
139 const response = await fetch(`/api/v1/messaging/api-users/${user.f100}`, {
140 method: 'DELETE',
141 });
142
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.');
145 } else {
146 alert('API Key revoked successfully');
147 }
148
149 setTimeout(loadApiUsers, 100);
150 } catch (err) {
151 console.error('Error revoking API key:', err);
152 alert('Failed to revoke API key');
153 }
154 };
155
156 const copyToClipboard = (text: string) => {
157 navigator.clipboard.writeText(text).then(() => {
158 alert('API Key copied to clipboard');
159 }).catch(() => {
160 alert('Failed to copy to clipboard');
161 });
162 };
163
164 const closeAllModals = () => {
165 setShowNewUserModal(false);
166 setShowEditModal(false);
167 setSelectedUser(null);
168 setNewUserForm({
169 comments: '',
170 permissions: [],
171 });
172 };
173
174 if (loading) {
175 return (
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>
181 </div>
182 </div>
183 </div>
184 );
185 }
186
187 return (
188 <div className="min-h-screen bg-gray-50 p-8">
189 <div className="max-w-7xl mx-auto">
190 {/* Header */}
191 <div className="mb-8">
192 <div className="flex items-center justify-between mb-4">
193 <div>
194 <Link href="/marketing/messaging" className="text-[#00946b] hover:text-[#007055] mb-2 inline-block">
195 ← Back to Messaging
196 </Link>
197 <h1 className="text-3xl font-bold text-gray-900">🔑 RetailAPI User Management</h1>
198 </div>
199 <button
200 onClick={() => setShowNewUserModal(true)}
201 className="bg-[#00946b] text-white px-6 py-3 rounded-lg hover:bg-[#007055] font-medium transition-colors"
202 >
203 + Create New API Key
204 </button>
205 </div>
206
207 {error && (
208 <div className="bg-yellow-50 border-l-4 border-yellow-400 p-4 mb-4">
209 <p className="text-yellow-700">{error}</p>
210 </div>
211 )}
212
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.
216 </p>
217 </div>
218 </div>
219
220 {/* Search */}
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">
224 <input
225 type="text"
226 value={searchQuery}
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]"
230 />
231 </div>
232 <button
233 onClick={filterUsers}
234 className="px-6 py-2 bg-[#00946b] text-white rounded-md hover:bg-[#007055] font-medium transition-colors"
235 >
236 Search
237 </button>
238 </div>
239 <div className="mt-2 text-sm text-gray-600">
240 Displaying {filteredUsers.length} of {users.length} API keys
241 </div>
242 </div>
243
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>
248 </div>
249
250 <div className="overflow-x-auto">
251 <table className="min-w-full divide-y divide-gray-200">
252 <thead className="bg-gray-50">
253 <tr>
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>
260 </tr>
261 </thead>
262 <tbody className="bg-white divide-y divide-gray-200">
263 {filteredUsers.length === 0 ? (
264 <tr>
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.'}
267 </td>
268 </tr>
269 ) : (
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">
275 {user.f100}
276 </code>
277 <button
278 onClick={() => copyToClipboard(user.f100)}
279 className="text-gray-500 hover:text-gray-700"
280 title="Copy to clipboard"
281 >
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" />
284 </svg>
285 </button>
286 </div>
287 </td>
288 <td className="px-6 py-4">
289 <div className="text-sm text-gray-900">{user.f110}</div>
290 </td>
291 <td className="px-6 py-4 whitespace-nowrap">
292 <span
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'
297 }`}
298 >
299 {user.f120 || 'Active'}
300 </span>
301 </td>
302 <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-700">
303 {user.f130 || '-'}
304 </td>
305 <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-700">
306 {user.f140 || '-'}
307 </td>
308 <td className="px-6 py-4 whitespace-nowrap text-sm">
309 <div className="flex gap-2">
310 <button
311 onClick={() => {
312 setSelectedUser(user);
313 setShowEditModal(true);
314 }}
315 className="text-blue-600 hover:text-blue-800 font-medium"
316 >
317 Edit
318 </button>
319 <button
320 onClick={() => revokeApiKey(user)}
321 className="text-red-600 hover:text-red-800 font-medium"
322 >
323 Revoke
324 </button>
325 </div>
326 </td>
327 </tr>
328 ))
329 )}
330 </tbody>
331 </table>
332 </div>
333 </div>
334 </div>
335
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>
343 </div>
344
345 <div className="p-6">
346 <div className="space-y-4">
347 <div>
348 <label className="block text-sm font-medium text-gray-700 mb-1">Description / Purpose</label>
349 <input
350 type="text"
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"
355 />
356 </div>
357
358 <div>
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">
363 <input
364 type="checkbox"
365 checked={newUserForm.permissions.includes(perm)}
366 onChange={(e) => {
367 if (e.target.checked) {
368 setNewUserForm({ ...newUserForm, permissions: [...newUserForm.permissions, perm] });
369 } else {
370 setNewUserForm({ ...newUserForm, permissions: newUserForm.permissions.filter(p => p !== perm) });
371 }
372 }}
373 className="rounded border-gray-300 text-[#00946b] focus:ring-[#00946b]"
374 />
375 <span className="text-sm text-gray-700">{perm}</span>
376 </label>
377 ))}
378 </div>
379 </div>
380
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.
384 </p>
385 </div>
386 </div>
387
388 <div className="mt-6 flex justify-end gap-3">
389 <button
390 onClick={closeAllModals}
391 className="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"
392 >
393 Cancel
394 </button>
395 <button
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"
399 >
400 Create API Key
401 </button>
402 </div>
403 </div>
404 </div>
405 </div>
406 )}
407
408 {/* Edit Modal */}
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>
415 </div>
416
417 <div className="p-6">
418 <div className="space-y-4">
419 <div>
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">
422 {selectedUser.f100}
423 </code>
424 </div>
425
426 <div>
427 <label className="block text-sm font-medium text-gray-700 mb-1">Description</label>
428 <input
429 type="text"
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]"
432 />
433 </div>
434
435 <div>
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>
440 </select>
441 </div>
442 </div>
443
444 <div className="mt-6 flex justify-end gap-3">
445 <button
446 onClick={closeAllModals}
447 className="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"
448 >
449 Cancel
450 </button>
451 <button
452 onClick={() => {
453 alert('⚠️ API Key update not yet implemented');
454 closeAllModals();
455 }}
456 className="px-4 py-2 bg-[#00946b] text-white rounded-md hover:bg-[#007055]"
457 >
458 Save Changes
459 </button>
460 </div>
461 </div>
462 </div>
463 </div>
464 )}
465 </div>
466 );
467}