Files
blog/src/blog/providers/Author.tsx
Vishesh 'ironeagle' Bangotra 226a6a651c Auth Package Extraction And Auth Flow Refactor (#2)
Reviewed-on: #2
Co-authored-by: Vishesh 'ironeagle' Bangotra <aetoskia@gmail.com>
Co-committed-by: Vishesh 'ironeagle' Bangotra <aetoskia@gmail.com>
2025-12-28 14:47:37 +00:00

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