feat: connect registration and jobs to real API

Backend fixes:
- Fix FK violation in candidate registration by creating company first
- Add CompanyRepository to RegisterCandidateUseCase
- Add handler integration tests for validation

Frontend improvements:
- Add registerCompany function in auth.ts
- Connect company registration form to backend API
- Replace mockJobs with API call in job detail page
- Add loading/error states to job detail page
- Add Jest tests for auth module
This commit is contained in:
Tiago Yamamoto 2025-12-23 08:19:49 -03:00
parent b09bd023ed
commit ce0531fefc
9 changed files with 570 additions and 46 deletions

View file

@ -0,0 +1,175 @@
package handlers_test
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/rede5/gohorsejobs/backend/internal/api/handlers"
"github.com/rede5/gohorsejobs/backend/internal/core/dto"
auth "github.com/rede5/gohorsejobs/backend/internal/core/usecases/auth"
tenant "github.com/rede5/gohorsejobs/backend/internal/core/usecases/tenant"
user "github.com/rede5/gohorsejobs/backend/internal/core/usecases/user"
)
// --- Mock Implementations ---
type mockUserRepo struct {
saveFunc func(user interface{}) (interface{}, error)
findByEmailFunc func(email string) (interface{}, error)
}
func (m *mockUserRepo) Save(ctx interface{}, user interface{}) (interface{}, error) {
if m.saveFunc != nil {
return m.saveFunc(user)
}
return user, nil
}
func (m *mockUserRepo) FindByEmail(ctx interface{}, email string) (interface{}, error) {
if m.findByEmailFunc != nil {
return m.findByEmailFunc(email)
}
return nil, nil
}
func (m *mockUserRepo) FindByID(ctx interface{}, id string) (interface{}, error) { return nil, nil }
func (m *mockUserRepo) FindAllByTenant(ctx interface{}, tenantID string, l, o int) ([]interface{}, int, error) {
return nil, 0, nil
}
func (m *mockUserRepo) Update(ctx interface{}, user interface{}) (interface{}, error) {
return nil, nil
}
func (m *mockUserRepo) Delete(ctx interface{}, id string) error { return nil }
type mockAuthService struct{}
func (m *mockAuthService) HashPassword(password string) (string, error) {
return "hashed_" + password, nil
}
func (m *mockAuthService) GenerateToken(userID, tenantID string, roles []string) (string, error) {
return "mock_token", nil
}
func (m *mockAuthService) VerifyPassword(hash, password string) bool { return true }
func (m *mockAuthService) ValidateToken(token string) (map[string]interface{}, error) {
return nil, nil
}
// --- Test Cases ---
func TestRegisterCandidateHandler_Success(t *testing.T) {
// This is a simplified integration test structure
// In production, you'd wire up the full dependency injection
t.Skip("Integration test requires full DI setup - use unit tests in usecases/auth instead")
}
func TestRegisterCandidateHandler_InvalidPayload(t *testing.T) {
// Create a minimal handler for testing payload validation
coreHandlers := createTestCoreHandlers(t)
if coreHandlers == nil {
t.Skip("Cannot create test handlers - skipping")
return
}
// Test with invalid JSON
body := bytes.NewBufferString("{invalid json}")
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/register", body)
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
coreHandlers.RegisterCandidate(rec, req)
if rec.Code != http.StatusBadRequest {
t.Errorf("Expected status %d, got %d", http.StatusBadRequest, rec.Code)
}
}
func TestRegisterCandidateHandler_MissingFields(t *testing.T) {
coreHandlers := createTestCoreHandlers(t)
if coreHandlers == nil {
t.Skip("Cannot create test handlers - skipping")
return
}
testCases := []struct {
name string
payload dto.RegisterCandidateRequest
wantCode int
}{
{
name: "Missing Email",
payload: dto.RegisterCandidateRequest{Name: "John", Password: "123456"},
wantCode: http.StatusBadRequest,
},
{
name: "Missing Password",
payload: dto.RegisterCandidateRequest{Name: "John", Email: "john@example.com"},
wantCode: http.StatusBadRequest,
},
{
name: "Missing Name",
payload: dto.RegisterCandidateRequest{Email: "john@example.com", Password: "123456"},
wantCode: http.StatusBadRequest,
},
{
name: "All Empty",
payload: dto.RegisterCandidateRequest{},
wantCode: http.StatusBadRequest,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
body, _ := json.Marshal(tc.payload)
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/register", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
coreHandlers.RegisterCandidate(rec, req)
if rec.Code != tc.wantCode {
t.Errorf("Expected status %d, got %d", tc.wantCode, rec.Code)
}
})
}
}
func TestLoginHandler_InvalidPayload(t *testing.T) {
coreHandlers := createTestCoreHandlers(t)
if coreHandlers == nil {
t.Skip("Cannot create test handlers - skipping")
return
}
body := bytes.NewBufferString("{invalid}")
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", body)
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
coreHandlers.Login(rec, req)
if rec.Code != http.StatusBadRequest {
t.Errorf("Expected status %d, got %d", http.StatusBadRequest, rec.Code)
}
}
// createTestCoreHandlers creates handlers with nil usecases for basic validation tests
// For full integration tests, wire up real mock implementations
func createTestCoreHandlers(t *testing.T) *handlers.CoreHandlers {
t.Helper()
// Return nil - these tests need proper DI which we skip for now
// The real tests are in usecases/auth package
return handlers.NewCoreHandlers(
(*auth.LoginUseCase)(nil),
(*auth.RegisterCandidateUseCase)(nil),
(*tenant.CreateCompanyUseCase)(nil),
(*user.CreateUserUseCase)(nil),
(*user.ListUsersUseCase)(nil),
(*user.DeleteUserUseCase)(nil),
(*tenant.ListCompaniesUseCase)(nil),
nil, // auditService
)
}

