fix: corrigir redirecionamento do magic link para painel correto baseado no contexto

This commit is contained in:
guisilvagomes 2025-10-29 10:33:53 -03:00
parent 31aacb7a3d
commit f22bca225c
22 changed files with 1464 additions and 98 deletions

348
MEDICONNECT 2/API-CONFIG.md Normal file
View File

@ -0,0 +1,348 @@
# Configuração das APIs - MediConnect
## ✅ APIs Testadas e Funcionando
### 1. Autenticação (Auth API)
**Base URL:** `https://yuanqfswhberkoevtmfr.supabase.co/auth/v1`
#### Endpoints Funcionais:
- **Login**
- `POST /token?grant_type=password`
- Body: `{ email, password }`
- Retorna: `{ access_token, refresh_token, user }`
- **Recuperação de Senha**
- `POST /recover`
- Body: `{ email, options: { redirectTo: url } }`
- Envia email com link de recuperação
- **Atualizar Senha**
- `PUT /user`
- Headers: `Authorization: Bearer <access_token>`
- Body: `{ password: "nova_senha" }`
- **IMPORTANTE:** Nova senha deve ser diferente da anterior (erro 422 se for igual)
### 2. REST API
**Base URL:** `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1`
#### Tabelas e Campos Corretos:
##### **appointments**
```typescript
{
id: string (UUID)
order_number: string (auto-gerado: APT-YYYY-NNNN)
patient_id: string (UUID)
doctor_id: string (UUID)
scheduled_at: string (ISO 8601 DateTime)
duration_minutes: number
appointment_type: "presencial" | "telemedicina"
status: "requested" | "confirmed" | "checked_in" | "in_progress" | "completed" | "cancelled" | "no_show"
chief_complaint: string | null
patient_notes: string | null
notes: string | null
insurance_provider: string | null
checked_in_at: string | null
completed_at: string | null
cancelled_at: string | null
cancellation_reason: string | null
created_at: string
updated_at: string
created_by: string (UUID)
updated_by: string | null
}
```
**Criar Consulta:**
```bash
POST /rest/v1/appointments
Headers:
- apikey: <SUPABASE_ANON_KEY>
- Authorization: Bearer <user_access_token>
- Content-Type: application/json
- Prefer: return=representation
Body:
{
"patient_id": "uuid",
"doctor_id": "uuid",
"scheduled_at": "2025-11-03T10:00:00.000Z",
"duration_minutes": 30,
"appointment_type": "presencial",
"chief_complaint": "Motivo da consulta"
}
```
##### **doctor_availability**
```typescript
{
id: string (UUID)
doctor_id: string (UUID)
weekday: "sunday" | "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday"
start_time: string (HH:MM:SS, ex: "07:00:00")
end_time: string (HH:MM:SS, ex: "19:00:00")
slot_duration_minutes: number (ex: 30)
appointment_type: "presencial" | "telemedicina"
is_active: boolean
created_at: string
updated_at: string
created_by: string (UUID)
updated_by: string | null
}
```
**Criar Disponibilidade:**
```bash
POST /rest/v1/doctor_availability
Headers:
- apikey: <SUPABASE_ANON_KEY>
- Authorization: Bearer <admin_access_token>
- Content-Type: application/json
- Prefer: return=representation
Body:
{
"doctor_id": "uuid",
"weekday": "monday", // ⚠️ Texto, não número!
"start_time": "07:00:00",
"end_time": "19:00:00",
"slot_duration_minutes": 30,
"appointment_type": "presencial",
"is_active": true,
"created_by": "admin_user_id"
}
```
##### **patients**
```typescript
{
id: string(UUID);
user_id: string(UUID); // ⚠️ Deve estar vinculado ao auth.users
full_name: string;
email: string;
cpf: string;
phone_mobile: string;
// ... outros campos
}
```
**Atualizar Patient:**
```bash
PATCH /rest/v1/patients?id=eq.<patient_id>
Headers:
- apikey: <SUPABASE_ANON_KEY>
- Authorization: Bearer <admin_access_token>
- Content-Type: application/json
Body:
{
"user_id": "auth_user_id"
}
```
##### **doctors**
```typescript
{
id: string(UUID);
user_id: string(UUID);
full_name: string;
email: string;
crm: string;
crm_uf: string;
specialty: string;
// ... outros campos
}
```
### 3. Edge Functions
**Base URL:** `https://yuanqfswhberkoevtmfr.supabase.co/functions/v1`
#### Funcionais:
- **create-user-with-password**
- `POST /functions/v1/create-user-with-password`
- Cria usuário com senha e perfil completo
- Body:
```json
{
"email": "email@example.com",
"password": "senha123",
"full_name": "Nome Completo",
"phone_mobile": "(11) 99999-9999",
"cpf": "12345678900",
"create_patient_record": true,
"role": "paciente"
}
```
#### Com Problemas:
- **request-password-reset**
- CORS blocking - não usar
- Usar diretamente `/auth/v1/recover` em vez disso
## 🔑 Chaves de API
```typescript
SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
SUPABASE_ANON_KEY =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
```
## 👥 Usuários de Teste
### Admin
- Email: `riseup@popcode.com.br`
- Senha: `riseup`
### Dr. Fernando Pirichowski
- Email: `fernando.pirichowski@souunit.com.br`
- Senha: `fernando123`
- User ID: `38aca60d-7418-4c35-95b6-cb206bb18a0a`
- Doctor ID: `6dad001d-229b-40b5-80f3-310243c4599c`
- CRM: `24245`
- Disponibilidade: Segunda a Domingo, 07:00-19:00
### Aurora Sabrina Clara Nascimento (Paciente)
- Email: `aurora-nascimento94@gmx.com`
- Senha: `auroranasc94`
- User ID: `6dc15cc5-7dae-4b30-924a-a4b4fa142f24`
- Patient ID: `b85486f7-9135-4b67-9aa7-b884d9603d12`
- CPF: `66864784231`
- Telefone: `(21) 99856-3014`
## ⚠️ Pontos de Atenção
### 1. Weekday no doctor_availability
- ❌ **NÃO** usar números (0-6)
- ✅ **USAR** strings em inglês: `"sunday"`, `"monday"`, `"tuesday"`, `"wednesday"`, `"thursday"`, `"friday"`, `"saturday"`
### 2. scheduled_at em appointments
- ❌ **NÃO** usar campos separados `appointment_date` e `appointment_time`
- ✅ **USAR** campo único `scheduled_at` com ISO 8601 DateTime
- Exemplo: `"2025-11-03T10:00:00.000Z"`
### 3. user_id nas tabelas patients e doctors
- ⚠️ Sempre vincular ao `auth.users.id`
- Sem esse vínculo, queries por `user_id` não funcionam
### 4. Senha na recuperação
- ⚠️ Nova senha DEVE ser diferente da anterior
- Erro 422 com `error_code: "same_password"` se tentar usar a mesma
### 5. redirectTo no password recovery
- ⚠️ Supabase pode ignorar o parâmetro `redirectTo`
- ✅ Implementar detecção de token no lado do cliente
- Verificar tanto query string `?token=` quanto hash `#access_token=`
## 📦 Estrutura de Serviços no Frontend
```typescript
// Tudo configurado em:
src / services / api / config.ts; // URLs e chaves
src / services / api / client.ts; // Cliente axios
src /
services /
appointments / // Serviço de consultas
src /
services /
availability / // Disponibilidade médicos
src /
services /
auth / // Autenticação
src /
services /
doctors / // Médicos
src /
services /
patients / // Pacientes
src /
services /
index.ts; // Exportações centralizadas
```
## ✅ Status Atual
- [x] Autenticação funcionando
- [x] Recuperação de senha funcionando
- [x] Criação de usuários funcionando
- [x] Criação de pacientes funcionando
- [x] Criação de disponibilidade médica funcionando
- [x] Criação de consultas funcionando
- [x] Vinculação user_id ↔ patient_id corrigida
- [x] Todos os serviços usando campos corretos
## 🚀 Próximos Passos
1. Testar agendamento completo no frontend
2. Verificar listagem de consultas
3. Testar cancelamento e atualização de consultas
4. Verificar notificações SMS
5. Testar fluxo completo de check-in e prontuário
## 📝 Exemplos de Uso
### Criar Consulta
```typescript
import { appointmentService } from "@/services";
const appointment = await appointmentService.create({
patient_id: "patient-uuid",
doctor_id: "doctor-uuid",
scheduled_at: "2025-11-03T10:00:00.000Z",
duration_minutes: 30,
appointment_type: "presencial",
chief_complaint: "Consulta de rotina",
});
```
### Criar Disponibilidade
```typescript
import { availabilityService } from "@/services";
const availability = await availabilityService.create({
doctor_id: "doctor-uuid",
weekday: "monday",
start_time: "07:00:00",
end_time: "19:00:00",
slot_duration_minutes: 30,
appointment_type: "presencial",
});
```
### Login
```typescript
import { authService } from "@/services";
const response = await authService.login({
email: "user@example.com",
password: "senha123",
});
// response.access_token - JWT token
// response.user - dados do usuário
```

