-
diff --git a/frontend/src/pages/ProfilePage.tsx b/frontend/src/pages/ProfilePage.tsx
index 3737939..af84c2c 100644
--- a/frontend/src/pages/ProfilePage.tsx
+++ b/frontend/src/pages/ProfilePage.tsx
@@ -1,32 +1,59 @@
-import React, { useEffect, useState } from "react";
+import React, { useEffect, useRef, useState } from "react";
import { motion } from "framer-motion";
-import { User, Mail, Save, CheckCircle, AlertCircle, Loader2 } from "lucide-react";
+import { Mail, Save, CheckCircle, AlertCircle, Loader2, Camera, User } from "lucide-react";
import { useAuth } from "../contexts/AuthContext";
import { userService } from "../services/userService";
import { cn } from "../lib/utils";
+const MAX_PHOTO_SIZE = 5 * 1024 * 1024;
+
export const ProfilePage: React.FC = () => {
const { user, refreshUser } = useAuth();
const [name, setName] = useState(user?.name ?? "");
const [email, setEmail] = useState(user?.email ?? "");
+ const [pendingPhoto, setPendingPhoto] = useState(null);
+ const [photoPreview, setPhotoPreview] = useState(null);
const [isSaving, setIsSaving] = useState(false);
const [successMsg, setSuccessMsg] = useState(null);
const [errorMsg, setErrorMsg] = useState(null);
+ const fileInputRef = useRef(null);
useEffect(() => {
setName(user?.name ?? "");
setEmail(user?.email ?? "");
}, [user]);
- const initials = (name || user?.name || "?")
+ const currentPhoto = photoPreview ?? user?.photo ?? null;
+ const displayInitials = (name || user?.name || "?")
.split(" ")
.slice(0, 2)
.map((w) => w[0])
.join("")
.toUpperCase();
- const hasChanges = name !== user?.name || email !== user?.email;
+ const hasChanges =
+ name !== user?.name || email !== user?.email || pendingPhoto !== null;
+
+ const handleFileChange = (e: React.ChangeEvent) => {
+ const file = e.target.files?.[0];
+ if (!file) return;
+
+ if (file.size > MAX_PHOTO_SIZE) {
+ setErrorMsg("A foto deve ter no máximo 5 MB.");
+ e.target.value = "";
+ return;
+ }
+ if (!file.type.startsWith("image/")) {
+ setErrorMsg("Selecione um arquivo de imagem.");
+ e.target.value = "";
+ return;
+ }
+
+ setErrorMsg(null);
+ setPendingPhoto(file);
+ setPhotoPreview(URL.createObjectURL(file));
+ };
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
@@ -37,8 +64,13 @@ export const ProfilePage: React.FC = () => {
setErrorMsg(null);
try {
+ if (pendingPhoto) {
+ await userService.uploadPhoto(pendingPhoto);
+ }
await userService.updateProfile({ name: name.trim(), email: email.trim() });
await refreshUser();
+ setPendingPhoto(null);
+ setPhotoPreview(null);
setSuccessMsg("Perfil atualizado com sucesso!");
} catch (err: unknown) {
const message = err instanceof Error ? err.message : "Erro ao atualizar perfil.";
@@ -63,14 +95,44 @@ export const ProfilePage: React.FC = () => {
- {/* Avatar */}
+ {/* Avatar com upload */}
-
- {initials}
-
+
+
+
+
{user?.name}
{user?.email}
+
+ Clique na foto para alterar · máx. 5 MB
+
diff --git a/frontend/src/services/userService.ts b/frontend/src/services/userService.ts
index ce6171c..c001343 100644
--- a/frontend/src/services/userService.ts
+++ b/frontend/src/services/userService.ts
@@ -11,6 +11,10 @@ export interface UpdateProfileResult {
photo: string
}
+export interface UploadPhotoResult {
+ photoUrl: string
+}
+
export const userService = {
updateProfile: async (payload: UpdateProfilePayload): Promise
=> {
return apiFetch('/auth/profile', {
@@ -18,4 +22,13 @@ export const userService = {
body: payload,
})
},
+
+ uploadPhoto: async (file: File): Promise => {
+ const form = new FormData()
+ form.append('photo', file)
+ return apiFetch('/auth/profile/photo', {
+ method: 'POST',
+ body: form,
+ })
+ },
}