chore: Reorganize project file structure.

This commit is contained in:
M-Gabrielly 2025-10-06 23:44:04 -03:00
parent caed16fabd
commit 6aef2b4910
128 changed files with 9625 additions and 9891 deletions

29
.gitignore vendored
View File

@ -1,2 +1,29 @@
node_modules/
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
# next.js
/.next/
/out/
# production
/build
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.tsriseup-squad20/
susconecta/riseup-squad20/
riseup-squad20/

View File

@ -0,0 +1,370 @@
# ✅ Implementação: Opção 2 - Vínculo por Email
## 🎯 Solução Implementada
**Vínculo entre Supabase Auth e API Mock através do EMAIL**
```
┌──────────────────────────┐
│ Supabase Auth │
│ (Login/Autenticação) │
│ │
│ email: "user@email.com"│ ◄─┐
│ password: "senha123!" │ │
│ userType: "paciente" │ │
└──────────────────────────┘ │
│ VÍNCULO
┌──────────────────────────┐ │ POR EMAIL
│ API Mock (Apidog) │ │
│ (Dados do Sistema) │ │
│ │ │
│ email: "user@email.com"│ ◄─┘
│ full_name: "João Silva"│
│ cpf: "123.456.789-00" │
└──────────────────────────┘
```
---
## 📝 Código Implementado
### `lib/api.ts` - Funções de Criação de Usuários
```typescript
import { ENV_CONFIG } from '@/lib/env-config';
import { API_KEY } from '@/lib/config';
// Gera senha aleatória (formato: senhaXXX!)
export function gerarSenhaAleatoria(): string {
const num1 = Math.floor(Math.random() * 10);
const num2 = Math.floor(Math.random() * 10);
const num3 = Math.floor(Math.random() * 10);
return `senha${num1}${num2}${num3}!`;
}
// Cria usuário MÉDICO no Supabase Auth
export async function criarUsuarioMedico(medico: {
email: string;
full_name: string;
phone_mobile: string;
}): Promise<CreateUserWithPasswordResponse> {
const senha = gerarSenhaAleatoria();
// Endpoint do Supabase Auth (mesmo que auth.ts usa)
const signupUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/signup`;
const payload = {
email: medico.email, // ◄── VÍNCULO!
password: senha,
data: {
userType: 'profissional',
full_name: medico.full_name,
phone: medico.phone_mobile,
},
};
const response = await fetch(signupUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
apikey: API_KEY,
},
body: JSON.stringify(payload),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Erro ao criar usuário: ${response.status}`);
}
const responseData = await response.json();
return {
success: true,
user: responseData.user,
email: medico.email,
password: senha,
};
}
// Cria usuário PACIENTE no Supabase Auth
export async function criarUsuarioPaciente(paciente: {
email: string;
full_name: string;
phone_mobile: string;
}): Promise<CreateUserWithPasswordResponse> {
const senha = gerarSenhaAleatoria();
const signupUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/signup`;
const payload = {
email: paciente.email, // ◄── VÍNCULO!
password: senha,
data: {
userType: 'paciente',
full_name: paciente.full_name,
phone: paciente.phone_mobile,
},
};
const response = await fetch(signupUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
apikey: API_KEY,
},
body: JSON.stringify(payload),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Erro ao criar usuário: ${response.status}`);
}
const responseData = await response.json();
return {
success: true,
user: responseData.user,
email: paciente.email,
password: senha,
};
}
```
---
## 🔄 Fluxo Completo
### 1⃣ **Admin Cadastra Paciente**
```typescript
// components/forms/patient-registration-form.tsx
async function handleSubmit() {
// 1. Salva paciente na API Mock
const saved = await salvarPaciente({
full_name: form.nome,
email: form.email, // ◄── EMAIL usado como vínculo
cpf: form.cpf,
telefone: form.telefone,
// ...outros dados
});
// 2. Cria usuário no Supabase Auth com MESMO EMAIL
if (mode === 'create' && form.email) {
try {
const credentials = await criarUsuarioPaciente({
email: form.email, // ◄── MESMO EMAIL!
full_name: form.nome,
phone_mobile: form.telefone,
});
// 3. Mostra popup com credenciais
setCredentials(credentials);
setShowCredentials(true);
} catch (error) {
alert('Paciente cadastrado, mas houve erro ao criar usuário de acesso');
}
}
}
```
### 2⃣ **Paciente Faz Login**
```typescript
// Paciente vai em /login-paciente
// Digita: email = "jonas@email.com", password = "senha481!"
// hooks/useAuth.tsx
const login = async (email, password, userType) => {
// Autentica no Supabase Auth
const response = await loginUser(email, password, userType);
// Token JWT contém o email
const token = response.access_token;
const decoded = decodeJWT(token);
console.log(decoded.email); // "jonas@email.com"
// Salva sessão
localStorage.setItem('token', token);
// Redireciona para /paciente
router.push('/paciente');
};
```
### 3⃣ **Buscar Dados do Paciente na Área Logada**
```typescript
// app/paciente/page.tsx
export default function PacientePage() {
const { user } = useAuth(); // user.email = "jonas@email.com"
useEffect(() => {
async function carregarDados() {
// Busca paciente pelo EMAIL (vínculo)
const response = await fetch(
`https://mock.apidog.com/pacientes?email=${user.email}`
);
const paciente = await response.json();
// Agora tem os dados completos do paciente
console.log(paciente);
// {
// id: "123",
// full_name: "Jonas Francisco",
// email: "jonas@email.com", ◄── VÍNCULO!
// cpf: "123.456.789-00",
// ...
// }
}
carregarDados();
}, [user.email]);
return <div>Bem-vindo, {user.email}!</div>;
}
```
---
## 🔐 Estrutura dos Dados
### **Supabase Auth (`auth.users`)**
```json
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "jonas@email.com",
"encrypted_password": "$2a$10$...",
"created_at": "2025-10-03T00:00:00",
"user_metadata": {
"userType": "paciente",
"full_name": "Jonas Francisco",
"phone": "(79) 99649-8907"
}
}
```
### **API Mock - Tabela `pacientes`**
```json
{
"id": "123",
"full_name": "Jonas Francisco Nascimento Bonfim",
"email": "jonas@email.com",
"cpf": "123.456.789-00",
"telefone": "(79) 99649-8907",
"data_nascimento": "1990-01-15",
"endereco": {
"cep": "49000-000",
"logradouro": "Rua Principal",
"cidade": "Aracaju"
}
}
```
**Vínculo:** Campo `email` presente em ambos os sistemas!
---
## 📊 Vantagens da Opção 2
**Simples:** Não precisa modificar estrutura da API Mock
**Natural:** Email já é único e obrigatório
**Sem duplicação:** Usa campo existente
**Funcional:** Supabase Auth retorna email no token JWT
**Escalável:** Fácil de manter e debugar
---
## ⚠️ Considerações Importantes
### **Email DEVE ser único**
- Cada email só pode ter um usuário no Supabase Auth
- Cada email só pode ter um paciente/médico na API Mock
- Se tentar cadastrar email duplicado, Supabase retorna erro
### **Email DEVE ser válido**
- Supabase valida formato (nome@dominio.com)
- Use validação no formulário antes de enviar
### **Formato da senha**
- Supabase exige mínimo 6 caracteres
- Geramos: `senhaXXX!` (10 caracteres) ✅
---
## 🧪 Logs Esperados (Sucesso)
```
🏥 [CRIAR PACIENTE] Iniciando criação no Supabase Auth...
📧 Email: jonas@email.com
👤 Nome: Jonas Francisco Nascimento Bonfim
📱 Telefone: (79) 99649-8907
🔑 Senha gerada: senha481!
📤 [CRIAR PACIENTE] Enviando para: https://yuanqfswhberkoevtmfr.supabase.co/auth/v1/signup
📋 [CRIAR PACIENTE] Status da resposta: 200 OK
✅ [CRIAR PACIENTE] Usuário criado com sucesso no Supabase Auth!
🆔 User ID: 550e8400-e29b-41d4-a716-446655440000
```
---
## ❌ Possíveis Erros
### **Erro: "Este email já está cadastrado no sistema"**
```
❌ [CRIAR PACIENTE] Erro: User already registered
```
**Solução:** Email já existe no Supabase. Use outro email ou delete o usuário existente.
### **Erro: "Formato de email inválido"**
```
❌ [CRIAR PACIENTE] Erro: Invalid email format
```
**Solução:** Valide o formato do email antes de enviar.
### **Erro: 429 - Too Many Requests**
```
❌ [CRIAR PACIENTE] Erro: 429
```
**Solução:** Aguarde 1 minuto. Supabase limita taxa de requisições.
---
## ✅ Resultado Final
**Agora o sistema funciona assim:**
1. ✅ Admin cadastra paciente → Salva na API Mock
2. ✅ Sistema cria usuário no Supabase Auth (mesmo email)
3. ✅ Popup mostra credenciais (email + senha)
4. ✅ Paciente faz login em `/login-paciente`
5. ✅ Login funciona! Token JWT é gerado
6. ✅ Sistema busca dados do paciente pelo email
7. ✅ Paciente acessa área `/paciente` com todos os dados
---
## 📅 Data da Implementação
3 de outubro de 2025
## 🔗 Arquivos Modificados
- ✅ `susconecta/lib/api.ts` - Funções de criação (Supabase Auth)
- ✅ `susconecta/components/forms/patient-registration-form.tsx` - Já integrado
- ✅ `susconecta/components/forms/doctor-registration-form.tsx` - Já integrado
- ✅ `susconecta/components/credentials-dialog.tsx` - Já implementado

9483
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,91 @@
{
"name": "my-v0-project",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"build": "next build",
"dev": "next dev",
"lint": "next lint",
"start": "next start"
},
"dependencies": {
"@headlessui/react": "^2.2.7",
"@heroicons/react": "^2.2.0",
"date-fns": "^4.1.0",
"react-big-calendar": "^1.19.4",
"react-signature-canvas": "^1.1.0-alpha.2"
"@fullcalendar/core": "^6.1.19",
"@fullcalendar/daygrid": "^6.1.19",
"@fullcalendar/interaction": "^6.1.19",
"@fullcalendar/react": "^6.1.19",
"@fullcalendar/timegrid": "^6.1.19",
"@hookform/resolvers": "^3.10.0",
"@radix-ui/react-accordion": "latest",
"@radix-ui/react-alert-dialog": "latest",
"@radix-ui/react-aspect-ratio": "latest",
"@radix-ui/react-avatar": "latest",
"@radix-ui/react-checkbox": "latest",
"@radix-ui/react-collapsible": "latest",
"@radix-ui/react-context-menu": "latest",
"@radix-ui/react-dialog": "latest",
"@radix-ui/react-dropdown-menu": "latest",
"@radix-ui/react-hover-card": "latest",
"@radix-ui/react-label": "latest",
"@radix-ui/react-menubar": "latest",
"@radix-ui/react-navigation-menu": "latest",
"@radix-ui/react-popover": "latest",
"@radix-ui/react-progress": "latest",
"@radix-ui/react-radio-group": "latest",
"@radix-ui/react-scroll-area": "latest",
"@radix-ui/react-select": "latest",
"@radix-ui/react-separator": "latest",
"@radix-ui/react-slider": "latest",
"@radix-ui/react-slot": "latest",
"@radix-ui/react-switch": "latest",
"@radix-ui/react-tabs": "latest",
"@radix-ui/react-toast": "latest",
"@radix-ui/react-toggle": "latest",
"@radix-ui/react-toggle-group": "latest",
"@radix-ui/react-tooltip": "latest",
"@vercel/analytics": "1.3.1",
"autoprefixer": "^10.4.20",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "latest",
"date-fns": "4.1.0",
"embla-carousel-react": "latest",
"geist": "^1.3.1",
"input-otp": "latest",
"jspdf": "^3.0.3",
"lucide-react": "^0.454.0",
"next-themes": "latest",
"react": "^18",
"react-day-picker": "latest",
"react-dom": "^18",
"react-hook-form": "latest",
"react-quill": "^2.0.0",
"react-resizable-panels": "latest",
"react-signature-canvas": "^1.1.0-alpha.2",
"recharts": "latest",
"sonner": "latest",
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7",
"vaul": "latest",
"zod": "3.25.67"
},
"devDependencies": {
"@eslint/js": "^9.36.0",
"@tailwindcss/postcss": "^4.1.9",
"@types/node": "^22",
"@types/react": "^18",
"@types/react-dom": "^18",
"@typescript-eslint/eslint-plugin": "^8.45.0",
"@typescript-eslint/parser": "^8.45.0",
"eslint": "^9.36.0",
"eslint-config-next": "^15.5.4",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-unicorn": "^61.0.2",
"next": "^15.5.4",
"postcss": "^8.5",
"tailwindcss": "^4.1.9",
"tw-animate-css": "1.3.3",
"typescript": "^5",
"typescript-eslint": "^8.45.0"
}
}

View File

Before

Width:  |  Height:  |  Size: 801 KiB

After

Width:  |  Height:  |  Size: 801 KiB

View File

Before

Width:  |  Height:  |  Size: 8.3 MiB

After

Width:  |  Height:  |  Size: 8.3 MiB

View File

Before

Width:  |  Height:  |  Size: 568 B

After

Width:  |  Height:  |  Size: 568 B

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Some files were not shown because too many files have changed in this diff Show More