View File

@ -0,0 +1,292 @@
# ✅ Checklist de Testes - MediConnect
## 🎯 Testes Funcionais
### 1. Autenticação ✅
#### Login
- [x] Login de admin funcionando
- [x] Login de médico (Dr. Fernando) funcionando
- [x] Login de paciente (Aurora) funcionando
- [x] Token JWT sendo retornado corretamente
- [x] Refresh token funcionando
#### Recuperação de Senha
- [x] Email de recuperação sendo enviado
- [x] Token de recuperação detectado na URL
- [x] Reset de senha funcionando (senha diferente da anterior)
- [x] Erro 422 tratado quando senha é igual à anterior
- [x] Redirecionamento para página de reset funcionando
### 2. Gestão de Usuários ✅
#### Criação de Paciente
- [x] Edge Function `create-user-with-password` funcionando
- [x] Paciente criado com auth user
- [x] Registro na tabela `patients` criado
- [x] `user_id` vinculado corretamente ao `auth.users.id`
- [x] Credenciais de login funcionando após criação
**Usuário Teste:**
- Email: aurora-nascimento94@gmx.com
- Senha: auroranasc94
- Patient ID: b85486f7-9135-4b67-9aa7-b884d9603d12
#### Médicos
- [x] Dr. Fernando no sistema
- [x] User ID vinculado corretamente
- [x] Doctor ID identificado
- [x] CRM registrado
**Médico Teste:**
- Email: fernando.pirichowski@souunit.com.br
- Senha: fernando123
- Doctor ID: 6dad001d-229b-40b5-80f3-310243c4599c
### 3. Disponibilidade Médica ✅
#### Criação de Disponibilidade
- [x] Script de criação funcionando
- [x] Campo `weekday` usando strings em inglês
- [x] Formato de horário correto (HH:MM:SS)
- [x] Disponibilidade criada para todos os dias da semana
- [x] Horário: 07:00 - 19:00
- [x] Duração de slot: 30 minutos
- [x] Tipo: presencial
**Status:**
- ✅ 7 dias configurados (Domingo a Sábado)
- ✅ Dr. Fernando disponível das 07:00 às 19:00
### 4. Agendamento de Consultas ✅
#### Criação de Consulta
- [x] API de appointments funcionando
- [x] Campo `scheduled_at` usando ISO 8601 DateTime
- [x] Consulta criada com status "requested"
- [x] Order number gerado automaticamente (APT-YYYY-NNNN)
- [x] Duração de 30 minutos configurada
- [x] Tipo presencial configurado
**Consulta Teste:**
- Paciente: Aurora
- Médico: Dr. Fernando
- Data: 03/11/2025 às 10:00
- Order Number: APT-2025-00027
- ID: cb4f608f-e580-437f-8653-75ec74621065
### 5. Frontend - Componentes
#### AgendamentoConsulta.tsx ✅
- [x] Usando `appointmentService` correto
- [x] Campo `scheduled_at` implementado
- [x] Formato ISO 8601 DateTime
- [x] Integração com availability service
- [x] Integração com exceptions service
- [x] SMS notification configurado
#### Outros Componentes
- [ ] BookAppointment.tsx - não usado (pode ser removido)
- [ ] AgendamentoConsultaSimples.tsx - não usado (pode ser removido)
## 🔧 Configurações Verificadas
### API Config ✅
- [x] `src/services/api/config.ts` - URLs corretas
- [x] SUPABASE_ANON_KEY atualizada
- [x] Endpoints configurados corretamente
### Services ✅
- [x] `appointmentService` - usando campos corretos
- [x] `availabilityService` - usando weekday strings
- [x] `authService` - recuperação de senha funcionando
- [x] `patientService` - CRUD funcionando
- [x] `doctorService` - CRUD funcionando
- [x] Todos exportados em `src/services/index.ts`
## 🧪 Testes Pendentes
### Fluxo Completo de Agendamento
- [ ] Paciente faz login
- [ ] Paciente busca médicos disponíveis
- [ ] Paciente visualiza horários disponíveis
- [ ] Paciente agenda consulta
- [ ] Consulta aparece na lista do paciente
- [ ] Médico visualiza consulta na agenda
- [ ] Notificação SMS enviada
### Check-in e Atendimento
- [ ] Check-in de paciente
- [ ] Status da consulta muda para "checked_in"
- [ ] Médico inicia atendimento
- [ ] Status muda para "in_progress"
- [ ] Preenchimento de prontuário
- [ ] Finalização da consulta
- [ ] Status muda para "completed"
### Cancelamento
- [ ] Paciente cancela consulta
- [ ] Médico cancela consulta
- [ ] Status muda para "cancelled"
- [ ] Motivo do cancelamento registrado
- [ ] Horário fica disponível novamente
### Exceções de Disponibilidade
- [ ] Criar exceção (feriado, folga)
- [ ] Exceção bloqueia horários
- [ ] Listar exceções
- [ ] Remover exceção
## 📊 Métricas e Relatórios
- [ ] Dashboard de consultas
- [ ] Estatísticas de atendimento
- [ ] Relatório de faturamento
- [ ] Exportação de dados
## 🔐 Segurança
### Autenticação
- [x] JWT tokens funcionando
- [x] Refresh tokens implementados
- [x] Session storage configurado
- [ ] Expiração de tokens tratada
- [ ] Logout funcionando corretamente
### Autorização
- [ ] RLS (Row Level Security) configurado no Supabase
- [ ] Paciente só vê suas próprias consultas
- [ ] Médico só vê consultas atribuídas
- [ ] Admin tem acesso total
- [ ] Secretária tem permissões específicas
## 🌐 Deploy e Performance
- [ ] Build de produção funcionando
- [ ] Deploy no Cloudflare Pages
- [ ] URLs de produção configuradas
- [ ] Performance otimizada
- [ ] Lazy loading de componentes
- [ ] Cache configurado
## 📱 Responsividade
- [ ] Desktop (1920x1080)
- [ ] Laptop (1366x768)
- [ ] Tablet (768x1024)
- [ ] Mobile (375x667)
## ♿ Acessibilidade
- [ ] Menu de acessibilidade funcionando
- [ ] Contraste de cores ajustável
- [ ] Tamanho de fonte ajustável
- [ ] Leitura de tela compatível
- [ ] Navegação por teclado
## 🐛 Bugs Conhecidos
Nenhum bug crítico identificado até o momento.
## 📝 Notas Importantes
### Campos Corretos nas APIs
1. **appointments.scheduled_at**
- ❌ NÃO: `appointment_date` e `appointment_time` separados
- ✅ SIM: `scheduled_at` com ISO 8601 DateTime
2. **doctor_availability.weekday**
- ❌ NÃO: Números 0-6
- ✅ SIM: Strings "sunday", "monday", etc.
3. **patients.user_id**
- ⚠️ DEVE estar vinculado ao `auth.users.id`
- Sem isso, queries por user_id falham
4. **Password Recovery**
- ⚠️ Nova senha DEVE ser diferente da anterior
- Erro 422 com `error_code: "same_password"` se igual
### Scripts Úteis
```bash
# Login como usuário
node get-fernando-user-id.cjs
# Buscar dados de paciente
node get-aurora-info.cjs
# Criar disponibilidade
node create-fernando-availability.cjs
# Criar consulta
node create-aurora-appointment.cjs
# Corrigir user_id
node fix-aurora-user-id.cjs
```
## 🚀 Próximas Funcionalidades
1. **Telemedicina**
- [ ] Integração com serviço de videochamada
- [ ] Sala de espera virtual
- [ ] Gravação de consultas (opcional)
2. **Prontuário Eletrônico**
- [ ] CRUD completo de prontuários
- [ ] Histórico de consultas
- [ ] Anexos e exames
- [ ] Prescrições médicas
3. **Notificações**
- [x] SMS via Twilio configurado
- [ ] Email notifications
- [ ] Push notifications (PWA)
- [ ] Lembretes de consulta
4. **Pagamentos**
- [ ] Integração com gateway de pagamento
- [ ] Registro de pagamentos
- [ ] Emissão de recibos
- [ ] Relatório financeiro
5. **Telemática**
- [ ] Assinatura digital de documentos
- [ ] Certificação digital A1/A3
- [ ] Integração com e-SUS
- [ ] Compliance LGPD
---
**Última atualização:** 27/10/2025
**Status:** ✅ APIs configuradas e funcionando
**Próximo passo:** Testar fluxo completo no frontend

