Reviewed-on: #2 Co-authored-by: Vishesh 'ironeagle' Bangotra <aetoskia@gmail.com> Co-committed-by: Vishesh 'ironeagle' Bangotra <aetoskia@gmail.com>
120 lines
3.1 KiB
TypeScript
120 lines
3.1 KiB
TypeScript
import React, { createContext, useState, useEffect, useContext } from "react";
|
|
import { api } from "../utils/api";
|
|
import { AuthorModel } from "../types/models";
|
|
import { AuthContextModel } from "../types/contexts";
|
|
import { useAuth as useBaseAuth } from "../../../auth/src";
|
|
|
|
const AuthContext = createContext<AuthContextModel | undefined>(undefined);
|
|
|
|
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
const { currentUser: authUser, logout } = useBaseAuth();
|
|
const [currentUser, setCurrentUser] = useState<AuthorModel | null>(null);
|
|
const [authors, setAuthors] = useState<AuthorModel[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
/**
|
|
* Hydrate application-level currentUser
|
|
*/
|
|
const hydrateCurrentUser = async () => {
|
|
if (!authUser) return;
|
|
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
const res = await api.get<AuthorModel>("/authors/me");
|
|
|
|
/**
|
|
* Explicit precedence:
|
|
* Auth service is source of truth for inherited fields
|
|
*/
|
|
const fullUser: AuthorModel = {
|
|
...res.data,
|
|
username: authUser.username,
|
|
email: authUser.email,
|
|
is_active: authUser.is_active,
|
|
};
|
|
|
|
setCurrentUser(fullUser);
|
|
} catch (err) {
|
|
console.error("Failed to hydrate current user:", err);
|
|
logout();
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
/** 🔹 Fetch all authors (JWT handled by api interceptor) */
|
|
const refreshAuthors = async () => {
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
const res = await api.get<AuthorModel[]>('/authors');
|
|
setAuthors(res.data);
|
|
} catch (err: any) {
|
|
console.error('Failed to fetch authors:', err);
|
|
setError(err.response?.data?.detail || 'Failed to fetch authors');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
/** 🔹 Update current user (full model) */
|
|
const updateProfile = async (userData: AuthorModel) => {
|
|
if (!userData._id) {
|
|
console.error('updateProfile called without _id');
|
|
return;
|
|
}
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
const res = await api.put<AuthorModel>(`/authors/${userData._id}`, userData);
|
|
setCurrentUser(res.data);
|
|
return res.data;
|
|
} catch (err: any) {
|
|
console.error('Profile update failed:', err);
|
|
setError(err.response?.data?.detail || 'Failed to update profile');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* React strictly to auth lifecycle
|
|
*/
|
|
useEffect(() => {
|
|
if (authUser) {
|
|
hydrateCurrentUser();
|
|
} else {
|
|
setCurrentUser(null);
|
|
setAuthors([]);
|
|
setError(null);
|
|
}
|
|
}, [authUser]);
|
|
|
|
return (
|
|
<AuthContext.Provider
|
|
value={{
|
|
currentUser,
|
|
authors,
|
|
loading,
|
|
error,
|
|
|
|
refreshAuthors,
|
|
updateProfile,
|
|
}}
|
|
>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
};
|
|
|
|
export const useAuth = (): AuthContextModel => {
|
|
const ctx = useContext(AuthContext);
|
|
if (!ctx) throw new Error('useAuth must be used inside AuthProvider');
|
|
return ctx;
|
|
};
|