189 lines
6.4 KiB
TypeScript
189 lines
6.4 KiB
TypeScript
import React, { useState } from 'react'
|
|
import { Button } from './Button'
|
|
|
|
interface LoginProps {
|
|
onLogin: (token: string) => void
|
|
}
|
|
|
|
export const Login: React.FC<LoginProps> = ({ onLogin }) => {
|
|
const [token, setToken] = useState('')
|
|
const [name, setName] = useState('')
|
|
const [description, setDescription] = useState('')
|
|
const [isCreating, setIsCreating] = useState(false)
|
|
const [error, setError] = useState('')
|
|
const [createdToken, setCreatedToken] = useState('')
|
|
|
|
const handleLoginWithToken = () => {
|
|
if (!token.trim()) {
|
|
setError('Please enter a token')
|
|
return
|
|
}
|
|
localStorage.setItem('admin_token', token.trim())
|
|
onLogin(token.trim())
|
|
}
|
|
|
|
const handleCreateToken = async () => {
|
|
if (!name.trim()) {
|
|
setError('Please enter a token name')
|
|
return
|
|
}
|
|
|
|
setIsCreating(true)
|
|
setError('')
|
|
|
|
try {
|
|
const response = await fetch('http://localhost:8000/api/v1/admin/auth/token', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
name: name.trim(),
|
|
description: description.trim() || undefined,
|
|
}),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to create token')
|
|
}
|
|
|
|
const data = await response.json()
|
|
setCreatedToken(data.token)
|
|
setToken(data.token)
|
|
setError('')
|
|
} catch (err) {
|
|
setError('Failed to create token. Please check your connection.')
|
|
console.error(err)
|
|
} finally {
|
|
setIsCreating(false)
|
|
}
|
|
}
|
|
|
|
const handleUseCreatedToken = () => {
|
|
if (createdToken) {
|
|
localStorage.setItem('admin_token', createdToken)
|
|
onLogin(createdToken)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-warm-bg flex items-center justify-center p-4">
|
|
<div className="bg-warm-card border border-warm-border rounded-lg shadow-modal p-8 max-w-md w-full">
|
|
<h1 className="text-2xl font-bold text-warm-text-primary mb-2">
|
|
Admin Authentication
|
|
</h1>
|
|
<p className="text-sm text-warm-text-muted mb-6">
|
|
Sign in with an admin token to access the document management system
|
|
</p>
|
|
|
|
{error && (
|
|
<div className="mb-4 p-3 bg-red-50 border border-red-200 text-red-800 rounded text-sm">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
{createdToken && (
|
|
<div className="mb-4 p-3 bg-green-50 border border-green-200 rounded">
|
|
<p className="text-sm font-medium text-green-800 mb-2">Token created successfully!</p>
|
|
<div className="bg-white border border-green-300 rounded p-2 mb-3">
|
|
<code className="text-xs font-mono text-warm-text-primary break-all">
|
|
{createdToken}
|
|
</code>
|
|
</div>
|
|
<p className="text-xs text-green-700 mb-3">
|
|
Save this token securely. You won't be able to see it again.
|
|
</p>
|
|
<Button onClick={handleUseCreatedToken} className="w-full">
|
|
Use This Token
|
|
</Button>
|
|
</div>
|
|
)}
|
|
|
|
<div className="space-y-6">
|
|
{/* Login with existing token */}
|
|
<div>
|
|
<h2 className="text-sm font-semibold text-warm-text-secondary mb-3">
|
|
Sign in with existing token
|
|
</h2>
|
|
<div className="space-y-3">
|
|
<div>
|
|
<label className="block text-sm text-warm-text-secondary mb-1">
|
|
Admin Token
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={token}
|
|
onChange={(e) => setToken(e.target.value)}
|
|
placeholder="Enter your admin token"
|
|
className="w-full px-3 py-2 border border-warm-border rounded-md text-sm focus:outline-none focus:ring-1 focus:ring-warm-state-info font-mono"
|
|
onKeyDown={(e) => e.key === 'Enter' && handleLoginWithToken()}
|
|
/>
|
|
</div>
|
|
<Button onClick={handleLoginWithToken} className="w-full">
|
|
Sign In
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="relative">
|
|
<div className="absolute inset-0 flex items-center">
|
|
<div className="w-full border-t border-warm-border"></div>
|
|
</div>
|
|
<div className="relative flex justify-center text-xs">
|
|
<span className="px-2 bg-warm-card text-warm-text-muted">OR</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Create new token */}
|
|
<div>
|
|
<h2 className="text-sm font-semibold text-warm-text-secondary mb-3">
|
|
Create new admin token
|
|
</h2>
|
|
<div className="space-y-3">
|
|
<div>
|
|
<label className="block text-sm text-warm-text-secondary mb-1">
|
|
Token Name <span className="text-red-500">*</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={name}
|
|
onChange={(e) => setName(e.target.value)}
|
|
placeholder="e.g., my-laptop"
|
|
className="w-full px-3 py-2 border border-warm-border rounded-md text-sm focus:outline-none focus:ring-1 focus:ring-warm-state-info"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm text-warm-text-secondary mb-1">
|
|
Description (optional)
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={description}
|
|
onChange={(e) => setDescription(e.target.value)}
|
|
placeholder="e.g., Personal laptop access"
|
|
className="w-full px-3 py-2 border border-warm-border rounded-md text-sm focus:outline-none focus:ring-1 focus:ring-warm-state-info"
|
|
/>
|
|
</div>
|
|
<Button
|
|
onClick={handleCreateToken}
|
|
variant="secondary"
|
|
disabled={isCreating}
|
|
className="w-full"
|
|
>
|
|
{isCreating ? 'Creating...' : 'Create Token'}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-6 pt-4 border-t border-warm-border">
|
|
<p className="text-xs text-warm-text-muted">
|
|
Admin tokens are used to authenticate with the document management API.
|
|
Keep your tokens secure and never share them.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|