View File

@ -0,0 +1,128 @@
const axios = require("axios");
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
const SUPABASE_ANON_KEY =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
async function createAppointment() {
try {
console.log("🔐 Fazendo login como Aurora...");
// Login como Aurora
const loginResponse = await axios.post(
`${SUPABASE_URL}/auth/v1/token?grant_type=password`,
{
email: "aurora-nascimento94@gmx.com",
password: "auroranasc94",
},
{
headers: {
apikey: SUPABASE_ANON_KEY,
"Content-Type": "application/json",
},
}
);
const auroraToken = loginResponse.data.access_token;
console.log("✅ Login realizado como Aurora");
// Buscar o patient_id da Aurora
console.log("\n👤 Buscando dados da paciente...");
const patientResponse = await axios.get(
`${SUPABASE_URL}/rest/v1/patients?user_id=eq.${loginResponse.data.user.id}`,
{
headers: {
apikey: SUPABASE_ANON_KEY,
Authorization: `Bearer ${auroraToken}`,
},
}
);
if (!patientResponse.data || patientResponse.data.length === 0) {
throw new Error("Paciente não encontrada");
}
const patientId = patientResponse.data[0].id;
console.log(`✅ Patient ID: ${patientId}`);
// Buscar disponibilidade do Dr. Fernando para segunda-feira
console.log("\n📅 Buscando disponibilidade do Dr. Fernando...");
const availabilityResponse = await axios.get(
`${SUPABASE_URL}/rest/v1/doctor_availability?doctor_id=eq.6dad001d-229b-40b5-80f3-310243c4599c&weekday=eq.monday`,
{
headers: {
apikey: SUPABASE_ANON_KEY,
Authorization: `Bearer ${auroraToken}`,
},
}
);
if (!availabilityResponse.data || availabilityResponse.data.length === 0) {
throw new Error("Disponibilidade não encontrada");
}
const availability = availabilityResponse.data[0];
console.log(
`✅ Disponibilidade encontrada: ${availability.start_time} - ${availability.end_time}`
);
// Criar consulta para próxima segunda-feira às 10:00
const today = new Date();
const daysUntilMonday = (1 - today.getDay() + 7) % 7 || 7; // Próxima segunda
const appointmentDate = new Date(today);
appointmentDate.setDate(today.getDate() + daysUntilMonday);
appointmentDate.setHours(10, 0, 0, 0);
const scheduledAt = appointmentDate.toISOString();
console.log(`\n📝 Criando consulta para ${scheduledAt}...`);
const appointmentData = {
patient_id: patientId,
doctor_id: "6dad001d-229b-40b5-80f3-310243c4599c",
scheduled_at: scheduledAt,
duration_minutes: 30,
appointment_type: "presencial",
chief_complaint: "Consulta de rotina",
};
const appointmentResponse = await axios.post(
`${SUPABASE_URL}/rest/v1/appointments`,
appointmentData,
{
headers: {
apikey: SUPABASE_ANON_KEY,
Authorization: `Bearer ${auroraToken}`,
"Content-Type": "application/json",
Prefer: "return=representation",
},
}
);
console.log("\n✅ Consulta criada com sucesso!");
console.log("\n📋 Detalhes da consulta:");
console.log(` - Paciente: Aurora Sabrina Clara Nascimento`);
console.log(` - Médico: Dr. Fernando Pirichowski`);
console.log(` - Data/Hora: ${scheduledAt}`);
console.log(` - Duração: 30 minutos`);
console.log(` - Tipo: presencial`);
if (appointmentResponse.data && appointmentResponse.data.length > 0) {
console.log(` - ID da consulta: ${appointmentResponse.data[0].id}`);
console.log(
` - Order Number: ${appointmentResponse.data[0].order_number}`
);
console.log(` - Status: ${appointmentResponse.data[0].status}`);
}
} catch (error) {
console.error(
"❌ Erro ao criar consulta:",
error.response?.data || error.message
);
if (error.response?.data) {
console.error("Detalhes:", JSON.stringify(error.response.data, null, 2));
}
}
}
createAppointment();

