130 lines
3.2 KiB
JavaScript
130 lines
3.2 KiB
JavaScript
"use client";
|
|
|
|
import { createContext, useContext, useState, useEffect, useCallback } from "react";
|
|
|
|
const AuthContext = createContext(null);
|
|
|
|
const TOKEN_KEY = "auth_token";
|
|
|
|
async function fetchCurrentUser(token) {
|
|
const res = await fetch("/api/v1/auth/me", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
if (!res.ok) return null;
|
|
return res.json();
|
|
}
|
|
|
|
export function AuthProvider({ children }) {
|
|
const [user, setUser] = useState(null);
|
|
const [token, setToken] = useState(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
const refreshUser = useCallback(async (providedToken) => {
|
|
const activeToken = providedToken ?? token;
|
|
if (!activeToken) {
|
|
setUser(null);
|
|
return null;
|
|
}
|
|
|
|
const userInfo = await fetchCurrentUser(activeToken);
|
|
if (!userInfo) {
|
|
localStorage.removeItem(TOKEN_KEY);
|
|
setToken(null);
|
|
setUser(null);
|
|
return null;
|
|
}
|
|
|
|
if (!token) {
|
|
setToken(activeToken);
|
|
}
|
|
setUser(userInfo);
|
|
return userInfo;
|
|
}, [token]);
|
|
|
|
useEffect(() => {
|
|
const stored = localStorage.getItem(TOKEN_KEY);
|
|
if (!stored) {
|
|
setLoading(false);
|
|
|
|
return;
|
|
}
|
|
refreshUser(stored)
|
|
.catch(() => {
|
|
localStorage.removeItem(TOKEN_KEY);
|
|
setToken(null);
|
|
setUser(null);
|
|
})
|
|
.finally(() => setLoading(false));
|
|
}, [refreshUser]);
|
|
|
|
const login = useCallback(async (username, password) => {
|
|
const res = await fetch("/api/v1/auth/login", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ username, password }),
|
|
});
|
|
|
|
const data = await res.json();
|
|
|
|
if (!res.ok) {
|
|
throw new Error(data.message || "Login failed");
|
|
}
|
|
|
|
const jwt = data.token;
|
|
localStorage.setItem(TOKEN_KEY, jwt);
|
|
setToken(jwt);
|
|
|
|
const userInfo = await refreshUser(jwt);
|
|
|
|
return userInfo;
|
|
}, [refreshUser]);
|
|
|
|
const register = useCallback(async ({ username, password, email, firstName, lastName, phone }) => {
|
|
const res = await fetch("/api/v1/auth/register", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ username, password, email, firstName, lastName, phone }),
|
|
});
|
|
const data = await res.json();
|
|
|
|
if (!res.ok) {
|
|
if (data.errors && typeof data.errors === "object") {
|
|
const fieldErrors = Object.entries(data.errors)
|
|
.map(([field, msg]) => `${field}: ${msg}`)
|
|
.join(", ");
|
|
throw new Error(fieldErrors || data.message || "Registration failed");
|
|
}
|
|
throw new Error(data.message || "Registration failed");
|
|
}
|
|
|
|
const jwt = data.token;
|
|
|
|
localStorage.setItem(TOKEN_KEY, jwt);
|
|
setToken(jwt);
|
|
|
|
const userInfo = await refreshUser(jwt);
|
|
|
|
return userInfo;
|
|
}, [refreshUser]);
|
|
|
|
const logout = useCallback(() => {
|
|
localStorage.removeItem(TOKEN_KEY);
|
|
setToken(null);
|
|
setUser(null);}, []);
|
|
|
|
return (
|
|
<AuthContext.Provider value={{ user, token, loading, login, logout, register, refreshUser }}>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useAuth() {
|
|
const ctx = useContext(AuthContext);
|
|
if (!ctx) {
|
|
throw new Error("useAuth must be used within an AuthProvider");
|
|
}
|
|
|
|
return ctx;
|
|
}
|