50 lines
1.3 KiB
TypeScript
50 lines
1.3 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|||
|
|
import { pingApi, type PingResponse } from './api'
|
||
|
|
import './index.css'
|
||
|
|
|
||
|
|
type ApiState =
|
||
|
|
| { status: 'loading' }
|
||
|
|
| { status: 'ok'; data: PingResponse }
|
||
|
|
| { status: 'error'; error: string }
|
||
|
|
|
||
|
|
function App() {
|
||
|
|
const [api, setApi] = useState<ApiState>({ status: 'loading' })
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
let cancelled = false
|
||
|
|
pingApi()
|
||
|
|
.then((data) => {
|
||
|
|
if (!cancelled) setApi({ status: 'ok', data })
|
||
|
|
})
|
||
|
|
.catch((err: unknown) => {
|
||
|
|
if (!cancelled) setApi({ status: 'error', error: String(err) })
|
||
|
|
})
|
||
|
|
return () => {
|
||
|
|
cancelled = true
|
||
|
|
}
|
||
|
|
}, [])
|
||
|
|
|
||
|
|
return (
|
||
|
|
<main className="shell">
|
||
|
|
<h1>Messenger</h1>
|
||
|
|
<div className="card">
|
||
|
|
<div className="status-row">
|
||
|
|
<span className={`dot ${api.status}`} aria-hidden />
|
||
|
|
<span className="status-label">
|
||
|
|
{api.status === 'loading' && 'API: connecting…'}
|
||
|
|
{api.status === 'ok' && 'API: connected'}
|
||
|
|
{api.status === 'error' && 'API: unreachable'}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
{api.status === 'ok' && (
|
||
|
|
<p className="detail">
|
||
|
|
{api.data.message} · server time {api.data.time}
|
||
|
|
</p>
|
||
|
|
)}
|
||
|
|
{api.status === 'error' && <p className="detail">{api.error}</p>}
|
||
|
|
</div>
|
||
|
|
</main>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
export default App
|