View File

@ -0,0 +1,93 @@
const axios = require("axios");
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
const SUPABASE_ANON_KEY =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
// IDs
const DOCTOR_ID = "6dad001d-229b-40b5-80f3-310243c4599c"; // Fernando (CRM 24245)
const ADMIN_ID = "c7fcd702-9a6e-4b7c-abd3-956b25af407d"; // Admin (riseup)
async function main() {
try {
console.log("🔐 Fazendo login como admin...");
const loginResponse = await axios.post(
`${SUPABASE_URL}/auth/v1/token?grant_type=password`,
{
email: "riseup@popcode.com.br",
password: "riseup",
},
{
headers: {
"Content-Type": "application/json",
apikey: SUPABASE_ANON_KEY,
},
}
);
const adminToken = loginResponse.data.access_token;
console.log("✅ Login realizado\n");
console.log("📅 Criando disponibilidade para Dr. Fernando...");
console.log("⏰ Horário: 07:00 às 19:00");
console.log("📆 Dias: Segunda a Domingo\n");
const weekdays = [
{ num: "sunday", name: "Domingo" },
{ num: "monday", name: "Segunda-feira" },
{ num: "tuesday", name: "Terça-feira" },
{ num: "wednesday", name: "Quarta-feira" },
{ num: "thursday", name: "Quinta-feira" },
{ num: "friday", name: "Sexta-feira" },
{ num: "saturday", name: "Sábado" },
];
for (const day of weekdays) {
try {
const availabilityData = {
doctor_id: DOCTOR_ID,
weekday: day.num,
start_time: "07:00:00",
end_time: "19:00:00",
slot_minutes: 30,
appointment_type: "presencial",
active: true,
created_by: ADMIN_ID,
};
const response = await axios.post(
`${SUPABASE_URL}/rest/v1/doctor_availability`,
availabilityData,
{
headers: {
"Content-Type": "application/json",
apikey: SUPABASE_ANON_KEY,
Authorization: `Bearer ${adminToken}`,
Prefer: "return=representation",
},
}
);
console.log(`${day.name}: Disponibilidade criada`);
} catch (error) {
console.error(
`${day.name}: Erro -`,
error.response?.data?.message || error.message
);
}
}
console.log("\n🎉 Disponibilidade criada com sucesso!");
console.log("\n📋 Resumo:");
console.log("- Médico: Dr. Fernando Pirichowski");
console.log("- Dias: Todos os dias da semana (Domingo a Sábado)");
console.log("- Horário: 07:00 às 19:00");
console.log("- Duração consulta: 30 minutos");
console.log("- Tipo: Presencial");
} catch (error) {
console.error("❌ Erro geral:", error.response?.data || error.message);
}
}
main();

View File

@ -0,0 +1,80 @@
const axios = require("axios");
// Configuração
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
const SUPABASE_ANON_KEY =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
// Credenciais do admin
const ADMIN_EMAIL = "riseup@popcode.com.br";
const ADMIN_PASSWORD = "riseup";
// Dados do paciente (Aurora Sabrina Clara Nascimento)
const PATIENT_DATA = {
email: "aurora-nascimento94@gmx.com",
password: "auroranasc94",
full_name: "Aurora Sabrina Clara Nascimento",
phone_mobile: "(21) 99856-3014",
cpf: "66864784231", // CPF sem pontuação
create_patient_record: true,
role: "paciente",
};
async function main() {
try {
console.log("🔐 1. Fazendo login como admin...");
// 1. Login do admin
const loginResponse = await axios.post(
`${SUPABASE_URL}/auth/v1/token?grant_type=password`,
{
email: ADMIN_EMAIL,
password: ADMIN_PASSWORD,
},
{
headers: {
"Content-Type": "application/json",
apikey: SUPABASE_ANON_KEY,
},
}
);
const adminToken = loginResponse.data.access_token;
console.log("✅ Login realizado com sucesso!");
console.log("🔑 Token:", adminToken.substring(0, 30) + "...");
console.log("\n👤 2. Criando paciente...");
console.log("Dados:", JSON.stringify(PATIENT_DATA, null, 2));
// 2. Criar paciente
const createResponse = await axios.post(
`${SUPABASE_URL}/functions/v1/create-user-with-password`,
PATIENT_DATA,
{
headers: {
"Content-Type": "application/json",
apikey: SUPABASE_ANON_KEY,
Authorization: `Bearer ${adminToken}`,
},
}
);
console.log("\n✅ Paciente criado com sucesso!");
console.log("Resposta:", JSON.stringify(createResponse.data, null, 2));
if (createResponse.data.patient_id) {
console.log("\n📋 ID do paciente:", createResponse.data.patient_id);
console.log("✉️ Email de confirmação enviado para:", PATIENT_DATA.email);
console.log("🔐 Senha temporária:", PATIENT_DATA.password);
console.log(
"\n⚠ O usuário precisa confirmar o email antes de fazer login!"
);
}
} catch (error) {
console.error("❌ Erro:", error.response?.data || error.message);
console.error("Status:", error.response?.status);
console.error("URL:", error.config?.url);
}
}
main();

View File