View file

@ -3,6 +3,7 @@ package auth
import (
"context"
"errors"
"fmt"
"github.com/google/uuid"
"github.com/rede5/gohorsejobs/backend/internal/core/domain/entity"
@ -12,12 +13,14 @@ import (
type RegisterCandidateUseCase struct {
userRepo ports.UserRepository
companyRepo ports.CompanyRepository
authService ports.AuthService
}
func NewRegisterCandidateUseCase(uRepo ports.UserRepository, auth ports.AuthService) *RegisterCandidateUseCase {
func NewRegisterCandidateUseCase(uRepo ports.UserRepository, cRepo ports.CompanyRepository, auth ports.AuthService) *RegisterCandidateUseCase {
return &RegisterCandidateUseCase{
userRepo: uRepo,
companyRepo: cRepo,
authService: auth,
}
}
@ -35,10 +38,21 @@ func (uc *RegisterCandidateUseCase) Execute(ctx context.Context, input dto.Regis
return nil, err
}
// 3. Create Entity
// Candidates belong to their own tenant/workspace in this model logic
// 3. Create Candidate's Personal Tenant/Workspace
candidateTenantID := uuid.New().String()
candidateCompany := entity.NewCompany(
candidateTenantID,
fmt.Sprintf("Candidate - %s", input.Name),
nil, // No document for candidates
nil, // No contact - will use user's contact info
)
_, err = uc.companyRepo.Save(ctx, candidateCompany)
if err != nil {
return nil, fmt.Errorf("failed to create candidate workspace: %w", err)
}
// 4. Create User Entity
user := entity.NewUser(uuid.New().String(), candidateTenantID, input.Name, input.Email)
user.PasswordHash = hashed

View file

@ -39,6 +39,27 @@ func (m *MockUserRepo) Update(ctx context.Context, user *entity.User) (*entity.U
}
func (m *MockUserRepo) Delete(ctx context.Context, id string) error { return nil }
type MockCompanyRepo struct {
SaveFunc func(ctx context.Context, company *entity.Company) (*entity.Company, error)
}
func (m *MockCompanyRepo) Save(ctx context.Context, company *entity.Company) (*entity.Company, error) {
if m.SaveFunc != nil {
return m.SaveFunc(ctx, company)
}
return company, nil
}
func (m *MockCompanyRepo) FindByID(ctx context.Context, id string) (*entity.Company, error) {
return nil, nil
}
func (m *MockCompanyRepo) FindAll(ctx context.Context) ([]*entity.Company, error) {
return nil, nil
}
func (m *MockCompanyRepo) Update(ctx context.Context, company *entity.Company) (*entity.Company, error) {
return nil, nil
}
func (m *MockCompanyRepo) Delete(ctx context.Context, id string) error { return nil }
type MockAuthService struct {
HashPasswordFunc func(password string) (string, error)
GenerateTokenFunc func(userID, tenantID string, roles []string) (string, error)
@ -65,7 +86,7 @@ func (m *MockAuthService) ValidateToken(token string) (map[string]interface{}, e
func TestRegisterCandidateUseCase_Execute(t *testing.T) {
t.Run("Success", func(t *testing.T) {
repo := &MockUserRepo{
userRepo := &MockUserRepo{
FindByEmailFunc: func(ctx context.Context, email string) (*entity.User, error) {
return nil, nil // Email not found
},
@ -74,9 +95,10 @@ func TestRegisterCandidateUseCase_Execute(t *testing.T) {
return user, nil
},
}
companyRepo := &MockCompanyRepo{}
authSvc := &MockAuthService{}
uc := auth.NewRegisterCandidateUseCase(repo, authSvc)
uc := auth.NewRegisterCandidateUseCase(userRepo, companyRepo, authSvc)
input := dto.RegisterCandidateRequest{
Name: "John Doe",
@ -103,13 +125,14 @@ func TestRegisterCandidateUseCase_Execute(t *testing.T) {
})
t.Run("EmailAlreadyExists", func(t *testing.T) {
repo := &MockUserRepo{
userRepo := &MockUserRepo{
FindByEmailFunc: func(ctx context.Context, email string) (*entity.User, error) {
return &entity.User{ID: "existing"}, nil // Found
},
}
companyRepo := &MockCompanyRepo{}
authSvc := &MockAuthService{}
uc := auth.NewRegisterCandidateUseCase(repo, authSvc)
uc := auth.NewRegisterCandidateUseCase(userRepo, companyRepo, authSvc)
_, err := uc.Execute(context.Background(), dto.RegisterCandidateRequest{Email: "exists@example.com", Password: "123"})
@ -124,14 +147,15 @@ func TestRegisterCandidateUseCase_Execute(t *testing.T) {
t.Run("MetadataSaved", func(t *testing.T) {
// Verify if username/phone ends up in metadata
var capturedUser *entity.User
repo := &MockUserRepo{
userRepo := &MockUserRepo{
SaveFunc: func(ctx context.Context, user *entity.User) (*entity.User, error) {
capturedUser = user
return user, nil
},
}
companyRepo := &MockCompanyRepo{}
authSvc := &MockAuthService{}
uc := auth.NewRegisterCandidateUseCase(repo, authSvc)
uc := auth.NewRegisterCandidateUseCase(userRepo, companyRepo, authSvc)
uc.Execute(context.Background(), dto.RegisterCandidateRequest{Username: "coder", Phone: "999"})
@ -145,4 +169,26 @@ func TestRegisterCandidateUseCase_Execute(t *testing.T) {
t.Errorf("Expected metadata phone '999', got %v", capturedUser.Metadata["phone"])
}
})
t.Run("CompanyCreatedForCandidate", func(t *testing.T) {
var capturedCompany *entity.Company
userRepo := &MockUserRepo{}
companyRepo := &MockCompanyRepo{
SaveFunc: func(ctx context.Context, company *entity.Company) (*entity.Company, error) {
capturedCompany = company
return company, nil
},
}
authSvc := &MockAuthService{}
uc := auth.NewRegisterCandidateUseCase(userRepo, companyRepo, authSvc)
uc.Execute(context.Background(), dto.RegisterCandidateRequest{Name: "Test User", Email: "test@test.com", Password: "123"})
if capturedCompany == nil {
t.Fatal("Company not created")
}
if capturedCompany.Name != "Candidate - Test User" {
t.Errorf("Expected company name 'Candidate - Test User', got %s", capturedCompany.Name)
}
})
}

View file

@ -50,7 +50,7 @@ func NewRouter() http.Handler {
// UseCases
loginUC := authUC.NewLoginUseCase(userRepo, authService)
registerCandidateUC := authUC.NewRegisterCandidateUseCase(userRepo, authService)
registerCandidateUC := authUC.NewRegisterCandidateUseCase(userRepo, companyRepo, authService)
createCompanyUC := tenantUC.NewCreateCompanyUseCase(companyRepo, userRepo, authService)
listCompaniesUC := tenantUC.NewListCompaniesUseCase(companyRepo)
createUserUC := userUC.NewCreateUserUseCase(userRepo, authService)

View file

@ -1,6 +1,6 @@
"use client";
import { use } from "react";
import { use, useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { Navbar } from "@/components/navbar";
import { Footer } from "@/components/footer";
@ -15,7 +15,7 @@ import {
import { Badge } from "@/components/ui/badge";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Separator } from "@/components/ui/separator";
import { mockJobs } from "@/lib/mock-data";
import { jobsApi, transformJob, type Job } from "@/lib/api";
import {
MapPin,
Briefcase,
@ -31,9 +31,9 @@ import {
Bookmark,
Star,
Globe,
Loader2,
} from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { motion } from "framer-motion";
@ -48,8 +48,44 @@ export default function JobDetailPage({
const router = useRouter();
const [isFavorited, setIsFavorited] = useState(false);
const [isBookmarked, setIsBookmarked] = useState(false);
const [job, setJob] = useState<Job | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const job = mockJobs.find((j) => j.id === id);
useEffect(() => {
async function fetchJob() {
try {
setLoading(true);
const response = await jobsApi.getById(id);
if (response.data) {
setJob(transformJob(response.data));
} else {
setError("Job not found");
}
} catch (err) {
console.error("Error fetching job:", err);
setError("Failed to load job");
} finally {
setLoading(false);
}
}
fetchJob();
}, [id]);
if (loading) {
return (
<div className="min-h-screen flex flex-col">
<Navbar />
<main className="flex-1 flex items-center justify-center">
<div className="text-center">
<Loader2 className="w-8 h-8 animate-spin mx-auto mb-4 text-primary" />
<p className="text-muted-foreground">Loading job...</p>
</div>
</main>
<Footer />
</div>
);
}
if (!job) {
return (
@ -189,8 +225,8 @@ export default function JobDetailPage({
>
<Heart
className={`h-4 w-4 ${isFavorited
? "fill-red-500 text-red-500"
: ""
? "fill-red-500 text-red-500"
: ""
}`}
/>
</Button>
@ -220,8 +256,8 @@ export default function JobDetailPage({
>
<Heart
className={`h-4 w-4 mr-1 ${isFavorited
? "fill-red-500 text-red-500"
: ""
? "fill-red-500 text-red-500"
: ""
}`}
/>
{isFavorited ? "Favorited" : "Favorite"}
@ -486,28 +522,9 @@ export default function JobDetailPage({
<CardTitle className="text-lg">Similar jobs</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{mockJobs
.filter((j) => j.id !== job.id)
.slice(0, 3)
.map((similarJob) => (
<Link
key={similarJob.id}
href={`/jobs/${similarJob.id}`}
>
<div className="p-3 rounded-lg border hover:bg-muted/50 transition-colors cursor-pointer">
<h4 className="font-medium text-sm mb-1 line-clamp-1">
{similarJob.title}
</h4>
<p className="text-xs text-muted-foreground mb-2">
{similarJob.company}
</p>
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<MapPin className="h-3 w-3" />
<span>{similarJob.location}</span>
</div>
</div>
</Link>
))}
<p className="text-sm text-muted-foreground">
Find more opportunities like this one.
</p>
<Link href="/jobs">
<Button variant="outline" size="sm" className="w-full">
View all jobs

View file

@ -88,22 +88,32 @@ export default function CompanyRegisterPage() {
const acceptTerms = watch("acceptTerms");
const acceptNewsletter = watch("acceptNewsletter");
const [errorMsg, setErrorMsg] = useState<string | null>(null);
const onSubmit = async (data: CompanyFormData) => {
setLoading(true);
setErrorMsg(null);
try {
// Simulate registration
await new Promise(resolve => setTimeout(resolve, 2000));
console.log("Company data:", data);
const { registerCompany } = await import("@/lib/auth");
await registerCompany({
companyName: data.companyName,
cnpj: data.cnpj,
email: data.email,
phone: data.phone,
});
// Redirect to login after registration
router.push("/login?message=Registration completed successfully! Please sign in to continue.");
} catch (error) {
router.push("/login?message=Empresa registrada com sucesso! Faça login com seu email e a senha padrão: ChangeMe123!");
} catch (error: any) {
console.error("Registration error:", error);
setErrorMsg(error.message || "Erro ao registrar empresa. Tente novamente.");
} finally {
setLoading(false);
}
};
const nextStep = () => {
if (currentStep < 3) setCurrentStep(currentStep + 1);
};

View file

@ -0,0 +1,215 @@
import { RegisterCandidateData } from '../auth';
// Mock fetch globally
const mockFetch = jest.fn();
global.fetch = mockFetch;
// Mock localStorage
const localStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
clear: jest.fn(),
};
Object.defineProperty(window, 'localStorage', { value: localStorageMock });
describe('Auth Module', () => {
let authModule: typeof import('../auth');
beforeEach(() => {
jest.resetModules();
mockFetch.mockReset();
localStorageMock.getItem.mockReset();
localStorageMock.setItem.mockReset();
localStorageMock.removeItem.mockReset();
// Re-import the module fresh
authModule = require('../auth');
});
describe('registerCandidate', () => {
const validData: RegisterCandidateData = {
name: 'John Doe',
email: 'john@example.com',
password: 'password123',
username: 'johndoe',
phone: '+5511999999999',
};
it('should register candidate successfully', async () => {
const mockResponse = {
token: 'mock-jwt-token',
user: {
id: '123',
name: 'John Doe',
email: 'john@example.com',
roles: ['CANDIDATE'],
},
};
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => mockResponse,
});
await expect(authModule.registerCandidate(validData)).resolves.not.toThrow();
expect(mockFetch).toHaveBeenCalledWith(
expect.stringContaining('/auth/register'),
expect.objectContaining({
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(validData),
})
);
});
it('should throw error when email already exists', async () => {
const errorResponse = { message: 'email already registered' };
mockFetch.mockResolvedValueOnce({
ok: false,
status: 409,
json: async () => errorResponse,
});
await expect(authModule.registerCandidate(validData)).rejects.toThrow(
'email already registered'
);
});
it('should throw error with status code when no message provided', async () => {
mockFetch.mockResolvedValueOnce({
ok: false,
status: 500,
json: async () => ({}),
});
await expect(authModule.registerCandidate(validData)).rejects.toThrow(
'Erro no registro: 500'
);
});
it('should handle network errors gracefully', async () => {
mockFetch.mockRejectedValueOnce(new Error('Network error'));
await expect(authModule.registerCandidate(validData)).rejects.toThrow(
'Network error'
);
});
it('should send all required fields in request body', async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({}),
});
await authModule.registerCandidate(validData);
const callArgs = mockFetch.mock.calls[0];
const body = JSON.parse(callArgs[1].body);
expect(body).toEqual({
name: 'John Doe',
email: 'john@example.com',
password: 'password123',
username: 'johndoe',
phone: '+5511999999999',
});
});
});
describe('login', () => {
it('should login and store user in localStorage', async () => {
const mockResponse = {
token: 'mock-jwt-token',
user: {
id: '123',
name: 'Test User',
email: 'test@example.com',
roles: ['CANDIDATE'],
status: 'active',
created_at: '2023-01-01T00:00:00Z',
},
};
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => mockResponse,
});
const user = await authModule.login('test@example.com', 'password123');
expect(user).toBeDefined();
expect(user?.email).toBe('test@example.com');
expect(localStorageMock.setItem).toHaveBeenCalledWith(
'job-portal-auth',
expect.any(String)
);
expect(localStorageMock.setItem).toHaveBeenCalledWith(
'auth_token',
'mock-jwt-token'
);
});
it('should throw error on invalid credentials', async () => {
mockFetch.mockResolvedValueOnce({
ok: false,
status: 401,
});
await expect(
authModule.login('test@example.com', 'wrongpassword')
).rejects.toThrow('Credenciais inválidas');
});
});
describe('logout', () => {
it('should remove auth data from localStorage', () => {
authModule.logout();
expect(localStorageMock.removeItem).toHaveBeenCalledWith('job-portal-auth');
expect(localStorageMock.removeItem).toHaveBeenCalledWith('auth_token');
});
});
describe('getCurrentUser', () => {
it('should return user from localStorage', () => {
const storedUser = { id: '123', name: 'Test', email: 'test@test.com', role: 'candidate' };
localStorageMock.getItem.mockReturnValueOnce(JSON.stringify(storedUser));
const user = authModule.getCurrentUser();
expect(user).toEqual(storedUser);
});
it('should return null when no user stored', () => {
localStorageMock.getItem.mockReturnValueOnce(null);
const user = authModule.getCurrentUser();
expect(user).toBeNull();
});
});
describe('isAdminUser', () => {
it('should return true for admin role', () => {
const adminUser = { id: '1', name: 'Admin', email: 'a@a.com', role: 'admin', roles: ['admin'] };
expect(authModule.isAdminUser(adminUser)).toBe(true);
});
it('should return true for SUPERADMIN in roles array', () => {
const superAdmin = { id: '1', name: 'Super', email: 's@s.com', role: 'candidate', roles: ['SUPERADMIN'] };
expect(authModule.isAdminUser(superAdmin)).toBe(true);
});
it('should return false for regular candidate', () => {
const candidate = { id: '1', name: 'User', email: 'u@u.com', role: 'candidate', roles: ['CANDIDATE'] };
expect(authModule.isAdminUser(candidate)).toBe(false);
});
it('should return false for null user', () => {
expect(authModule.isAdminUser(null)).toBe(false);
});
});
});

View file

@ -191,7 +191,7 @@ export const jobsApi = {
return apiRequest<PaginatedResponse<ApiJob>>(`/jobs${queryStr ? `?${queryStr}` : ""}`);
},
getById: (id: number) => {
getById: (id: string) => {
logCrudAction("read", "jobs", { id });
return apiRequest<ApiJob>(`/jobs/${id}`);
},

View file

@ -112,6 +112,8 @@ export interface RegisterCandidateData {
}
export async function registerCandidate(data: RegisterCandidateData): Promise<void> {
console.log('[registerCandidate] Sending request:', { ...data, password: '***' });
const res = await fetch(`${API_URL}/auth/register`, {
method: "POST",
headers: {
@ -122,13 +124,58 @@ export async function registerCandidate(data: RegisterCandidateData): Promise<vo
if (!res.ok) {
const errorData = await res.json().catch(() => ({}));
console.error('[registerCandidate] Error response:', res.status, errorData);
throw new Error(errorData.message || `Erro no registro: ${res.status}`);
}
const responseData = await res.json().catch(() => ({}));
console.log('[registerCandidate] Success response:', {
...responseData,
token: responseData.token ? '***' : undefined
});
}
export function getToken(): string | null {
if (typeof window !== "undefined") {
return localStorage.getItem("auth_token");
}
return null;
}
// Company Registration
export interface RegisterCompanyData {
companyName: string;
cnpj: string;
email: string;
phone: string;
}
export async function registerCompany(data: RegisterCompanyData): Promise<void> {
console.log('[registerCompany] Sending request:', data);
// Map frontend fields to backend DTO
const payload = {
name: data.companyName,
document: data.cnpj,
contact: data.phone,
admin_email: data.email,
};
const res = await fetch(`${API_URL}/companies`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
if (!res.ok) {
const errorData = await res.json().catch(() => ({}));
console.error('[registerCompany] Error response:', res.status, errorData);
throw new Error(errorData.message || `Erro no registro: ${res.status}`);
}
const responseData = await res.json().catch(() => ({}));
console.log('[registerCompany] Success - Company created:', responseData);
}