diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 82561de..d1a09c7 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -334,6 +334,8 @@ function DashboardPage({ user, setUser }) { ) : activeTab === 'settings' ? ( + ) : activeTab === 'api-tokens' ? ( + ) : activeTab === 'users' ? (
馃懃
@@ -597,6 +599,310 @@ function SettingsTab({ user }) { ) } +function APITokensTab({ user }) { + const [tokens, setTokens] = useState([]) + const [loading, setLoading] = useState(true) + const [showCreateForm, setShowCreateForm] = useState(false) + const [newTokenDescription, setNewTokenDescription] = useState('') + const [createdToken, setCreatedToken] = useState(null) + const [creating, setCreating] = useState(false) + + useEffect(() => { + loadTokens() + }, []) + + const loadTokens = async () => { + try { + const token = localStorage.getItem('token') + const API_URL = import.meta.env.VITE_API_URL || '' + + const response = await fetch(`${API_URL}/api/users/me/tokens`, { + headers: { 'Authorization': `Bearer ${token}` } + }) + + if (response.ok) { + const data = await response.json() + setTokens(data) + } + + setLoading(false) + } catch (error) { + console.error('Error loading tokens:', error) + setLoading(false) + } + } + + const handleCreateToken = async (e) => { + e.preventDefault() + setCreating(true) + + try { + const token = localStorage.getItem('token') + const API_URL = import.meta.env.VITE_API_URL || '' + + const response = await fetch(`${API_URL}/api/users/me/tokens`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ description: newTokenDescription || null }), + }) + + if (response.ok) { + const data = await response.json() + setCreatedToken(data.token) + setShowCreateForm(false) + setNewTokenDescription('') + loadTokens() + } else { + alert('Error al crear token') + } + } catch (error) { + console.error('Error:', error) + alert('Error al crear token') + } finally { + setCreating(false) + } + } + + const handleRevokeToken = async (tokenId) => { + if (!confirm('驴Est谩s seguro de revocar este token? Esta acci贸n no se puede deshacer.')) { + return + } + + try { + const token = localStorage.getItem('token') + const API_URL = import.meta.env.VITE_API_URL || '' + + const response = await fetch(`${API_URL}/api/users/me/tokens/${tokenId}`, { + method: 'DELETE', + headers: { 'Authorization': `Bearer ${token}` } + }) + + if (response.ok) { + alert('Token revocado correctamente') + loadTokens() + } else { + alert('Error al revocar token') + } + } catch (error) { + console.error('Error:', error) + alert('Error al revocar token') + } + } + + const copyToClipboard = (text) => { + navigator.clipboard.writeText(text) + alert('Token copiado al portapapeles') + } + + return ( +
+
+
+

Mis API Tokens

+

+ Genera tokens para acceder a la API sin necesidad de login +

+
+ +
+ + {/* Modal de Token Creado */} + {createdToken && ( +
+
+
+

Token Creado Exitosamente

+

+ 鈿狅笍 Guarda este token ahora. No podr谩s verlo de nuevo. +

+
+ +
+
+ {createdToken} + +
+
+ +
+

Ejemplo de uso:

+ + curl -H "Authorization: Bearer {createdToken}" http://tu-api.com/api/inspections + +
+ + +
+
+ )} + + {/* Formulario de Crear Token */} + {showCreateForm && ( +
+
+

Generar Nuevo Token

+ +
+
+ + setNewTokenDescription(e.target.value)} + placeholder="ej: Integraci贸n con sistema X" + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + /> +

+ Te ayuda a identificar para qu茅 usas este token +

+
+ +
+ + +
+
+
+
+ )} + + {/* Lista de Tokens */} + {loading ? ( +
+
Cargando tokens...
+
+ ) : tokens.length === 0 ? ( +
+
馃攽
+

No tienes tokens API creados

+

Genera uno para acceder a la API sin login

+
+ ) : ( +
+ {tokens.map((token) => ( +
+
+
+
+ 馃攽 +

+ {token.description || 'Token sin descripci贸n'} +

+ + {token.is_active ? 'Activo' : 'Revocado'} + +
+ +
+
+ Creado:{' '} + {new Date(token.created_at).toLocaleDateString('es-ES', { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + })} +
+ {token.last_used_at && ( +
+ 脷ltimo uso:{' '} + {new Date(token.last_used_at).toLocaleDateString('es-ES', { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + })} +
+ )} + {!token.last_used_at && ( +
+ 鈿狅笍 Nunca usado +
+ )} +
+
+ + {token.is_active && ( + + )} +
+
+ ))} +
+ )} + + {/* Informaci贸n de Ayuda */} +
+
+ 鈩癸笍 +
+

驴C贸mo usar los tokens API?

+

+ Los tokens API te permiten acceder a todos los endpoints sin necesidad de hacer login. + Son perfectos para integraciones, scripts automatizados o aplicaciones externas. +

+

+ Incluye el token en el header Authorization de tus requests: +

+ + Authorization: Bearer syntria_tu_token_aqui + +
+
+
+
+ ) +} + function QuestionsManagerModal({ checklist, onClose }) { const [questions, setQuestions] = useState([]) const [loading, setLoading] = useState(true) diff --git a/frontend/src/Sidebar.jsx b/frontend/src/Sidebar.jsx index 7d6151c..c7350c2 100644 --- a/frontend/src/Sidebar.jsx +++ b/frontend/src/Sidebar.jsx @@ -81,6 +81,20 @@ export default function Sidebar({ user, activeTab, setActiveTab, sidebarOpen, se {sidebarOpen && Reportes} +
  • + +