From ea59c45a76eccd1147ee6637b1681d0928859e4e Mon Sep 17 00:00:00 2001 From: Alexander Ng Date: Sat, 10 Jan 2026 02:53:53 -0800 Subject: [PATCH] feat: auth --- package.json | 4 ++- scripts/watch-openapi.ts | 29 +++++++++++++++++ src/App.tsx | 19 +++++++++-- src/hooks/{useAuth.ts => useAuth.tsx} | 45 ++++++++++++++++++++++----- src/lib/api.ts | 2 +- src/pages/login/Login.tsx | 37 ++++++++++++++++++---- src/pages/settings/Settings.tsx | 4 +++ 7 files changed, 121 insertions(+), 19 deletions(-) create mode 100644 scripts/watch-openapi.ts rename src/hooks/{useAuth.ts => useAuth.tsx} (65%) diff --git a/package.json b/package.json index 3725f5f..ccdf022 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,9 @@ "version": "0.0.1", "type": "module", "scripts": { - "dev": "vite", + "dev": "bun scripts/watch-openapi.ts & vite", + "dev:vite": "vite", + "dev:watch": "bun scripts/watch-openapi.ts", "build": "tsc && vite build", "preview": "vite preview", "test.e2e": "cypress run", diff --git a/scripts/watch-openapi.ts b/scripts/watch-openapi.ts new file mode 100644 index 0000000..9591e67 --- /dev/null +++ b/scripts/watch-openapi.ts @@ -0,0 +1,29 @@ +import { watch } from "node:fs"; +import { spawn } from "bun"; + +const file = "openapi.json"; + +console.log(`Watching ${file} for changes...`); + +// Run postinstall once on startup with --force to ensure client is up to date +await spawn(["bun", "scripts/postinstall.ts", "--force"], { + stdio: ["inherit", "inherit", "inherit"], +}).exited; + +let debounceTimer: Timer | null = null; + +watch(file, async (eventType) => { + if (eventType !== "change") return; + + // Debounce rapid changes + if (debounceTimer) clearTimeout(debounceTimer); + debounceTimer = setTimeout(async () => { + console.log(`\n${file} changed, regenerating client...`); + await spawn(["bun", "scripts/postinstall.ts", "--force"], { + stdio: ["inherit", "inherit", "inherit"], + }).exited; + }, 100); +}); + +// Keep the process running +await new Promise(() => {}); diff --git a/src/App.tsx b/src/App.tsx index 930f96a..8af1032 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -44,8 +44,9 @@ import Settings from "./pages/settings/Settings"; import Stats from "./pages/stats/Stats"; import Workout from "./pages/workout/Workout"; -import { useAuth } from "./hooks/useAuth"; +import { AuthProvider, useAuth } from "./hooks/useAuth"; import Login from "./pages/login/Login"; +import { useEffect } from "react"; setupIonicReact(); @@ -105,14 +106,18 @@ function UnauthenticatedRouter() { - + } /> ); } -function App() { +function AppContent() { const { isAuthenticated, isLoading } = useAuth(); + useEffect(() => { + console.log("isAuthenticated", isAuthenticated); + }, [isAuthenticated]); + return ( @@ -123,4 +128,12 @@ function App() { ); } +function App() { + return ( + + + + ); +} + export default App; diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.tsx similarity index 65% rename from src/hooks/useAuth.ts rename to src/hooks/useAuth.tsx index 6fcd2d4..e4e53a3 100644 --- a/src/hooks/useAuth.ts +++ b/src/hooks/useAuth.tsx @@ -1,4 +1,11 @@ -import { useCallback, useEffect, useState } from "react"; +import { + createContext, + useCallback, + useContext, + useEffect, + useState, + type ReactNode, +} from "react"; import { clearTokens, getAccessToken, @@ -13,7 +20,15 @@ interface AuthState { refreshToken: string | null; } -export function useAuth() { +interface AuthContextValue extends AuthState { + login: (accessToken: string, refreshToken?: string) => Promise; + logout: () => Promise; + checkAuth: () => Promise; +} + +const AuthContext = createContext(null); + +export function AuthProvider({ children }: { children: ReactNode }) { const [state, setState] = useState({ isAuthenticated: false, isLoading: true, @@ -71,10 +86,24 @@ export function useAuth() { }); }, []); - return { - ...state, - login, - logout, - checkAuth, - }; + return ( + + {children} + + ); +} + +export function useAuth() { + const context = useContext(AuthContext); + if (!context) { + throw new Error("useAuth must be used within an AuthProvider"); + } + return context; } diff --git a/src/lib/api.ts b/src/lib/api.ts index 74d2adc..7f8be86 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -2,7 +2,7 @@ import { client } from "./api-openapi-gen/client.gen"; client.setConfig({ baseUrl: "http://localhost:8080", - credentials: "include", + // credentials: "include", }); export * as api from "./api-openapi-gen/sdk.gen"; diff --git a/src/pages/login/Login.tsx b/src/pages/login/Login.tsx index be79923..bf64d97 100644 --- a/src/pages/login/Login.tsx +++ b/src/pages/login/Login.tsx @@ -10,8 +10,11 @@ import { } from "@ionic/react"; import { useCallback, useState } from "react"; import "./Login.css"; +import { api } from "../../lib/api"; +import { useAuth } from "../../hooks/useAuth"; export default function Login() { + const { login } = useAuth(); const [isRegistering, setIsRegistering] = useState(false); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); @@ -20,13 +23,35 @@ export default function Login() { const title = isRegistering ? "Register" : "Login"; - const handleLogin = useCallback(() => { - console.log("login", username, password); - }, [username, password]); + const handleLogin = useCallback(async () => { + const { data, error } = await api.login({ + body: { + username, + password, + }, + }); + if (error || !data) { + console.error("Failed to login", error); + return; + } + await login(data); + }, [username, password, login]); - const handleRegister = useCallback(() => { - console.log("register", username, password, realName, email); - }, [username, password, realName, email]); + const handleRegister = useCallback(async () => { + const { data, error } = await api.signup({ + body: { + username, + password, + real_name: realName, + email, + }, + }); + if (error || !data) { + console.error("Failed to register", error); + return; + } + await login(data); + }, [username, password, realName, email, login]); const handleClick = useCallback(() => { if (isRegistering) { diff --git a/src/pages/settings/Settings.tsx b/src/pages/settings/Settings.tsx index ca8eb02..def8499 100644 --- a/src/pages/settings/Settings.tsx +++ b/src/pages/settings/Settings.tsx @@ -1,4 +1,5 @@ import { + IonButton, IonContent, IonHeader, IonPage, @@ -7,8 +8,10 @@ import { } from "@ionic/react"; import ExploreContainer from "../../components/ExploreContainer"; import "./Settings.css"; +import { useAuth } from "../../hooks/useAuth"; export default function Settings() { + const { logout } = useAuth(); return ( @@ -23,6 +26,7 @@ export default function Settings() { + Logout );