diff --git a/MEDICONNECT 2/README.md b/MEDICONNECT 2/README.md index a0395d4a1..e5dc11e25 100644 --- a/MEDICONNECT 2/README.md +++ b/MEDICONNECT 2/README.md @@ -1,6 +1,6 @@ # MediConnect - Sistema de Agendamento Médico -Aplicação SPA (React + Vite + TypeScript) consumindo **Supabase** (Auth, PostgREST) diretamente do frontend, hospedada no **Cloudflare Pages**. +Aplicação SPA (React + Vite + TypeScript) consumindo **Supabase** (Auth, PostgREST, Storage) diretamente do frontend, hospedada no **Cloudflare Pages**. --- @@ -15,11 +15,12 @@ Aplicação SPA (React + Vite + TypeScript) consumindo **Supabase** (Auth, Postg ``` Frontend (Vite/React) → Supabase API - ↓ - Cloudflare Pages + ↓ ├── Auth (JWT) + Cloudflare Pages ├── PostgREST (PostgreSQL) + └── Storage (Avatares) ``` -**Mudança importante:** O sistema **não usa mais Netlify Functions**. Toda comunicação é direta entre frontend e Supabase via services (`authService`, `userService`, `patientService`, etc.). +**Mudança importante:** O sistema **não usa mais Netlify Functions**. Toda comunicação é direta entre frontend e Supabase via services (`authService`, `userService`, `patientService`, `avatarService`, etc.). --- @@ -242,6 +243,44 @@ await appointmentService.updateStatus(id, "confirmed"); await appointmentService.cancel(id, "Motivo do cancelamento"); ``` +#### 📸 Avatares (avatarService) + +```typescript +// Upload de avatar (usa FormData com x-upsert: true) +const file = event.target.files[0]; // File do input +const result = await avatarService.upload({ + userId: user.id, + file: file, +}); +// Retorna: { Key: "url-publica-do-avatar" } + +// Obter URL pública do avatar +const url = avatarService.getPublicUrl({ + userId: user.id, + ext: "png", // ou 'jpg', 'webp' +}); +// Retorna: https://yuanqfswhberkoevtmfr.supabase.co/storage/v1/object/avatars/{userId}/avatar.png + +// Auto-load de avatar (testa múltiplas extensões) +const extensions = ["png", "jpg", "webp"]; +for (const ext of extensions) { + const url = avatarService.getPublicUrl({ userId: user.id, ext }); + const response = await fetch(url, { method: "HEAD" }); + if (response.ok) { + setAvatarUrl(url); + break; + } +} +``` + +**Detalhes importantes:** + +- Upload usa **multipart/form-data** via FormData +- Header `x-upsert: true` permite sobrescrever avatares existentes +- Suporta formatos: PNG, JPG, WEBP (máx 2MB) +- URLs públicas não requerem autenticação +- Avatar é carregado automaticamente nos painéis + --- ## 🔧 Configuração da API diff --git a/MEDICONNECT 2/avatar_download_test.png b/MEDICONNECT 2/avatar_download_test.png deleted file mode 100644 index 4e4a03fb6..000000000 Binary files a/MEDICONNECT 2/avatar_download_test.png and /dev/null differ diff --git a/MEDICONNECT 2/check-fernando.js b/MEDICONNECT 2/check-fernando.js deleted file mode 100644 index cd3c4cac1..000000000 --- a/MEDICONNECT 2/check-fernando.js +++ /dev/null @@ -1,58 +0,0 @@ -const axios = require("axios"); - -const ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; -const BASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; - -(async () => { - try { - console.log("🔐 Fazendo login como admin..."); - const loginRes = await axios.post( - `${BASE_URL}/auth/v1/token?grant_type=password`, - { - email: "riseup@popcode.com.br", - password: "riseup", - }, - { - headers: { - "Content-Type": "application/json", - apikey: ANON_KEY, - }, - } - ); - - console.log("✅ Login admin bem-sucedido!"); - const token = loginRes.data.access_token; - - console.log("\n🔍 Buscando usuário fernando..."); - const usersRes = await axios.get(`${BASE_URL}/rest/v1/profiles?select=*`, { - headers: { - apikey: ANON_KEY, - Authorization: `Bearer ${token}`, - }, - }); - - console.log(`\n📊 Total de usuários: ${usersRes.data.length}`); - - const fernando = usersRes.data.find( - (u) => - u.email && - (u.email.toLowerCase().includes("fernando") || - u.full_name?.toLowerCase().includes("fernando")) - ); - - if (fernando) { - console.log("\n✅ Usuário Fernando encontrado:"); - console.log(JSON.stringify(fernando, null, 2)); - } else { - console.log("\n❌ Usuário Fernando NÃO encontrado na tabela profiles"); - console.log("\n📧 Alguns emails cadastrados:"); - usersRes.data.slice(0, 15).forEach((u) => { - if (u.email) - console.log(` - ${u.email} (${u.full_name || "sem nome"})`); - }); - } - } catch (err) { - console.error("❌ Erro:", err.response?.data || err.message); - } -})(); diff --git a/MEDICONNECT 2/test-password-recovery.js b/MEDICONNECT 2/test-password-recovery.js deleted file mode 100644 index 03fb555ca..000000000 --- a/MEDICONNECT 2/test-password-recovery.js +++ /dev/null @@ -1,55 +0,0 @@ -import axios from "axios"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const SUPABASE_ANON_KEY = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - -async function testPasswordRecovery() { - console.log("\n=== TESTE DE RECUPERAÇÃO DE SENHA ===\n"); - - const testEmail = "fernando.pirichowski@souunit.com.br"; - - try { - console.log("📧 Enviando email de recuperação para:", testEmail); - - const response = await axios.post( - `${SUPABASE_URL}/auth/v1/recover`, - { - email: testEmail, - options: { - redirectTo: "https://mediconnectbrasil.app/reset-password", - }, - }, - { - headers: { - "Content-Type": "application/json", - apikey: SUPABASE_ANON_KEY, - }, - } - ); - - console.log("✅ Email de recuperação enviado com sucesso!"); - console.log("Status:", response.status); - console.log("Response:", JSON.stringify(response.data, null, 2)); - console.log("\n📬 Verifique o email:", testEmail); - console.log( - "🔗 O link redirecionará para: https://mediconnectbrasil.app/reset-password" - ); - console.log("\n💡 O link virá no formato:"); - console.log( - " https://yuanqfswhberkoevtmfr.supabase.co/auth/v1/verify?token=...&type=recovery&redirect_to=https://mediconnectbrasil.app/reset-password" - ); - console.log("\n💡 Depois do clique, será redirecionado para:"); - console.log( - " https://mediconnectbrasil.app/reset-password#access_token=...&refresh_token=..." - ); - } catch (error) { - console.log("❌ Erro ao enviar email de recuperação:"); - console.log("Status:", error.response?.status); - console.log("Error:", error.response?.data || error.message); - } - - console.log("\n=== TESTE CONCLUÍDO ===\n"); -} - -testPasswordRecovery(); diff --git a/README.md b/README.md index 5da65b346..9962f746b 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ O MediConnect é uma plataforma web que conecta **pacientes**, **médicos** e ** - ✅ Agendamento de consultas online - ✅ Gestão de disponibilidade dos médicos - ✅ Acompanhamento de consultas e prontuários +- ✅ Sistema de avatares com upload de imagens - ✅ Recuperação de senha - ✅ Sistema de roles e permissões - ✅ Interface responsiva e acessível @@ -75,7 +76,8 @@ O MediConnect é uma plataforma web que conecta **pacientes**, **médicos** e ** 3. **Persistência (Supabase)** - PostgreSQL com Row Level Security (RLS) - - Supabase Auth para autenticação + - Supabase Auth para autenticação JWT + - Supabase Storage para upload de avatares - Supabase Functions para lógica serverless --- @@ -98,6 +100,7 @@ O MediConnect é uma plataforma web que conecta **pacientes**, **médicos** e ** - **Supabase** - Backend as a Service - **PostgreSQL** - Banco de dados relacional - **Supabase Auth** - Autenticação JWT +- **Supabase Storage** - Armazenamento de avatares ### Deploy