From 3f77c52bcd5da299b77cef5ad102740d4d10f661 Mon Sep 17 00:00:00 2001 From: Gabriel Lira Figueira Date: Sun, 9 Nov 2025 21:10:51 -0300 Subject: [PATCH 1/2] =?UTF-8?q?refactor(auth):=20Centraliza=20l=C3=B3gica?= =?UTF-8?q?=20de=20autentica=C3=A7=C3=A3o=20e=20corrige=20avatares?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Cria o hook customizado 'useAuthLayout' para gerenciar os dados do usuário e as permissões de acesso de forma centralizada. - Refatora todos os layouts (Manager, Doctor, Secretary, Patient, etc.) para utilizar o novo hook, simplificando o código e eliminando repetição. - Corrige o bug no fluxo de login de múltiplos perfis, garantindo que a role seja salva corretamente em minúsculas. - Implementa a exibição correta do avatar do usuário em todos os layouts, corrigindo a montagem da URL do Supabase Storage. - Corrige o erro de CORS no upload de avatar na página de perfil do paciente, utilizando a API REST para atualizar a tabela 'profiles' diretamente. - Adiciona a funcionalidade completa de edição de dados e troca de foto na página 'Meus Dados' do paciente. --- app/patient/profile/page.tsx | 309 ++++++++++---------- components/LoginForm.tsx | 72 +++-- components/doctor-layout.tsx | 480 +++++--------------------------- components/finance-layout.tsx | 230 +++------------ components/hospital-layout.tsx | 210 ++++---------- components/manager-layout.tsx | 210 +++----------- components/patient-layout.tsx | 243 +++------------- components/secretary-layout.tsx | 232 +++------------ components/ui/button.tsx | 56 ++-- hooks/useAuthLayout.ts | 75 +++++ services/api.mjs | 21 ++ 11 files changed, 574 insertions(+), 1564 deletions(-) create mode 100644 hooks/useAuthLayout.ts diff --git a/app/patient/profile/page.tsx b/app/patient/profile/page.tsx index 0d92b0c..2dfa00b 100644 --- a/app/patient/profile/page.tsx +++ b/app/patient/profile/page.tsx @@ -1,52 +1,127 @@ -"use client" +// ARQUIVO COMPLETO PARA: app/patient/profile/page.tsx -import { useState, useEffect } from "react" -import PatientLayout from "@/components/patient-layout" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" -import { Textarea } from "@/components/ui/textarea" -import { User, Mail, Phone, Calendar, FileText } from "lucide-react" +"use client"; -interface PatientData { - name: string - email: string - phone: string - cpf: string - birthDate: string - address: string +import { useState, useEffect, useRef } from "react"; +import PatientLayout from "@/components/patient-layout"; +import { useAuthLayout } from "@/hooks/useAuthLayout"; +import { patientsService } from "@/services/patientsApi.mjs"; +import { api } from "@/services/api.mjs"; + +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { User, Mail, Phone, Calendar, Upload } from "lucide-react"; +import { toast } from "@/hooks/use-toast"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; + +interface PatientProfileData { + name: string; + email: string; + phone: string; + cpf: string; + birthDate: string; + cep: string; + street: string; + number: string; + city: string; + avatarFullUrl?: string; } export default function PatientProfile() { - const [patientData, setPatientData] = useState({ - name: "", - email: "", - phone: "", - cpf: "", - birthDate: "", - address: "", - }) - const [isEditing, setIsEditing] = useState(false) + const { user, isLoading: isAuthLoading } = useAuthLayout({ requiredRole: 'patient' }); + const [patientData, setPatientData] = useState(null); + const [isEditing, setIsEditing] = useState(false); + const [isSaving, setIsSaving] = useState(false); + const fileInputRef = useRef(null); useEffect(() => { - const data = localStorage.getItem("patientData") - if (data) { - setPatientData(JSON.parse(data)) + if (user?.id) { + const fetchPatientDetails = async () => { + try { + const patientDetails = await patientsService.getById(user.id); + setPatientData({ + name: patientDetails.full_name || user.name, + email: user.email, + phone: patientDetails.phone_mobile || '', + cpf: patientDetails.cpf || '', + birthDate: patientDetails.birth_date || '', + cep: patientDetails.cep || '', + street: patientDetails.street || '', + number: patientDetails.number || '', + city: patientDetails.city || '', + avatarFullUrl: user.avatarFullUrl, + }); + } catch (error) { + console.error("Erro ao buscar detalhes do paciente:", error); + toast({ title: "Erro", description: "Não foi possível carregar seus dados completos.", variant: "destructive" }); + } + }; + fetchPatientDetails(); } - }, []) + }, [user]); - const handleSave = () => { - localStorage.setItem("patientData", JSON.stringify(patientData)) - setIsEditing(false) - alert("Dados atualizados com sucesso!") - } + const handleInputChange = (field: keyof PatientProfileData, value: string) => { + setPatientData((prev) => (prev ? { ...prev, [field]: value } : null)); + }; - const handleInputChange = (field: keyof PatientData, value: string) => { - setPatientData((prev) => ({ - ...prev, - [field]: value, - })) + const handleSave = async () => { + if (!patientData || !user) return; + setIsSaving(true); + try { + const patientPayload = { + full_name: patientData.name, + cpf: patientData.cpf, + birth_date: patientData.birthDate, + phone_mobile: patientData.phone, + cep: patientData.cep, + street: patientData.street, + number: patientData.number, + city: patientData.city, + }; + await patientsService.update(user.id, patientPayload); + toast({ title: "Sucesso!", description: "Seus dados foram atualizados." }); + setIsEditing(false); + } catch (error) { + console.error("Erro ao salvar dados:", error); + toast({ title: "Erro", description: "Não foi possível salvar suas alterações.", variant: "destructive" }); + } finally { + setIsSaving(false); + } + }; + + const handleAvatarClick = () => { + fileInputRef.current?.click(); + }; + + const handleAvatarUpload = async (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (!file || !user) return; + + const fileExt = file.name.split('.').pop(); + + // *** A CORREÇÃO ESTÁ AQUI *** + // O caminho salvo no banco de dados não deve conter o nome do bucket. + const filePath = `${user.id}/avatar.${fileExt}`; + + try { + await api.storage.upload('avatars', filePath, file); + await api.patch(`/rest/v1/profiles?id=eq.${user.id}`, { avatar_url: filePath }); + + const newFullUrl = `https://yuanqfswhberkoevtmfr.supabase.co/storage/v1/object/public/avatars/${filePath}?t=${new Date().getTime()}`; + setPatientData(prev => prev ? { ...prev, avatarFullUrl: newFullUrl } : null); + + toast({ title: "Sucesso!", description: "Sua foto de perfil foi atualizada." }); + } catch (error) { + console.error("Erro no upload do avatar:", error); + toast({ title: "Erro de Upload", description: "Não foi possível enviar sua foto.", variant: "destructive" }); + } + }; + + if (isAuthLoading || !patientData) { + return
Carregando seus dados...
; } return ( @@ -57,99 +132,37 @@ export default function PatientProfile() {

Meus Dados

Gerencie suas informações pessoais

-
- - - - Informações Pessoais - - Seus dados pessoais básicos - + Informações Pessoais
-
- - handleInputChange("name", e.target.value)} - disabled={!isEditing} - /> -
-
- - handleInputChange("cpf", e.target.value)} - disabled={!isEditing} - /> -
-
- -
- - handleInputChange("birthDate", e.target.value)} - disabled={!isEditing} - /> +
handleInputChange("name", e.target.value)} disabled={!isEditing} />
+
handleInputChange("cpf", e.target.value)} disabled={!isEditing} />
+
handleInputChange("birthDate", e.target.value)} disabled={!isEditing} />
- - - - - Contato - - Informações de contato - + Contato e Endereço
-
- - handleInputChange("email", e.target.value)} - disabled={!isEditing} - /> -
-
- - handleInputChange("phone", e.target.value)} - disabled={!isEditing} - /> -
+
+
handleInputChange("phone", e.target.value)} disabled={!isEditing} />
- -
- -