89 lines
2.2 KiB
TypeScript
89 lines
2.2 KiB
TypeScript
|
|
import { useEffect, useMemo, useState } from 'react'
|
|||
|
|
import './App.css'
|
|||
|
|
|
|||
|
|
type UserProfile = {
|
|||
|
|
sub: string
|
|||
|
|
name?: string
|
|||
|
|
email?: string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function App() {
|
|||
|
|
const [loading, setLoading] = useState(true)
|
|||
|
|
const [user, setUser] = useState<UserProfile | null>(null)
|
|||
|
|
const [error, setError] = useState<string | null>(null)
|
|||
|
|
|
|||
|
|
const apiBase = useMemo(() => import.meta.env.VITE_API_BASE_URL ?? '', [])
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
const loadMe = async () => {
|
|||
|
|
try {
|
|||
|
|
const response = await fetch(`${apiBase}/api/me`, {
|
|||
|
|
credentials: 'include',
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (response.status === 401) {
|
|||
|
|
setUser(null)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!response.ok) {
|
|||
|
|
throw new Error(`Request failed with status ${response.status}`)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const data = await response.json()
|
|||
|
|
setUser(data.user ?? null)
|
|||
|
|
} catch (err) {
|
|||
|
|
setError(err instanceof Error ? err.message : 'Failed to load session')
|
|||
|
|
} finally {
|
|||
|
|
setLoading(false)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void loadMe()
|
|||
|
|
}, [apiBase])
|
|||
|
|
|
|||
|
|
const loginUrl = `${apiBase}/api/login`
|
|||
|
|
const logoutUrl = `${apiBase}/api/logout`
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<main className="page">
|
|||
|
|
<section className="card">
|
|||
|
|
<h1>OIDC Login</h1>
|
|||
|
|
<p className="subtitle">Авторизация обрабатывается на Express сервере.</p>
|
|||
|
|
|
|||
|
|
{loading && <p>Проверяю сессию...</p>}
|
|||
|
|
{error && <p className="error">Ошибка: {error}</p>}
|
|||
|
|
|
|||
|
|
{!loading && !user && (
|
|||
|
|
<a className="btn" href={loginUrl}>
|
|||
|
|
Войти через OIDC
|
|||
|
|
</a>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{!loading && user && (
|
|||
|
|
<div className="profile">
|
|||
|
|
<p>
|
|||
|
|
<strong>ID:</strong> {user.sub}
|
|||
|
|
</p>
|
|||
|
|
{user.name && (
|
|||
|
|
<p>
|
|||
|
|
<strong>Имя:</strong> {user.name}
|
|||
|
|
</p>
|
|||
|
|
)}
|
|||
|
|
{user.email && (
|
|||
|
|
<p>
|
|||
|
|
<strong>Email:</strong> {user.email}
|
|||
|
|
</p>
|
|||
|
|
)}
|
|||
|
|
<a className="btn ghost" href={logoutUrl}>
|
|||
|
|
Выйти
|
|||
|
|
</a>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</section>
|
|||
|
|
</main>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export default App
|