@ -0,0 +1,72 @@
const axios = require("axios");
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
const SUPABASE_ANON_KEY =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
async function fixAuroraUserId() {
try {
console.log("🔐 Fazendo login como admin...");
const loginResponse = await axios.post(
`${SUPABASE_URL}/auth/v1/token?grant_type=password`,
{
email: "riseup@popcode.com.br",
password: "riseup",
},
{
headers: {
apikey: SUPABASE_ANON_KEY,
"Content-Type": "application/json",
},
}
);
const adminToken = loginResponse.data.access_token;
console.log("✅ Login realizado");
// Fazer login como Aurora para pegar o user_id
console.log("\n👤 Fazendo login como Aurora...");
const auroraLoginResponse = await axios.post(
`${SUPABASE_URL}/auth/v1/token?grant_type=password`,
{
email: "aurora-nascimento94@gmx.com",
password: "auroranasc94",
},
{
headers: {
apikey: SUPABASE_ANON_KEY,
"Content-Type": "application/json",
},
}
);
const auroraUserId = auroraLoginResponse.data.user.id;
console.log(`✅ User ID da Aurora: ${auroraUserId}`);
// Atualizar patient com user_id
console.log("\n📝 Atualizando registro da paciente...");
const updateResponse = await axios.patch(
`${SUPABASE_URL}/rest/v1/patients?id=eq.b85486f7-9135-4b67-9aa7-b884d9603d12`,
{
user_id: auroraUserId,
},
{
headers: {
apikey: SUPABASE_ANON_KEY,
Authorization: `Bearer ${adminToken}`,
"Content-Type": "application/json",
Prefer: "return=representation",
},
}
);
console.log("✅ Registro atualizado com sucesso!");
console.log(` - Patient ID: b85486f7-9135-4b67-9aa7-b884d9603d12`);
console.log(` - User ID: ${auroraUserId}`);
} catch (error) {
console.error("❌ Erro:", error.response?.data || error.message);
}
}
fixAuroraUserId();

View File

@ -0,0 +1,59 @@
const axios = require("axios");
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
const SUPABASE_ANON_KEY =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
async function main() {
try {
console.log("🔐 Fazendo login como admin...");
const loginResponse = await axios.post(
`${SUPABASE_URL}/auth/v1/token?grant_type=password`,
{
email: "riseup@popcode.com.br",
password: "riseup",
},
{
headers: {
"Content-Type": "application/json",
apikey: SUPABASE_ANON_KEY,
},
}
);
const adminToken = loginResponse.data.access_token;
console.log("✅ Login realizado\n");
console.log("👤 Buscando dados de Aurora na tabela patients...");
const patientsResponse = await axios.get(
`${SUPABASE_URL}/rest/v1/patients?email=eq.aurora-nascimento94@gmx.com`,
{
headers: {
apikey: SUPABASE_ANON_KEY,
Authorization: `Bearer ${adminToken}`,
},
}
);
if (patientsResponse.data.length > 0) {
const patient = patientsResponse.data[0];
console.log("✅ Paciente encontrada!\n");
console.log("📋 DADOS DA AURORA:\n");
console.log("User ID (auth):", patient.id);
console.log("Patient ID:", patient.id); // Em patients, o id é o mesmo do auth
console.log("Nome:", patient.full_name);
console.log("Email:", patient.email);
console.log("CPF:", patient.cpf);
console.log("Telefone:", patient.phone_mobile);
console.log("\n📄 Dados completos:", JSON.stringify(patient, null, 2));
} else {
console.log("❌ Paciente não encontrada na tabela patients");
}
} catch (error) {
console.error("❌ Erro:", error.response?.data || error.message);
}
}
main();

View File

@ -0,0 +1,80 @@
const axios = require("axios");
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
const SUPABASE_ANON_KEY =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
async function main() {
try {
console.log("🔐 Fazendo login como admin...");
const loginResponse = await axios.post(
`${SUPABASE_URL}/auth/v1/token?grant_type=password`,
{
email: "riseup@popcode.com.br",
password: "riseup",
},
{
headers: {
"Content-Type": "application/json",
apikey: SUPABASE_ANON_KEY,
},
}
);
const adminToken = loginResponse.data.access_token;
console.log("✅ Login realizado\n");
console.log("🔍 Buscando Fernando em profiles...");
const profilesResponse = await axios.get(
`${SUPABASE_URL}/rest/v1/profiles?email=eq.fernando.pirichowski@souunit.com.br`,
{
headers: {
apikey: SUPABASE_ANON_KEY,
Authorization: `Bearer ${adminToken}`,
},
}
);
if (profilesResponse.data.length > 0) {
console.log(
`${profilesResponse.data.length} perfil(is) encontrado(s)!\n`
);
profilesResponse.data.forEach((profile, index) => {
console.log(`📋 PERFIL ${index + 1}:\n`);
console.log("User ID:", profile.id);
console.log("Email:", profile.email);
console.log("Nome:", profile.full_name);
console.log("Telefone:", profile.phone || "Não informado");
console.log("\n" + "=".repeat(60) + "\n");
});
// Pegar roles do primeiro perfil
const userId = profilesResponse.data[0].id;
console.log("🔍 Buscando roles...");
const rolesResponse = await axios.get(
`${SUPABASE_URL}/rest/v1/user_roles?user_id=eq.${userId}`,
{
headers: {
apikey: SUPABASE_ANON_KEY,
Authorization: `Bearer ${adminToken}`,
},
}
);
console.log(
"📌 Roles:",
rolesResponse.data.map((r) => r.role).join(", ")
);
} else {
console.log("❌ Nenhum perfil encontrado");
}
} catch (error) {
console.error("❌ Erro:", error.response?.data || error.message);
}
}
main();

View File

@ -0,0 +1,82 @@
const axios = require("axios");
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
const SUPABASE_ANON_KEY =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
async function main() {
try {
console.log("🔐 Fazendo login como admin...");
const loginResponse = await axios.post(
`${SUPABASE_URL}/auth/v1/token?grant_type=password`,
{
email: "riseup@popcode.com.br",
password: "riseup",
},
{
headers: {
"Content-Type": "application/json",
apikey: SUPABASE_ANON_KEY,
},
}
);
const adminToken = loginResponse.data.access_token;
console.log("✅ Login realizado\n");
console.log("👨‍⚕️ Buscando dados de Fernando na tabela doctors...");
const doctorsResponse = await axios.get(
`${SUPABASE_URL}/rest/v1/doctors?email=eq.fernando.pirichowski@souunit.com.br`,
{
headers: {
apikey: SUPABASE_ANON_KEY,
Authorization: `Bearer ${adminToken}`,
Prefer: "return=representation",
},
}
);
if (doctorsResponse.data.length > 0) {
console.log(
`${doctorsResponse.data.length} médico(s) encontrado(s)!\n`
);
doctorsResponse.data.forEach((doctor, index) => {
console.log(`📋 MÉDICO ${index + 1}:\n`);
console.log("Doctor ID:", doctor.id);
console.log("Nome:", doctor.full_name);
console.log("Email:", doctor.email);
console.log("CRM:", doctor.crm);
console.log("Especialidade:", doctor.specialty || "Não informada");
console.log("Telefone:", doctor.phone || "Não informado");
console.log("\n" + "=".repeat(60) + "\n");
});
} else {
console.log("❌ Nenhum médico chamado Fernando encontrado");
console.log("\n🔍 Buscando todos os médicos...");
const allDoctorsResponse = await axios.get(
`${SUPABASE_URL}/rest/v1/doctors`,
{
headers: {
apikey: SUPABASE_ANON_KEY,
Authorization: `Bearer ${adminToken}`,
},
}
);
console.log(
`\n📊 Total de médicos cadastrados: ${allDoctorsResponse.data.length}`
);
allDoctorsResponse.data.forEach((doctor, index) => {
console.log(`${index + 1}. ${doctor.full_name} - ${doctor.email}`);
});
}
} catch (error) {
console.error("❌ Erro:", error.response?.data || error.message);
}
}
main();

