From 8a29261a3e706de0dea0b1a733b86192c95e32db Mon Sep 17 00:00:00 2001 From: Vishesh 'ironeagle' Bangotra Date: Tue, 11 Nov 2025 20:47:37 +0530 Subject: [PATCH] profile and update view for author --- src/blog/Blog.tsx | 53 +++++++--- src/blog/components/Profile.tsx | 166 ++++++++++++++++++++++++++++++++ src/blog/providers/Author.tsx | 34 ++++++- src/blog/types/contexts.ts | 1 + 4 files changed, 239 insertions(+), 15 deletions(-) create mode 100644 src/blog/components/Profile.tsx diff --git a/src/blog/Blog.tsx b/src/blog/Blog.tsx index e189fa1..397a3ad 100644 --- a/src/blog/Blog.tsx +++ b/src/blog/Blog.tsx @@ -10,10 +10,11 @@ import Latest from './components/Latest'; import Footer from './components/Footer'; import Login from './components/Login'; import Register from './components/Register'; +import Profile from './components/Profile'; import { useArticles } from './providers/Article'; import { useAuth } from './providers/Author'; -type View = 'home' | 'login' | 'register' | 'article'; +type View = 'home' | 'login' | 'register' | 'article' | 'profile'; export default function Blog(props: { disableCustomTheme?: boolean }) { const { articles, loading, error } = useArticles(); @@ -22,6 +23,7 @@ export default function Blog(props: { disableCustomTheme?: boolean }) { const [selectedArticle, setSelectedArticle] = React.useState(null); const [showLogin, setShowLogin] = React.useState(false); const [showRegister, setShowRegister] = React.useState(false); + const [showProfile, setShowProfile] = React.useState(false); const handleSelectArticle = (index: number) => { setSelectedArticle(index); @@ -44,14 +46,22 @@ export default function Blog(props: { disableCustomTheme?: boolean }) { setShowLogin(false); setShowRegister(false); }; + const handleShowProfile = () => { + setShowProfile(true); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }; + const handleHideProfile = () => { + setShowProfile(false); + }; // derive a single source of truth for view const view: View = React.useMemo(() => { if (selectedArticle !== null) return 'article'; if (showRegister) return 'register'; if (showLogin) return 'login'; + if (showProfile) return 'profile'; return 'home'; - }, [selectedArticle, showLogin, showRegister]); + }, [selectedArticle, showLogin, showRegister, showProfile]); // render function keeps JSX tidy const renderView = () => { @@ -67,6 +77,12 @@ export default function Blog(props: { disableCustomTheme?: boolean }) { }} /> ); + case 'profile': + return ( + + ); case 'article': if (selectedArticle == null || !articles[selectedArticle]) return null; return
; @@ -74,16 +90,29 @@ export default function Blog(props: { disableCustomTheme?: boolean }) { default: return ( <> - {!currentUser && ( - - - - )} + + {!currentUser ? ( + <> + + + ) : ( + <> + + + )} + void; +} + +export default function Profile({ onBack }: ProfileProps) { + const { currentUser, loading, error, token, refreshAuthors, updateProfile } = useAuth(); + const [formData, setFormData] = React.useState({ + username: currentUser?.username || '', + name: currentUser?.name || '', + email: currentUser?.email || '', + avatar: currentUser?.avatar || '', + }); + const [success, setSuccess] = React.useState(null); + const [saving, setSaving] = React.useState(false); + + React.useEffect(() => { + if (currentUser) setFormData(currentUser); + }, [currentUser]); + + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData((prev) => ({ ...prev, [name]: value })); + }; + + const handleSave = async () => { + if (!currentUser) return; + + try { + setSaving(true); + setSuccess(null); + + const updatedUser = { ...currentUser, ...formData }; + console.log('updatedUser'); + console.log(updatedUser); + const updated = await updateProfile(updatedUser); + + if (updated) setSuccess('Profile updated successfully'); + } catch (err: any) { + console.error('Failed to update profile:', err); + } finally { + setSaving(false); + } + }; + + if (!currentUser) { + return ( + + + You must be logged in to view your profile. + + + + ); + } + + return ( + + + + + + + Profile + + + + + + + + + + + + {error && ( + + {error} + + )} + {success && ( + + {success} + + )} + + + + ); +} diff --git a/src/blog/providers/Author.tsx b/src/blog/providers/Author.tsx index b212a5a..e5d1dad 100644 --- a/src/blog/providers/Author.tsx +++ b/src/blog/providers/Author.tsx @@ -74,13 +74,40 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children } }; + /** 🔹 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(`/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); + } + }; + + /** 🔹 Auto-load current user if token exists */ const fetchCurrentUser = async () => { if (!token) return; try { - const res = await api.get('/auth/me'); - setCurrentUser(res.data); - } catch (err: any) { + const me = await api.get<{ _id: string; username: string; email: string }>('/auth/me'); + + const author = await api.get(`/authors/${me.data._id}`); + + const fullUser = { ...me.data, ...author.data }; + + setCurrentUser(fullUser); + } catch (err) { console.error('Failed to fetch current user:', err); logout(); // invalid/expired token } @@ -103,6 +130,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children logout, register, refreshAuthors, + updateProfile, }} > {children} diff --git a/src/blog/types/contexts.ts b/src/blog/types/contexts.ts index 2100ccd..0aab367 100644 --- a/src/blog/types/contexts.ts +++ b/src/blog/types/contexts.ts @@ -17,4 +17,5 @@ export interface AuthContextModel { register: (username: string, password: string) => Promise; logout: () => void; refreshAuthors: () => Promise; + updateProfile: (user: AuthorModel) => Promise; }