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
);