11import { useEffect , useState } from "react" ;
22import { api } from "./api" ;
3- import { Spinner } from "./ui" ;
3+ import { GhostButton , PrimaryButton , Spinner } from "./ui" ;
44import { Explorer } from "./views/Explorer" ;
55import { Overview } from "./views/Overview" ;
66import { Review } from "./views/Review" ;
77
8- const VIEWS = [ "Overview" , "Incident Explorer" , "Review Queue " ] as const ;
8+ const VIEWS = [ "Overview" , "Explorer" , "Review" ] as const ;
99type View = ( typeof VIEWS ) [ number ] ;
1010
11+ const DOCS_URL = "https://kohsheen1234.github.io/Open-source-AI-Incident-Observatory/" ;
12+ const REPO_URL = "https://github.com/kohsheen1234/Open-source-AI-Incident-Observatory" ;
13+ const API_DOCS = "https://agentwatch-api-7mhz.onrender.com/docs" ;
14+
1115export default function App ( ) {
1216 const [ view , setView ] = useState < View > ( "Overview" ) ;
1317 const [ ready , setReady ] = useState < boolean | null > ( null ) ;
@@ -29,76 +33,85 @@ export default function App() {
2933 } , [ ] ) ;
3034
3135 return (
32- < div className = "min-h-screen flex" >
33- < aside className = "w-64 shrink-0 border-r border-border bg-[#171717] hidden md:flex flex-col p-6" >
34- < div className = "flex items-center gap-2 text-lg font-bold text-white" > 🔭 AgentWatch</ div >
35- < div className = "text-xs text-muted mt-1" > Public AI-agent incident observatory</ div >
36+ < div className = "relative min-h-screen flex flex-col" >
37+ < div className = "page-bg" />
3638
37- < nav className = "mt-8 flex flex-col gap-1" >
38- { VIEWS . map ( ( v ) => (
39- < button
40- key = { v }
41- onClick = { ( ) => setView ( v ) }
42- className = { `text-left px-3 py-2 rounded-md text-sm transition-colors ${
43- view === v ? "bg-brand/10 text-brand font-medium" : "text-muted hover:text-ink hover:bg-panel"
44- } `}
45- >
46- { v }
47- </ button >
48- ) ) }
49- </ nav >
39+ < header className = "sticky top-0 z-30 border-b border-border bg-bg/70 backdrop-blur-xl" >
40+ < div className = "max-w-content mx-auto px-6 h-16 flex items-center justify-between" >
41+ < button onClick = { ( ) => setView ( "Overview" ) } className = "flex items-center gap-2.5 group" >
42+ < span className = "text-xl" > 🔭</ span >
43+ < span className = "font-bold text-ink tracking-tight" > AgentWatch</ span >
44+ < span className = "hidden sm:inline eyebrow ml-1" > Observatory</ span >
45+ </ button >
5046
51- < div className = "mt-auto pt-8 text-xs text-muted space-y-3" >
52- < div className = "border border-border rounded-lg p-3 bg-surface" >
53- < div className = "font-mono uppercase tracking-wider text-[0.65rem] text-brand mb-1" > Data sources</ div >
54- Hacker News (live), Reddit (optional), and a bundled sample set. A demo instance —
55- classifications are automated and unverified until reviewed.
56- </ div >
57- < div className = "flex gap-3" >
58- < a href = "https://kohsheen1234.github.io/Open-source-AI-Incident-Observatory/" > Docs</ a >
59- < a href = "https://github.com/kohsheen1234/Open-source-AI-Incident-Observatory" > Code</ a >
47+ < nav className = "hidden md:flex items-center gap-1" >
48+ { VIEWS . map ( ( v ) => (
49+ < button
50+ key = { v }
51+ onClick = { ( ) => setView ( v ) }
52+ className = { `px-3.5 py-1.5 rounded-lg text-sm transition-colors ${
53+ view === v ? "text-ink bg-panel" : "text-muted hover:text-ink"
54+ } `}
55+ >
56+ { v }
57+ </ button >
58+ ) ) }
59+ </ nav >
60+
61+ < div className = "flex items-center gap-2" >
62+ < a className = "text-sm text-muted hover:text-ink no-underline hidden sm:inline" href = { API_DOCS } target = "_blank" rel = "noreferrer" >
63+ API
64+ </ a >
65+ < a className = "text-sm text-muted hover:text-ink no-underline hidden sm:inline" href = { DOCS_URL } target = "_blank" rel = "noreferrer" >
66+ Docs
67+ </ a >
68+ < GhostButton href = { REPO_URL } > GitHub ↗</ GhostButton >
6069 </ div >
6170 </ div >
62- </ aside >
63-
64- < main className = "flex-1 min-w-0" >
65- { /* Mobile nav */ }
66- < div className = "md:hidden flex gap-1 p-3 border-b border-border bg-[#171717] overflow-x-auto" >
71+ { /* mobile nav */ }
72+ < div className = "md:hidden flex gap-1 px-4 pb-3 overflow-x-auto" >
6773 { VIEWS . map ( ( v ) => (
6874 < button
6975 key = { v }
7076 onClick = { ( ) => setView ( v ) }
71- className = { `px-3 py-1.5 rounded-md text-xs whitespace-nowrap ${
72- view === v ? "bg-brand/10 text-brand " : "text-muted"
77+ className = { `px-3 py-1.5 rounded-lg text-xs whitespace-nowrap ${
78+ view === v ? "text-ink bg-panel " : "text-muted"
7379 } `}
7480 >
7581 { v }
7682 </ button >
7783 ) ) }
7884 </ div >
85+ </ header >
7986
80- < div className = "max-w-6xl mx-auto px-6 py-10" >
81- { ready === null && (
82- < Spinner label = "Connecting to the API… the first load after idle can take up to ~60s while the free-tier backend wakes up. Thanks for your patience." />
83- ) }
84- { ready === false && (
85- < div className = "text-center py-24" >
86- < p className = "text-muted mb-4" >
87- The API is still waking up (free-tier cold start). It should be ready shortly.
88- </ p >
89- < button
90- onClick = { waitForApi }
91- className = "bg-brand hover:bg-brand-hover text-[#0b0f0d] font-semibold px-4 py-2 rounded-md"
92- >
93- Retry
94- </ button >
95- </ div >
96- ) }
97- { ready === true && view === "Overview" && < Overview /> }
98- { ready === true && view === "Incident Explorer" && < Explorer /> }
99- { ready === true && view === "Review Queue" && < Review /> }
100- </ div >
87+ < main className = "relative z-10 flex-1 w-full max-w-content mx-auto px-6 py-12" >
88+ { ready === null && (
89+ < Spinner label = "Connecting to the API… the first load after idle can take up to ~60s while the free-tier backend wakes up. Thanks for your patience." />
90+ ) }
91+ { ready === false && (
92+ < div className = "text-center py-28" >
93+ < p className = "text-muted mb-5 max-w-md mx-auto" >
94+ The API is still waking up (free-tier cold start). It should be ready in a few
95+ seconds.
96+ </ p >
97+ < PrimaryButton onClick = { waitForApi } > Retry</ PrimaryButton >
98+ </ div >
99+ ) }
100+ { ready === true && view === "Overview" && < Overview onExplore = { ( ) => setView ( "Explorer" ) } /> }
101+ { ready === true && view === "Explorer" && < Explorer /> }
102+ { ready === true && view === "Review" && < Review /> }
101103 </ main >
104+
105+ < footer className = "relative z-10 border-t border-border" >
106+ < div className = "max-w-content mx-auto px-6 py-8 flex flex-col sm:flex-row gap-3 items-center justify-between text-sm text-faint" >
107+ < div > AgentWatch — open-source observatory for public AI-agent incidents.</ div >
108+ < div className = "flex gap-4" >
109+ < a href = { REPO_URL } target = "_blank" rel = "noreferrer" > GitHub</ a >
110+ < a href = { DOCS_URL } target = "_blank" rel = "noreferrer" > Docs</ a >
111+ < a href = { API_DOCS } target = "_blank" rel = "noreferrer" > API</ a >
112+ </ div >
113+ </ div >
114+ </ footer >
102115 </ div >
103116 ) ;
104117}
0 commit comments