feat: auth
This commit is contained in:
@@ -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",
|
||||
|
||||
29
scripts/watch-openapi.ts
Normal file
29
scripts/watch-openapi.ts
Normal file
@@ -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(() => {});
|
||||
19
src/App.tsx
19
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() {
|
||||
<Route exact path="/login">
|
||||
<Login />
|
||||
</Route>
|
||||
<Redirect exact from="/" to="/login" />
|
||||
<Route render={() => <Redirect to="/login" />} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
function AppContent() {
|
||||
const { isAuthenticated, isLoading } = useAuth();
|
||||
|
||||
useEffect(() => {
|
||||
console.log("isAuthenticated", isAuthenticated);
|
||||
}, [isAuthenticated]);
|
||||
|
||||
return (
|
||||
<IonApp>
|
||||
<IonReactRouter>
|
||||
@@ -123,4 +128,12 @@ function App() {
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<AuthProvider>
|
||||
<AppContent />
|
||||
</AuthProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -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<void>;
|
||||
logout: () => Promise<void>;
|
||||
checkAuth: () => Promise<void>;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextValue | null>(null);
|
||||
|
||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [state, setState] = useState<AuthState>({
|
||||
isAuthenticated: false,
|
||||
isLoading: true,
|
||||
@@ -71,10 +86,24 @@ export function useAuth() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
...state,
|
||||
login,
|
||||
logout,
|
||||
checkAuth,
|
||||
};
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
...state,
|
||||
login,
|
||||
logout,
|
||||
checkAuth,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useAuth() {
|
||||
const context = useContext(AuthContext);
|
||||
if (!context) {
|
||||
throw new Error("useAuth must be used within an AuthProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
@@ -23,6 +26,7 @@ export default function Settings() {
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<ExploreContainer name="Settings page" />
|
||||
<IonButton onClick={logout}>Logout</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user