View File

@ -0,0 +1,38 @@
const axios = require("axios");
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
const SUPABASE_ANON_KEY =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
async function main() {
try {
console.log("🔐 Fazendo login como Fernando...");
const loginResponse = await axios.post(
`${SUPABASE_URL}/auth/v1/token?grant_type=password`,
{
email: "fernando.pirichowski@souunit.com.br",
password: "fernando123",
},
{
headers: {
"Content-Type": "application/json",
apikey: SUPABASE_ANON_KEY,
},
}
);
const userData = loginResponse.data;
console.log("✅ Login realizado\n");
console.log("📋 DADOS DO FERNANDO (AUTH):\n");
console.log("User ID:", userData.user.id);
console.log("Email:", userData.user.email);
console.log("Role:", userData.user.role);
console.log("\n" + "=".repeat(60));
} catch (error) {
console.error("❌ Erro:", error.response?.data || error.message);
}
}
main();

View File

@ -14,27 +14,31 @@ const RecoveryRedirect: React.FC = () => {
console.log("[RecoveryRedirect] Pathname:", location.pathname);
console.log("[RecoveryRedirect] Search:", location.search);
console.log("[RecoveryRedirect] Hash:", location.hash);
let shouldRedirect = false;
// Verificar query string: ?token=xxx&type=recovery
const searchParams = new URLSearchParams(location.search);
const queryToken = searchParams.get("token");
const queryType = searchParams.get("type");
if (queryToken && queryType === "recovery") {
console.log("[RecoveryRedirect] ✅ Token de recovery no query string detectado");
console.log(
"[RecoveryRedirect] ✅ Token de recovery no query string detectado"
);
shouldRedirect = true;
}
// Verificar hash: #access_token=xxx&type=recovery
if (location.hash) {
const hashParams = new URLSearchParams(location.hash.substring(1));
const hashToken = hashParams.get("access_token");
const hashType = hashParams.get("type");
if (hashToken && hashType === "recovery") {
console.log("[RecoveryRedirect] ✅ Token de recovery no hash detectado");
console.log(
"[RecoveryRedirect] ✅ Token de recovery no hash detectado"
);
shouldRedirect = true;
}
}
@ -42,7 +46,9 @@ const RecoveryRedirect: React.FC = () => {
if (shouldRedirect) {
console.log("[RecoveryRedirect] 🔄 Redirecionando para /reset-password");
// Preservar os parâmetros e redirecionar
navigate(`/reset-password${location.search}${location.hash}`, { replace: true });
navigate(`/reset-password${location.search}${location.hash}`, {
replace: true,
});
} else {
console.log("[RecoveryRedirect] Nenhum token de recovery detectado");
}

View File

@ -87,9 +87,9 @@ const AcompanhamentoPaciente: React.FC = () => {
const testAvatar = async () => {
for (const ext of extensions) {
try {
const url = avatarService.getPublicUrl({
userId: user.id,
ext: ext as "jpg" | "png" | "webp"
const url = avatarService.getPublicUrl({
userId: user.id,
ext: ext as "jpg" | "png" | "webp",
});
const response = await fetch(url, { method: "HEAD" });
if (response.ok) {

View File

@ -22,23 +22,25 @@ export default function AuthCallback() {
const handleCallback = async () => {
try {
console.log("[AuthCallback] Iniciando processamento");
// Verificar se é um token de recovery
const hash = window.location.hash;
const hashParams = new URLSearchParams(hash.substring(1));
const accessToken = hashParams.get("access_token");
const type = hashParams.get("type");
console.log("[AuthCallback] Hash:", hash);
console.log("[AuthCallback] Type:", type);
console.log("[AuthCallback] Access Token presente:", !!accessToken);
// Se for recovery, redirecionar para página de reset
if (type === "recovery" && accessToken) {
console.log("[AuthCallback] ✅ Token de recovery detectado, redirecionando para /reset-password");
console.log(
"[AuthCallback] ✅ Token de recovery detectado, redirecionando para /reset-password"
);
setStatus("success");
setMessage("Redirecionando para página de redefinição de senha...");
// Redirecionar preservando o hash com o token
setTimeout(() => {
navigate(`/reset-password${hash}`, { replace: true });
@ -81,20 +83,32 @@ export default function AuthCallback() {
setMessage("Autenticado com sucesso! Redirecionando...");
toast.success("Login realizado com sucesso!");
// Redirecionar baseado no role
// Redirecionar baseado no contexto salvo ou role do usuário
setTimeout(() => {
// Verificar se há redirecionamento salvo do magic link
const savedRedirect = localStorage.getItem("magic_link_redirect");
if (savedRedirect) {
console.log("[AuthCallback] Redirecionando para:", savedRedirect);
localStorage.removeItem("magic_link_redirect"); // Limpar após uso
navigate(savedRedirect, { replace: true });
return;
}
// Fallback: redirecionar baseado no role
const userRole = session.user.user_metadata?.role || "paciente";
console.log("[AuthCallback] Redirecionando baseado no role:", userRole);
switch (userRole) {
case "medico":
navigate("/painel-medico");
navigate("/painel-medico", { replace: true });
break;
case "secretaria":
navigate("/painel-secretaria");
navigate("/painel-secretaria", { replace: true });
break;
case "paciente":
default:
navigate("/acompanhamento");
navigate("/acompanhamento", { replace: true });
break;
}
}, 1500);

View File

@ -100,7 +100,7 @@ const Home: React.FC = () => {
<div className="space-y-8" id="main-content">
{/* Componente invisível que detecta tokens de recuperação e redireciona */}
<RecoveryRedirect />
{/* Hero Section com Background Rotativo */}
<HeroBanner />

View File

@ -202,9 +202,12 @@ const LoginMedico: React.FC = () => {
}
setLoading(true);
try {
// Salvar contexto para redirecionamento correto após magic link
localStorage.setItem("magic_link_redirect", "/painel-medico");
await authService.sendMagicLink(
formData.email,
"https://mediconnectbrasil.netlify.app/medico/painel"
`${window.location.origin}/auth/callback`
);
toast.success(
"Link de acesso enviado para seu email! Verifique sua caixa de entrada.",

View File

@ -312,9 +312,12 @@ const LoginPaciente: React.FC = () => {
}
setLoading(true);
try {
// Salvar contexto para redirecionamento correto após magic link
localStorage.setItem("magic_link_redirect", "/acompanhamento");
await authService.sendMagicLink(
formData.email,
"https://mediconnectbrasil.netlify.app/paciente/agendamento"
`${window.location.origin}/auth/callback`
);
toast.success(
"Link de acesso enviado para seu email! Verifique sua caixa de entrada.",

View File

@ -212,9 +212,12 @@ const LoginSecretaria: React.FC = () => {
}
setLoading(true);
try {
// Salvar contexto para redirecionamento correto após magic link
localStorage.setItem("magic_link_redirect", "/painel-secretaria");
await authService.sendMagicLink(
formData.email,
"https://mediconnectbrasil.netlify.app/secretaria/painel"
`${window.location.origin}/auth/callback`
);
toast.success(
"Link de acesso enviado para seu email! Verifique sua caixa de entrada.",

View File

@ -109,9 +109,9 @@ const PainelMedico: React.FC = () => {
const testAvatar = async () => {
for (const ext of extensions) {
try {
const url = avatarService.getPublicUrl({
userId: user.id,
ext: ext as "jpg" | "png" | "webp"
const url = avatarService.getPublicUrl({
userId: user.id,
ext: ext as "jpg" | "png" | "webp",
});
const response = await fetch(url, { method: "HEAD" });
if (response.ok) {

View File

@ -192,9 +192,7 @@ export default function PerfilPaciente() {
<div className="flex items-center justify-center min-h-screen bg-gray-50">
<div className="text-center">
<div className="w-16 h-16 border-4 border-blue-600 border-t-transparent rounded-full animate-spin mx-auto mb-4" />
<p className="text-gray-600">
Carregando perfil...
</p>
<p className="text-gray-600">Carregando perfil...</p>
</div>
</div>
);
@ -204,9 +202,7 @@ export default function PerfilPaciente() {
return (
<div className="flex items-center justify-center min-h-screen bg-gray-50">
<div className="text-center">
<p className="text-red-600 mb-4">
Usuário não identificado
</p>
<p className="text-red-600 mb-4">Usuário não identificado</p>
<button
onClick={() => navigate("/paciente")}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
@ -233,9 +229,7 @@ export default function PerfilPaciente() {
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-gray-900">
Meu Perfil
</h1>
<h1 className="text-3xl font-bold text-gray-900">Meu Perfil</h1>
<p className="text-gray-600">
Gerencie suas informações pessoais e médicas
</p>
@ -286,9 +280,7 @@ export default function PerfilPaciente() {
<p className="font-medium text-gray-900">
{formData.full_name || "Carregando..."}
</p>
<p className="text-gray-500">
{formData.email || "Sem email"}
</p>
<p className="text-gray-500">{formData.email || "Sem email"}</p>
</div>
</div>
</div>
@ -688,4 +680,3 @@ export default function PerfilPaciente() {
</div>
);
}

View File

@ -16,7 +16,7 @@ const ResetPassword: React.FC = () => {
useEffect(() => {
// Extrair access_token do hash da URL (#access_token=...)
// OU do query string (?token=...&type=recovery)
let token: string | null = null;
let type: string | null = null;
@ -28,8 +28,11 @@ const ResetPassword: React.FC = () => {
const hashParams = new URLSearchParams(hash.substring(1));
token = hashParams.get("access_token");
type = hashParams.get("type");
console.log("[ResetPassword] Token do hash:", token ? token.substring(0, 20) + "..." : "null");
console.log(
"[ResetPassword] Token do hash:",
token ? token.substring(0, 20) + "..." : "null"
);
console.log("[ResetPassword] Type do hash:", type);
}
@ -37,23 +40,30 @@ const ResetPassword: React.FC = () => {
if (!token) {
const search = window.location.search;
console.log("[ResetPassword] Query string completo:", search);
if (search) {
const queryParams = new URLSearchParams(search);
token = queryParams.get("token");
type = queryParams.get("type");
console.log("[ResetPassword] Token do query:", token ? token.substring(0, 20) + "..." : "null");
console.log(
"[ResetPassword] Token do query:",
token ? token.substring(0, 20) + "..." : "null"
);
console.log("[ResetPassword] Type do query:", type);
}
}
if (token) {
setAccessToken(token);
console.log("[ResetPassword] ✅ Token de recuperação detectado e armazenado");
console.log(
"[ResetPassword] ✅ Token de recuperação detectado e armazenado"
);
console.log("[ResetPassword] Type:", type);
} else {
console.error("[ResetPassword] ❌ Token não encontrado no hash nem no query string");
console.error(
"[ResetPassword] ❌ Token não encontrado no hash nem no query string"
);
console.log("[ResetPassword] URL completa:", window.location.href);
toast.error("Link de recuperação inválido ou expirado");
setTimeout(() => navigate("/"), 3000);
@ -106,24 +116,24 @@ const ResetPassword: React.FC = () => {
} catch (error: unknown) {
console.error("[ResetPassword] Erro ao atualizar senha:", error);
const err = error as {
response?: {
data?: {
error_description?: string;
response?: {
data?: {
error_description?: string;
message?: string;
msg?: string;
error_code?: string;
}
};
};
message?: string;
};
// Mensagem específica para senha igual
if (err?.response?.data?.error_code === "same_password") {
toast.error("A nova senha deve ser diferente da senha atual");
setLoading(false);
return;
}
const errorMessage =
err?.response?.data?.msg ||
err?.response?.data?.error_description ||

View File

@ -154,7 +154,7 @@ class AuthService {
/**
* Solicita reset de senha via email (público)
* POST /auth/v1/recover
*
*
* Envia redirectTo dentro de options para controlar para onde o Supabase redireciona
*/
async requestPasswordReset(
@ -162,40 +162,50 @@ class AuthService {
): Promise<{ success: boolean; message: string }> {
try {
const redirectUrl = `${API_CONFIG.APP_URL}/reset-password`;
console.log("[authService.requestPasswordReset] Email:", email);
console.log("[authService.requestPasswordReset] Redirect URL:", redirectUrl);
console.log("[authService.requestPasswordReset] Usando endpoint /auth/v1/recover");
console.log(
"[authService.requestPasswordReset] Redirect URL:",
redirectUrl
);
console.log(
"[authService.requestPasswordReset] Usando endpoint /auth/v1/recover"
);
const payload = {
email,
options: {
redirectTo: redirectUrl,
},
};
console.log("[authService.requestPasswordReset] Payload:", JSON.stringify(payload, null, 2));
await axios.post(
`${API_CONFIG.AUTH_URL}/recover`,
payload,
{
headers: {
"Content-Type": "application/json",
apikey: API_CONFIG.SUPABASE_ANON_KEY,
},
}
console.log(
"[authService.requestPasswordReset] Payload:",
JSON.stringify(payload, null, 2)
);
console.log("[authService.requestPasswordReset] ✅ Email de recuperação enviado");
await axios.post(`${API_CONFIG.AUTH_URL}/recover`, payload, {
headers: {
"Content-Type": "application/json",
apikey: API_CONFIG.SUPABASE_ANON_KEY,
},
});
console.log(
"[authService.requestPasswordReset] ✅ Email de recuperação enviado"
);
return {
success: true,
message: "Email de recuperação de senha enviado com sucesso. Verifique sua caixa de entrada.",
message:
"Email de recuperação de senha enviado com sucesso. Verifique sua caixa de entrada.",
};
} catch (error: any) {
console.error("[authService.requestPasswordReset] ❌ Erro:", error);
console.error("[authService.requestPasswordReset] Response:", error.response?.data);
console.error(
"[authService.requestPasswordReset] Response:",
error.response?.data
);
throw error;
}
}
@ -209,10 +219,21 @@ class AuthService {
newPassword: string
): Promise<{ success: boolean; message: string }> {
try {
console.log("[authService.updatePassword] Iniciando atualização de senha");
console.log("[authService.updatePassword] Token (primeiros 30 chars):", accessToken.substring(0, 30));
console.log("[authService.updatePassword] Nova senha length:", newPassword.length);
console.log("[authService.updatePassword] URL:", `${API_CONFIG.AUTH_URL}/user`);
console.log(
"[authService.updatePassword] Iniciando atualização de senha"
);
console.log(
"[authService.updatePassword] Token (primeiros 30 chars):",
accessToken.substring(0, 30)
);
console.log(
"[authService.updatePassword] Nova senha length:",
newPassword.length
);
console.log(
"[authService.updatePassword] URL:",
`${API_CONFIG.AUTH_URL}/user`
);
const response = await axios.put(
`${API_CONFIG.AUTH_URL}/user`,
@ -228,35 +249,76 @@ class AuthService {
}
);
console.log("[authService.updatePassword] ✅ Senha atualizada com sucesso");
console.log("[authService.updatePassword] Response status:", response.status);
console.log(
"[authService.updatePassword] ✅ Senha atualizada com sucesso"
);
console.log(
"[authService.updatePassword] Response status:",
response.status
);
return {
success: true,
message: "Senha atualizada com sucesso",
};
} catch (error: any) {
console.error("[authService.updatePassword] ❌ Erro ao atualizar senha:", error);
console.error("[authService.updatePassword] Error response:", error.response?.data);
console.error("[authService.updatePassword] Error response (stringified):", JSON.stringify(error.response?.data, null, 2));
console.error("[authService.updatePassword] Error status:", error.response?.status);
console.error("[authService.updatePassword] Error statusText:", error.response?.statusText);
console.error("[authService.updatePassword] Error headers:", error.response?.headers);
console.error("[authService.updatePassword] Request URL:", error.config?.url);
console.error("[authService.updatePassword] Request headers:", error.config?.headers);
console.error("[authService.updatePassword] Request data:", error.config?.data);
console.error(
"[authService.updatePassword] ❌ Erro ao atualizar senha:",
error
);
console.error(
"[authService.updatePassword] Error response:",
error.response?.data
);
console.error(
"[authService.updatePassword] Error response (stringified):",
JSON.stringify(error.response?.data, null, 2)
);
console.error(
"[authService.updatePassword] Error status:",
error.response?.status
);
console.error(
"[authService.updatePassword] Error statusText:",
error.response?.statusText
);
console.error(
"[authService.updatePassword] Error headers:",
error.response?.headers
);
console.error(
"[authService.updatePassword] Request URL:",
error.config?.url
);
console.error(
"[authService.updatePassword] Request headers:",
error.config?.headers
);
console.error(
"[authService.updatePassword] Request data:",
error.config?.data
);
// Se for 422, mostrar mensagem específica
if (error.response?.status === 422) {
const errorMsg = error.response?.data?.msg || error.response?.data?.message || error.response?.data?.error_description || "Token inválido ou expirado";
console.error("[authService.updatePassword] ⚠️ Erro 422 - Possíveis causas:");
const errorMsg =
error.response?.data?.msg ||
error.response?.data?.message ||
error.response?.data?.error_description ||
"Token inválido ou expirado";
console.error(
"[authService.updatePassword] ⚠️ Erro 422 - Possíveis causas:"
);
console.error(" 1. Token de recuperação expirado (válido por 1 hora)");
console.error(" 2. Token já foi usado");
console.error(" 3. Formato do token incorreto");
console.error(" 4. Usuário não existe");
console.error("[authService.updatePassword] Mensagem do servidor:", errorMsg);
console.error(
"[authService.updatePassword] Mensagem do servidor:",
errorMsg
);
}
throw error;
}
}

View File

@ -22,11 +22,11 @@ class AvatarService {
async upload(data: UploadAvatarInput): Promise<UploadAvatarResponse> {
try {
const token = localStorage.getItem(API_CONFIG.STORAGE_KEYS.ACCESS_TOKEN);
if (!token) {
throw new Error("Token de autenticação não encontrado");
}
// Determina a extensão do arquivo
const ext = data.file.name.split(".").pop()?.toLowerCase() || "jpg";
const filePath = `${data.userId}/avatar.${ext}`;
@ -46,14 +46,14 @@ class AvatarService {
// Upload usando Supabase Storage API
// x-upsert: true permite sobrescrever arquivos existentes
// Importante: NÃO definir Content-Type manualmente, deixar o axios/navegador
// Importante: NÃO definir Content-Type manualmente, deixar o axios/navegador
// definir automaticamente com o boundary correto para multipart/form-data
const response = await axios.post(
`${this.STORAGE_URL}/${this.BUCKET_NAME}/${filePath}`,
formData,
{
headers: {
"Authorization": `Bearer ${token}`,
Authorization: `Bearer ${token}`,
"x-upsert": "true",
},
}
@ -91,7 +91,9 @@ class AvatarService {
try {
// Não há endpoint de delete, então apenas removemos a referência do perfil
// O upload futuro irá sobrescrever a imagem antiga
console.log("Avatar será removido do perfil. Upload futuro sobrescreverá a imagem.");
console.log(
"Avatar será removido do perfil. Upload futuro sobrescreverá a imagem."
);
} catch (error) {
console.error("Erro ao deletar avatar:", error);
throw error;