feat: versão 1.5 - CRM Beta com leads, funis, campanhas e portal do cliente
This commit is contained in:
48
front-end-agency/app/api/portal/change-password/route.ts
Normal file
48
front-end-agency/app/api/portal/change-password/route.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const token = request.headers.get('Authorization')?.replace('Bearer ', '');
|
||||
|
||||
if (!token) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Token não fornecido' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
|
||||
if (!body.current_password || !body.new_password) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Senha atual e nova senha são obrigatórias' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const response = await fetch('http://aggios-backend:8080/api/portal/change-password', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
return NextResponse.json(
|
||||
{ error: errorData.error || 'Erro ao alterar senha' },
|
||||
{ status: response.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({ message: 'Senha alterada com sucesso' });
|
||||
} catch (error) {
|
||||
console.error('Change password error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Erro ao alterar senha' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
34
front-end-agency/app/api/portal/dashboard/route.ts
Normal file
34
front-end-agency/app/api/portal/dashboard/route.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const token = request.headers.get('authorization');
|
||||
|
||||
if (!token) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Token não fornecido' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
const response = await fetch('http://aggios-backend:8080/api/portal/dashboard', {
|
||||
headers: {
|
||||
'Authorization': token,
|
||||
},
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(data, { status: response.status });
|
||||
}
|
||||
|
||||
return NextResponse.json(data);
|
||||
} catch (error) {
|
||||
console.error('Dashboard fetch error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Erro ao buscar dados do dashboard' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
34
front-end-agency/app/api/portal/leads/route.ts
Normal file
34
front-end-agency/app/api/portal/leads/route.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const token = request.headers.get('authorization');
|
||||
|
||||
if (!token) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Token não fornecido' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
const response = await fetch('http://aggios-backend:8080/api/portal/leads', {
|
||||
headers: {
|
||||
'Authorization': token,
|
||||
},
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(data, { status: response.status });
|
||||
}
|
||||
|
||||
return NextResponse.json(data);
|
||||
} catch (error) {
|
||||
console.error('Leads fetch error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Erro ao buscar leads' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
30
front-end-agency/app/api/portal/login/route.ts
Normal file
30
front-end-agency/app/api/portal/login/route.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
|
||||
// Usar endpoint unificado
|
||||
const response = await fetch('http://aggios-backend:8080/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(data, { status: response.status });
|
||||
}
|
||||
|
||||
return NextResponse.json(data);
|
||||
} catch (error) {
|
||||
console.error('Customer login error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Erro ao processar login' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
36
front-end-agency/app/api/portal/profile/route.ts
Normal file
36
front-end-agency/app/api/portal/profile/route.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const token = request.headers.get('Authorization')?.replace('Bearer ', '');
|
||||
|
||||
if (!token) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Token não fornecido' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
const response = await fetch('http://aggios-backend:8080/api/portal/profile', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Erro ao buscar perfil' },
|
||||
{ status: response.status }
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return NextResponse.json(data);
|
||||
} catch (error) {
|
||||
console.error('Profile fetch error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Erro ao buscar perfil' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
125
front-end-agency/app/api/portal/register/route.ts
Normal file
125
front-end-agency/app/api/portal/register/route.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { writeFile } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const formData = await request.formData();
|
||||
|
||||
// Extrair campos do FormData
|
||||
const personType = formData.get('person_type') as string;
|
||||
const email = formData.get('email') as string;
|
||||
const phone = formData.get('phone') as string;
|
||||
const cpf = formData.get('cpf') as string || '';
|
||||
const fullName = formData.get('full_name') as string || '';
|
||||
const cnpj = formData.get('cnpj') as string || '';
|
||||
const companyName = formData.get('company_name') as string || '';
|
||||
const tradeName = formData.get('trade_name') as string || '';
|
||||
const postalCode = formData.get('postal_code') as string || '';
|
||||
const street = formData.get('street') as string || '';
|
||||
const number = formData.get('number') as string || '';
|
||||
const complement = formData.get('complement') as string || '';
|
||||
const neighborhood = formData.get('neighborhood') as string || '';
|
||||
const city = formData.get('city') as string || '';
|
||||
const state = formData.get('state') as string || '';
|
||||
const message = formData.get('message') as string || '';
|
||||
const logoFile = formData.get('logo') as File | null;
|
||||
|
||||
// Validar campos obrigatórios
|
||||
if (!email || !phone) {
|
||||
return NextResponse.json(
|
||||
{ error: 'E-mail e telefone são obrigatórios' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Validar campos específicos por tipo
|
||||
if (personType === 'pf') {
|
||||
if (!cpf || !fullName) {
|
||||
return NextResponse.json(
|
||||
{ error: 'CPF e Nome Completo são obrigatórios para Pessoa Física' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
} else if (personType === 'pj') {
|
||||
if (!cnpj || !companyName) {
|
||||
return NextResponse.json(
|
||||
{ error: 'CNPJ e Razão Social são obrigatórios para Pessoa Jurídica' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Processar upload de logo
|
||||
let logoPath = '';
|
||||
if (logoFile && logoFile.size > 0) {
|
||||
try {
|
||||
const bytes = await logoFile.arrayBuffer();
|
||||
const buffer = Buffer.from(bytes);
|
||||
|
||||
// Criar nome único para o arquivo
|
||||
const timestamp = Date.now();
|
||||
const fileExt = logoFile.name.split('.').pop();
|
||||
const fileName = `logo-${timestamp}.${fileExt}`;
|
||||
const uploadDir = join(process.cwd(), 'public', 'uploads', 'logos');
|
||||
logoPath = `/uploads/logos/${fileName}`;
|
||||
|
||||
// Salvar arquivo (em produção, use S3, Cloudinary, etc.)
|
||||
await writeFile(join(uploadDir, fileName), buffer);
|
||||
} catch (uploadError) {
|
||||
console.error('Error uploading logo:', uploadError);
|
||||
// Continuar sem logo em caso de erro
|
||||
}
|
||||
}
|
||||
|
||||
// Buscar tenant_id do subdomínio (por enquanto hardcoded como 1)
|
||||
const tenantId = 1;
|
||||
|
||||
// Preparar nome baseado no tipo
|
||||
const customerName = personType === 'pf' ? fullName : (tradeName || companyName);
|
||||
|
||||
// Preparar endereço completo
|
||||
const addressParts = [street, number, complement, neighborhood, city, state, postalCode].filter(Boolean);
|
||||
const fullAddress = addressParts.join(', ');
|
||||
|
||||
// Criar o cliente no backend
|
||||
const response = await fetch('http://aggios-backend:8080/api/crm/customers', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
tenant_id: tenantId,
|
||||
name: customerName,
|
||||
email: email,
|
||||
phone: phone,
|
||||
company: personType === 'pj' ? companyName : '',
|
||||
address: fullAddress,
|
||||
notes: JSON.stringify({
|
||||
person_type: personType,
|
||||
cpf, cnpj, full_name: fullName, company_name: companyName, trade_name: tradeName,
|
||||
postal_code: postalCode, street, number, complement, neighborhood, city, state,
|
||||
message, logo_path: logoPath,
|
||||
}),
|
||||
status: 'lead',
|
||||
source: 'cadastro_publico',
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.error || 'Erro ao criar cadastro');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
return NextResponse.json({
|
||||
message: 'Cadastro realizado com sucesso! Você receberá um e-mail com as credenciais.',
|
||||
customer_id: data.customer?.id,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('Register error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Erro ao processar cadastro' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user