diff --git a/src/blog/components/Profile.tsx b/src/blog/components/Profile.tsx index 72b0aa2..b300c1e 100644 --- a/src/blog/components/Profile.tsx +++ b/src/blog/components/Profile.tsx @@ -10,6 +10,7 @@ import { } from '@mui/material'; import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded'; import { useAuth } from '../providers/Author'; +import { useUpload } from "../providers/Upload"; import ImageUploadField from './ImageUploadField'; interface ProfileProps { @@ -17,7 +18,8 @@ interface ProfileProps { } export default function Profile({ onBack }: ProfileProps) { - const { currentUser, loading, error, logout, updateProfile, updateAvatar } = useAuth(); + const { currentUser, loading, error, logout, updateProfile } = useAuth(); + const { uploadFile } = useUpload(); const [formData, setFormData] = React.useState({ username: currentUser?.username || '', name: currentUser?.name || '', @@ -44,9 +46,9 @@ export default function Profile({ onBack }: ProfileProps) { setUploadingAvatar(true); try { - const updated = await updateAvatar(file); - if (updated) { - setFormData((prev) => ({ ...prev, avatar: updated.avatar })); + const avatar = await uploadFile(file); + if (avatar) { + setFormData((prev) => ({ ...prev, avatar: avatar })); } } catch (err) { console.error("Avatar upload failed:", err); diff --git a/src/blog/providers/Author.tsx b/src/blog/providers/Author.tsx index 7c769f6..1df7c07 100644 --- a/src/blog/providers/Author.tsx +++ b/src/blog/providers/Author.tsx @@ -112,51 +112,6 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children } }; - /** -------------------------------------------- - * 🔹 Upload avatar binary → return URL - * -------------------------------------------- */ - const uploadAvatar = async (file: File): Promise => { - try { - const arrayBuffer = await file.arrayBuffer(); - const binary = new Uint8Array(arrayBuffer); - - const res = await api.post( - "/uploads", - binary, - { - headers: { - "Content-Type": file.type, - "Content-Disposition": `attachment; filename="${file.name}"`, - }, - } - ); - - return res.data.url; - } catch (err: any) { - console.error("Avatar upload failed:", err); - setError(err.response?.data?.detail || "Failed to upload avatar"); - return null; - } - }; - - /** -------------------------------------------- - * 🔹 Full flow: upload avatar → update profile - * -------------------------------------------- */ - const updateAvatar = async (file: File) => { - if (!currentUser) return; - - const url = await uploadAvatar(file); - if (!url) return; - - // Now update the author document in DB - const updatedUser = await updateProfile({ - ...currentUser, - avatar: url, - }); - - return updatedUser; - }; - /** 🔹 On mount, try to fetch user if token exists */ useEffect(() => { if (token) fetchCurrentUser(); @@ -175,8 +130,6 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children register, refreshAuthors, updateProfile, - uploadAvatar, - updateAvatar, }} > {children} diff --git a/src/blog/providers/Upload.tsx b/src/blog/providers/Upload.tsx new file mode 100644 index 0000000..a6c4b7c --- /dev/null +++ b/src/blog/providers/Upload.tsx @@ -0,0 +1,56 @@ +import React, { createContext, useContext, useState } from "react"; +import { api } from "../utils/api"; +import { UploadContextModel } from "../types/contexts"; + +const UploadContext = createContext(undefined); + +export const UploadProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [uploading, setUploading] = useState(false); + const [error, setError] = useState(null); + + /** + * 🔹 Upload any file → return public URL + */ + const uploadFile = async (file: File): Promise => { + setUploading(true); + setError(null); + + try { + const arrayBuffer = await file.arrayBuffer(); + const binary = new Uint8Array(arrayBuffer); + + const res = await api.post("/uploads", binary, { + headers: { + "Content-Type": file.type, + "Content-Disposition": `attachment; filename="${file.name}"`, + }, + }); + + return res.data.url as string; + } catch (err: any) { + console.error("File upload failed:", err); + setError(err.response?.data?.detail || "Failed to upload file"); + return null; + } finally { + setUploading(false); + } + }; + + return ( + + {children} + + ); +}; + +export const useUpload = (): UploadContextModel => { + const ctx = useContext(UploadContext); + if (!ctx) throw new Error("useUpload must be used within UploadProvider"); + return ctx; +}; diff --git a/src/blog/types/contexts.ts b/src/blog/types/contexts.ts index e1f07c2..9a46603 100644 --- a/src/blog/types/contexts.ts +++ b/src/blog/types/contexts.ts @@ -18,6 +18,10 @@ export interface AuthContextModel { logout: () => void; refreshAuthors: () => Promise; updateProfile: (user: AuthorModel) => Promise; - uploadAvatar: (file: File) => Promise; - updateAvatar: (file: File) => Promise; +} + +export interface UploadContextModel { + uploadFile: (file: File) => Promise; + uploading: boolean; + error: string | null; } diff --git a/src/main.jsx b/src/main.jsx index d93e38c..3d2ff48 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -3,16 +3,19 @@ import { createRoot } from 'react-dom/client'; import Blog from './blog/Blog'; import { ArticleProvider } from './blog/providers/Article'; import { AuthProvider } from './blog/providers/Author'; +import { UploadProvider } from "./blog/providers/Upload"; const rootElement = document.getElementById('root'); const root = createRoot(rootElement); root.render( - - - - - + + + + + + + , );