login system
This commit is contained in:
+112
-11
@@ -1,8 +1,10 @@
|
||||
import { useState, useMemo } from 'react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { Settings } from 'lucide-react'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useStats } from './hooks/useStats'
|
||||
import { useTests } from './hooks/useTests'
|
||||
import { useConfig } from './hooks/useConfig'
|
||||
import { useAuth } from './hooks/useAuth'
|
||||
import cgw453Image from './assets/CGW453.PNG'
|
||||
import StatCard from './components/StatCard'
|
||||
import CompletionBar from './components/CompletionBar'
|
||||
@@ -24,12 +26,19 @@ function hasCoePairs(value) {
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const queryClient = useQueryClient()
|
||||
const { user, isReady, isAuthenticated, isAdmin, login, logout } = useAuth()
|
||||
|
||||
const [showConfig, setShowConfig] = useState(false)
|
||||
const [filters, setFilters] = useState({})
|
||||
const [username, setUsername] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [loginError, setLoginError] = useState('')
|
||||
const [isLoggingIn, setIsLoggingIn] = useState(false)
|
||||
|
||||
const { data: stats, isLoading: statsLoading } = useStats()
|
||||
const { data: allTests = [], isLoading: testsLoading } = useTests()
|
||||
const { data: config, isLoading: configLoading } = useConfig()
|
||||
const { data: stats, isLoading: statsLoading } = useStats(isAuthenticated)
|
||||
const { data: allTests = [], isLoading: testsLoading } = useTests({}, isAuthenticated)
|
||||
const { data: config, isLoading: configLoading } = useConfig(isAuthenticated && isAdmin)
|
||||
|
||||
// Apply filters client-side
|
||||
const filteredTests = useMemo(() => {
|
||||
@@ -52,22 +61,114 @@ export default function App() {
|
||||
})
|
||||
}, [allTests, filters])
|
||||
|
||||
const noConfig = !configLoading && !config?.target_dir && !config?.results_dir
|
||||
const configuredButEmpty = !configLoading && config?.target_dir && config?.results_dir &&
|
||||
const noConfig = isAdmin && !configLoading && !config?.target_dir && !config?.results_dir
|
||||
const configuredButEmpty = isAdmin && !configLoading && config?.target_dir && config?.results_dir &&
|
||||
!statsLoading && stats?.overall.total === 0
|
||||
|
||||
async function handleLogin(e) {
|
||||
e.preventDefault()
|
||||
setLoginError('')
|
||||
setIsLoggingIn(true)
|
||||
try {
|
||||
await login(username.trim(), password)
|
||||
setPassword('')
|
||||
} catch (err) {
|
||||
setLoginError(err?.message ?? 'Login failed')
|
||||
} finally {
|
||||
setIsLoggingIn(false)
|
||||
}
|
||||
}
|
||||
|
||||
function handleLogout() {
|
||||
logout()
|
||||
queryClient.clear()
|
||||
setShowConfig(false)
|
||||
setFilters({})
|
||||
}
|
||||
|
||||
if (!isReady) {
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-950 text-slate-200 flex items-center justify-center">
|
||||
<p className="text-sm text-slate-400">Checking session...</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-950 text-slate-100 flex items-center justify-center px-4">
|
||||
<form
|
||||
onSubmit={handleLogin}
|
||||
className="w-full max-w-md bg-slate-900 border border-slate-800 rounded-xl p-6 space-y-4"
|
||||
>
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold tracking-tight">Sign in</h1>
|
||||
</div>
|
||||
|
||||
{loginError && (
|
||||
<div className="bg-red-950/50 border border-red-700 rounded-lg px-3 py-2 text-red-300 text-sm">
|
||||
{loginError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<label className="block text-sm text-slate-300">
|
||||
Username
|
||||
<input
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
className="mt-1 w-full rounded-lg border border-slate-700 bg-slate-950 px-3 py-2 text-slate-100 focus:outline-none focus:ring-2 focus:ring-cyan-600"
|
||||
autoComplete="username"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className="block text-sm text-slate-300">
|
||||
Password
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="mt-1 w-full rounded-lg border border-slate-700 bg-slate-950 px-3 py-2 text-slate-100 focus:outline-none focus:ring-2 focus:ring-cyan-600"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoggingIn}
|
||||
className="w-full rounded-lg bg-cyan-700 hover:bg-cyan-600 disabled:opacity-60 px-4 py-2 text-sm font-medium transition-colors"
|
||||
>
|
||||
{isLoggingIn ? 'Signing in...' : 'Sign in'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-950 text-slate-100 flex flex-col">
|
||||
{/* Top bar */}
|
||||
<header className="border-b border-slate-800 px-6 py-4 flex items-center justify-between">
|
||||
<h1 className="text-lg font-bold tracking-tight">CGW453 Test Dashboard</h1>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-slate-400 hidden md:inline">
|
||||
{user?.username} ({user?.role})
|
||||
</span>
|
||||
{isAdmin && (
|
||||
<button
|
||||
onClick={() => setShowConfig(true)}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-lg border border-slate-700 text-slate-300 hover:bg-slate-800 transition-colors"
|
||||
>
|
||||
<Settings size={14} />
|
||||
Settings
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setShowConfig(true)}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-lg border border-slate-700 text-slate-300 hover:bg-slate-800 transition-colors"
|
||||
onClick={handleLogout}
|
||||
className="px-3 py-1.5 text-sm rounded-lg border border-slate-700 text-slate-300 hover:bg-slate-800 transition-colors"
|
||||
>
|
||||
<Settings size={14} />
|
||||
Settings
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
@@ -155,7 +256,7 @@ export default function App() {
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{showConfig && <ConfigModal onClose={() => setShowConfig(false)} />}
|
||||
{isAdmin && showConfig && <ConfigModal onClose={() => setShowConfig(false)} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user