import { useState, useEffect, useCallback, useRef } from "react" import "./App.css" const API = window.location.hostname === "localhost" ? "http://localhost:8000" : "" function App() { const [page, setPage] = useState("login") const [token, setToken] = useState(localStorage.getItem("access_token")) const [user, setUser] = useState(null) const [form, setForm] = useState({ username: "", email: "", password: "" }) const [otp, setOtp] = useState("") const [msg, setMsg] = useState("") const hasCheckedAuth = useRef(false) const refreshAccessToken = useCallback(async () => { const refreshToken = localStorage.getItem("refresh_token") if (!refreshToken) { logout(); return null } try { const res = await fetch(`${API}/auth/refresh`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ refresh_token: refreshToken }) }) if (res.ok) { const data = await res.json() localStorage.setItem("access_token", data.access_token) localStorage.setItem("refresh_token", data.refresh_token) setToken(data.access_token) return data.access_token } else { logout(); return null } } catch { logout(); return null } }, []) const apiCall = useCallback(async (url, options = {}) => { let currentToken = localStorage.getItem("access_token") let res = await fetch(url, { ...options, headers: { "Content-Type": "application/json", ...options.headers, Authorization: `Bearer ${currentToken}` } }) if (res.status === 401) { const newToken = await refreshAccessToken() if (newToken) { res = await fetch(url, { ...options, headers: { "Content-Type": "application/json", ...options.headers, Authorization: `Bearer ${newToken}` } }) } } return res }, [refreshAccessToken]) function navigateTo(newPage) { setPage(newPage) setMsg("") setOtp("") } function resetAll() { setPage("login") setMsg("") setOtp("") setForm({ username: "", email: "", password: "" }) } useEffect(() => { if (!hasCheckedAuth.current) { hasCheckedAuth.current = true; if (token) getMe() } }, []) async function getMe() { const res = await apiCall(`${API}/me`) if (res.ok) { setUser(await res.json()); navigateTo("dashboard") } else logout() } async function register() { try { if (!/^[a-zA-Z0-9_]{3,20}$/.test(form.username)) { setMsg("Username: 3-20 chars, letters/numbers/_ only"); return } if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) { setMsg("Invalid email format"); return } if (form.password.length < 8) { setMsg("Password must be at least 8 characters"); return } const res = await fetch(`${API}/auth/register`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: form.username, email: form.email, password: form.password }) }) const data = await res.json() if (res.ok) { setPage("verify-register") setOtp("") setMsg("OTP sent to your email. Enter it to verify.") } else { setMsg(data.detail || "Registration failed") } } catch (err) { setMsg("Network error. Is the backend running?") } } async function verifyRegister() { try { if (otp.length !== 6) { setMsg("Enter the 6-digit OTP"); return } const res = await fetch(`${API}/auth/verify-email`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: form.email, code: otp }) }) const data = await res.json() if (res.ok) { setMsg("Account verified! Now login") navigateTo("login") } else { setMsg(data.detail || "Verification failed") } } catch (err) { setMsg("Network error. Is the backend running?") } } async function login() { try { const body = new URLSearchParams() body.append("username", form.username) body.append("password", form.password) const res = await fetch(`${API}/auth/login`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: body.toString() }) const data = await res.json() if (res.ok) { localStorage.setItem("access_token", data.access_token) localStorage.setItem("refresh_token", data.refresh_token) setToken(data.access_token) const meRes = await apiCall(`${API}/me`) if (meRes.ok) setUser(await meRes.json()) navigateTo("dashboard") } else { setMsg(data.detail || "Login failed") } } catch (err) { setMsg("Network error. Is the backend running?") } } function logout() { localStorage.removeItem("access_token") localStorage.removeItem("refresh_token") setToken(null); setUser(null); resetAll() } async function forgotPassword() { try { if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) { setMsg("Enter a valid email"); return } const res = await fetch(`${API}/auth/forgot-password`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: form.email }) }) const data = await res.json() if (res.ok) { setPage("verify-forgot") setOtp("") setMsg("OTP sent to your email") } else { setMsg(data.detail || "Failed to send OTP") } } catch (err) { setMsg("Network error. Is the backend running?") } } async function resetPassword() { try { if (otp.length !== 6) { setMsg("Enter the 6-digit OTP"); return } if (form.password.length < 8) { setMsg("Password must be at least 8 characters"); return } const res = await fetch(`${API}/auth/reset-password`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: form.email, code: otp, new_password: form.password }) }) const data = await res.json() if (res.ok) { setMsg("Password reset successful!") navigateTo("login") } else { setMsg(data.detail || "Reset failed") } } catch (err) { setMsg("Network error. Is the backend running?") } } return (
{page === "login" && (

Login

{msg &&

{msg}

} setForm({ ...form, username: e.target.value })} /> setForm({ ...form, password: e.target.value })} />

navigateTo("register")}>No account? Register

navigateTo("forgot-password")}>Forgot password?

)} {page === "register" && (

Register

{msg &&

{msg}

} setForm({ ...form, username: e.target.value })} /> setForm({ ...form, email: e.target.value })} /> setForm({ ...form, password: e.target.value })} />

navigateTo("login")}>Have account? Login

)} {page === "verify-register" && (

Verify Account

{msg &&

{msg}

}

OTP sent to: {form.email || "your email"}

setOtp(e.target.value)} />
)} {page === "forgot-password" && (

Forgot Password

{msg &&

{msg}

} setForm({ ...form, email: e.target.value })} />

navigateTo("login")}>Back to Login

)} {page === "verify-forgot" && (

Reset Password

{msg &&

{msg}

}

OTP sent to: {form.email || "your email"}

setOtp(e.target.value)} /> setForm({ ...form, password: e.target.value })} />

navigateTo("forgot-password")}>Back

)} {page === "dashboard" && (

Dashboard

ID: {user?.id}

Username: {user?.username}

Email: {user?.email}

)}
) } export default App