Files
furumi-ng/furumi-node-player/client/src/App.tsx

143 lines
4.0 KiB
TypeScript
Raw Normal View History

2026-03-19 15:06:32 +03:00
import { useEffect, useMemo, useState } from 'react'
2026-03-19 17:31:09 +03:00
import { FurumiPlayer } from './FurumiPlayer'
2026-03-19 15:06:32 +03:00
import './App.css'
type UserProfile = {
sub: string
name?: string
email?: string
}
2026-03-19 15:47:21 +03:00
const NO_AUTH_STORAGE_KEY = 'furumiNodePlayer.runWithoutAuth'
2026-03-19 15:06:32 +03:00
function App() {
const [loading, setLoading] = useState(true)
const [user, setUser] = useState<UserProfile | null>(null)
const [error, setError] = useState<string | null>(null)
2026-03-19 15:47:21 +03:00
const [runWithoutAuth, setRunWithoutAuth] = useState(() => {
try {
return window.localStorage.getItem(NO_AUTH_STORAGE_KEY) === '1'
} catch {
return false
}
})
2026-03-19 15:06:32 +03:00
const apiBase = useMemo(() => import.meta.env.VITE_API_BASE_URL ?? '', [])
useEffect(() => {
2026-03-19 15:47:21 +03:00
if (runWithoutAuth) {
setError(null)
setUser({ sub: 'noauth', name: 'No Auth' })
setLoading(false)
return
}
2026-03-19 15:06:32 +03:00
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()
2026-03-19 15:47:21 +03:00
}, [apiBase, runWithoutAuth])
2026-03-19 15:06:32 +03:00
const loginUrl = `${apiBase}/api/login`
const logoutUrl = `${apiBase}/api/logout`
2026-03-19 17:31:09 +03:00
const playerApiRoot = `${apiBase}/api`
2026-03-19 15:06:32 +03:00
return (
2026-03-19 17:31:09 +03:00
<>
{!loading && (user || runWithoutAuth) ? (
<FurumiPlayer apiRoot={playerApiRoot} />
) : (
<main className="page">
<section className="card">
<h1>OIDC Login</h1>
<p className="subtitle">Авторизация обрабатывается на Express сервере.</p>
<div className="settings">
<label className="toggle">
<input
type="checkbox"
checked={runWithoutAuth}
onChange={(e) => {
const next = e.target.checked
setRunWithoutAuth(next)
try {
if (next) window.localStorage.setItem(NO_AUTH_STORAGE_KEY, '1')
else window.localStorage.removeItem(NO_AUTH_STORAGE_KEY)
} catch {
// ignore
}
setLoading(true)
setUser(null)
}}
/>
<span>Запускать без авторизации</span>
</label>
</div>
{loading && <p>Проверяю сессию...</p>}
{error && <p className="error">Ошибка: {error}</p>}
{!loading && runWithoutAuth && (
<p className="hint">
Режим без авторизации включён. Для входа отключи настройку выше.
2026-03-19 15:06:32 +03:00
</p>
)}
2026-03-19 17:31:09 +03:00
{!loading && !user && (
<a className="btn" href={loginUrl}>
Войти через OIDC
2026-03-19 15:47:21 +03:00
</a>
)}
2026-03-19 17:31:09 +03:00
{!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>
)}
{!runWithoutAuth && (
<a className="btn ghost" href={logoutUrl}>
Выйти
</a>
)}
</div>
)}
</section>
</main>
)}
</>
2026-03-19 15:06:32 +03:00
)
}
export default App