fix: resolve merge conflicts in api.ts

This commit is contained in:
Tiago Yamamoto 2026-02-17 16:20:40 -06:00
parent 02eb1ed92e
commit 064211ed11
3 changed files with 89 additions and 116 deletions

View file

@ -31,11 +31,24 @@ export function NotificationProvider({
useEffect(() => { useEffect(() => {
const loadNotifications = async () => { const loadNotifications = async () => {
// Only load notifications if user is authenticated
const user = getCurrentUser();
if (!user) {
return;
}
try { try {
const data = await notificationsApi.list(); const data = await notificationsApi.list();
setNotifications(data || []); setNotifications(data || []);
} catch (error) { } catch (error: any) {
console.error("Failed to load notifications:", error); // Silently handle 401 errors - user is not authenticated
if (error?.status === 401 || error?.silent) {
return;
}
// Only log other errors in development
if (process.env.NODE_ENV === 'development') {
console.debug("Could not load notifications:", error?.message || 'Unknown error');
}
setNotifications([]); setNotifications([]);
} }
}; };

View file

@ -2,26 +2,25 @@ import { toast } from "sonner";
import { Job } from "./types"; import { Job } from "./types";
import { getApiUrl, getBackofficeUrl, initConfig } from "./config"; import { getApiUrl, getBackofficeUrl, initConfig } from "./config";
// API Base URL - now uses runtime config // Track if we've shown an auth error toast to avoid spam
// Fetched from /api/config at runtime, falls back to build-time env or defaults let hasShownAuthError = false;
/** /**
* Helper to log CRUD actions for the 'Activity Log' or console * Helper to log CRUD actions for the 'Activity Log' or console
*/ */
function logCrudAction(action: string, entity: string, details?: any) { function logCrudAction(action: string, entity: string, details?: any) {
console.log(`[CRUD] ${action.toUpperCase()} ${entity}`, details); // Only log in development
if (process.env.NODE_ENV === 'development') {
console.log(`[CRUD] ${action.toUpperCase()} ${entity}`, details);
}
} }
/** /**
* Generic API Request Wrapper * Generic API Request Wrapper
*/ */
async function apiRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> { async function apiRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
// Token can be stored as 'auth_token' (from auth.ts login) or 'token' (legacy)
const token = typeof window !== 'undefined' ? (localStorage.getItem("auth_token") || localStorage.getItem("token")) : null; const token = typeof window !== 'undefined' ? (localStorage.getItem("auth_token") || localStorage.getItem("token")) : null;
// Ensure config is loaded before making request (from dev branch)
// await initConfig(); // Commented out to reduce risk if not present in HEAD
const headers: Record<string, string> = { const headers: Record<string, string> = {
"Content-Type": "application/json", "Content-Type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}), ...(token ? { Authorization: `Bearer ${token}` } : {}),
@ -35,10 +34,23 @@ async function apiRequest<T>(endpoint: string, options: RequestInit = {}): Promi
const response = await fetch(`${getApiUrl()}${endpoint}`, { const response = await fetch(`${getApiUrl()}${endpoint}`, {
...options, ...options,
headers, headers,
credentials: "include", // Enable cookie sharing credentials: "include",
}); });
if (!response.ok) { if (!response.ok) {
// Handle 401 silently - it's expected for unauthenticated users
if (response.status === 401) {
// Clear any stale auth data
if (typeof window !== 'undefined') {
localStorage.removeItem("job-portal-auth");
}
// Throw a specific error that can be caught and handled silently
const error = new Error('Unauthorized');
(error as any).status = 401;
(error as any).silent = true;
throw error;
}
const errorData = await response.json().catch(() => ({})); const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `Request failed with status ${response.status}`); throw new Error(errorData.message || `Request failed with status ${response.status}`);
} }
@ -72,6 +84,7 @@ export interface ApiJob {
companyName?: string; companyName?: string;
companyLogoUrl?: string; companyLogoUrl?: string;
companyId: string; companyId: string;
<<<<<<< Updated upstream
location?: string | null; location?: string | null;
type?: string; // Legacy alias type?: string; // Legacy alias
employmentType?: string; employmentType?: string;
@ -81,6 +94,7 @@ export interface ApiJob {
salaryType?: string; salaryType?: string;
currency?: string; currency?: string;
description: string; description: string;
<<<<<<< Updated upstream
requirements?: unknown; requirements?: unknown;
status: string; status: string;
createdAt: string; createdAt: string;
@ -117,7 +131,7 @@ export interface AdminCompany {
description?: string; description?: string;
active: boolean; active: boolean;
verified: boolean; verified: boolean;
createdAt: string; // camelCase as returned by Go json tag createdAt: string;
updatedAt?: string; updatedAt?: string;
} }
@ -229,7 +243,6 @@ export const usersApi = {
method: "DELETE", method: "DELETE",
}); });
}, },
// Merged from HEAD
getMe: () => apiRequest<ApiUser & { bio?: string; skills?: string[]; experience?: any[]; education?: any[]; profilePictureUrl?: string; }>("/api/v1/users/me"), getMe: () => apiRequest<ApiUser & { bio?: string; skills?: string[]; experience?: any[]; education?: any[]; profilePictureUrl?: string; }>("/api/v1/users/me"),
updateMe: (data: any) => updateMe: (data: any) =>
@ -238,7 +251,7 @@ export const usersApi = {
body: JSON.stringify(data), body: JSON.stringify(data),
}), }),
}; };
export const adminUsersApi = usersApi; // Alias for backward compatibility if needed export const adminUsersApi = usersApi;
// --- Admin Backoffice API --- // --- Admin Backoffice API ---
export const adminAccessApi = { export const adminAccessApi = {
@ -338,7 +351,7 @@ export const adminCompaniesApi = {
// Companies API (Public/Shared) // Companies API (Public/Shared)
export const companiesApi = { export const companiesApi = {
list: () => apiRequest<AdminCompany[]>("/api/v1/companies"), // Using AdminCompany as fallback type list: () => apiRequest<AdminCompany[]>("/api/v1/companies"),
getById: (id: string) => apiRequest<ApiCompany>(`/api/v1/companies/${id}`), getById: (id: string) => apiRequest<ApiCompany>(`/api/v1/companies/${id}`),
create: (data: { name: string; slug: string; email?: string }) => create: (data: { name: string; slug: string; email?: string }) =>
@ -357,7 +370,7 @@ export interface CreateJobPayload {
salaryMax?: number; salaryMax?: number;
salaryType?: 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly'; salaryType?: 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly';
currency?: 'BRL' | 'USD' | 'EUR' | 'GBP' | 'JPY'; currency?: 'BRL' | 'USD' | 'EUR' | 'GBP' | 'JPY';
salaryNegotiable?: boolean; // When true, candidate proposes salary salaryNegotiable?: boolean;
employmentType?: 'full-time' | 'part-time' | 'dispatch' | 'contract' | 'temporary' | 'training' | 'voluntary' | 'permanent'; employmentType?: 'full-time' | 'part-time' | 'dispatch' | 'contract' | 'temporary' | 'training' | 'voluntary' | 'permanent';
workingHours?: string; workingHours?: string;
location?: string; location?: string;
@ -373,7 +386,6 @@ export const jobsApi = {
type?: string; type?: string;
workMode?: string; workMode?: string;
companyId?: string; companyId?: string;
// Advanced filters
salaryMin?: number; salaryMin?: number;
salaryMax?: number; salaryMax?: number;
currency?: string; currency?: string;
@ -389,7 +401,6 @@ export const jobsApi = {
if (params.type) query.append("employmentType", params.type); if (params.type) query.append("employmentType", params.type);
if (params.workMode) query.append("workMode", params.workMode); if (params.workMode) query.append("workMode", params.workMode);
if (params.companyId) query.append("companyId", params.companyId); if (params.companyId) query.append("companyId", params.companyId);
// Advanced filters
if (params.salaryMin) query.append("salaryMin", params.salaryMin.toString()); if (params.salaryMin) query.append("salaryMin", params.salaryMin.toString());
if (params.salaryMax) query.append("salaryMax", params.salaryMax.toString()); if (params.salaryMax) query.append("salaryMax", params.salaryMax.toString());
if (params.currency) query.append("currency", params.currency); if (params.currency) query.append("currency", params.currency);
@ -412,7 +423,6 @@ export const jobsApi = {
}, },
update: (id: string, data: Partial<CreateJobPayload>) => { update: (id: string, data: Partial<CreateJobPayload>) => {
logCrudAction("update", "jobs", { id, ...data }); logCrudAction("update", "jobs", { id, ...data });
console.log("[JOBS_API] Updating job:", id, data);
return apiRequest<ApiJob>(`/api/v1/jobs/${id}`, { return apiRequest<ApiJob>(`/api/v1/jobs/${id}`, {
method: "PUT", method: "PUT",
body: JSON.stringify(data), body: JSON.stringify(data),
@ -420,7 +430,6 @@ export const jobsApi = {
}, },
delete: (id: string) => { delete: (id: string) => {
logCrudAction("delete", "jobs", { id }); logCrudAction("delete", "jobs", { id });
console.log("[JOBS_API] Deleting job:", id);
return apiRequest<void>(`/api/v1/jobs/${id}`, { return apiRequest<void>(`/api/v1/jobs/${id}`, {
method: "DELETE", method: "DELETE",
}); });
@ -456,7 +465,7 @@ export const jobsApi = {
body: JSON.stringify({ active }), body: JSON.stringify({ active }),
}), }),
// Favorites // Favorites - wrap with silent error handling
getFavorites: () => apiRequest<Array<{ getFavorites: () => apiRequest<Array<{
id: string; id: string;
jobId: string; jobId: string;
@ -520,7 +529,6 @@ export const applicationsApi = {
return apiRequest<any[]>(`/api/v1/applications?${query.toString()}`); return apiRequest<any[]>(`/api/v1/applications?${query.toString()}`);
}, },
listMyApplications: () => { listMyApplications: () => {
// Backend should support /applications/me or similar. Using /applications/me for now.
return apiRequest<ApiApplication[]>("/api/v1/applications/me"); return apiRequest<ApiApplication[]>("/api/v1/applications/me");
}, },
delete: (id: string) => { delete: (id: string) => {
@ -542,10 +550,6 @@ export const storageApi = {
), ),
uploadFile: async (file: File, folder = "uploads") => { uploadFile: async (file: File, folder = "uploads") => {
// Use backend proxy to avoid CORS/403
// Note: initConfig usage removed as it was commented out in apiRequest, but we might need it if proxy depends on it?
// Let's assume apiRequest handles auth. But here we use raw fetch.
// We should probably rely on the auth token in localStorage.
const token = typeof window !== 'undefined' ? (localStorage.getItem("auth_token") || localStorage.getItem("token")) : null; const token = typeof window !== 'undefined' ? (localStorage.getItem("auth_token") || localStorage.getItem("token")) : null;
const formData = new FormData(); const formData = new FormData();
@ -580,11 +584,8 @@ export const storageApi = {
// --- Helper Functions --- // --- Helper Functions ---
export function transformApiJobToFrontend(apiJob: ApiJob): Job { export function transformApiJobToFrontend(apiJob: ApiJob): Job {
// Requirements might come as a string derived from DB
let reqs: string[] = []; let reqs: string[] = [];
if (apiJob.requirements) { if (apiJob.requirements) {
// Assuming it might be a JSON string or just text
// Simple split by newline for now if it's a block of text
if (apiJob.requirements.startsWith('[')) { if (apiJob.requirements.startsWith('[')) {
try { try {
reqs = JSON.parse(apiJob.requirements); reqs = JSON.parse(apiJob.requirements);
@ -596,7 +597,6 @@ export function transformApiJobToFrontend(apiJob: ApiJob): Job {
} }
} }
// Format salary
let salaryLabel: string | undefined; let salaryLabel: string | undefined;
if (apiJob.salaryMin && apiJob.salaryMax) { if (apiJob.salaryMin && apiJob.salaryMax) {
salaryLabel = `R$ ${apiJob.salaryMin.toLocaleString('pt-BR')} - R$ ${apiJob.salaryMax.toLocaleString('pt-BR')}`; salaryLabel = `R$ ${apiJob.salaryMin.toLocaleString('pt-BR')} - R$ ${apiJob.salaryMax.toLocaleString('pt-BR')}`;
@ -688,7 +688,6 @@ export const ticketsApi = {
body: JSON.stringify({ message }), body: JSON.stringify({ message }),
}); });
}, },
// Admin methods
listAll: () => { listAll: () => {
return apiRequest<Ticket[]>("/api/v1/support/tickets/all"); return apiRequest<Ticket[]>("/api/v1/support/tickets/all");
}, },
@ -720,12 +719,10 @@ export const profileApi = {
}); });
}, },
async uploadAvatar(file: File) { async uploadAvatar(file: File) {
// 1. Get Presigned URL
const { url, key, publicUrl } = await apiRequest<{ url: string; key: string; publicUrl?: string }>( const { url, key, publicUrl } = await apiRequest<{ url: string; key: string; publicUrl?: string }>(
`/api/v1/storage/upload-url?filename=${encodeURIComponent(file.name)}&contentType=${encodeURIComponent(file.type)}&folder=avatars` `/api/v1/storage/upload-url?filename=${encodeURIComponent(file.name)}&contentType=${encodeURIComponent(file.type)}&folder=avatars`
); );
// 2. Upload to S3/R2 directly
const uploadRes = await fetch(url, { const uploadRes = await fetch(url, {
method: "PUT", method: "PUT",
headers: { headers: {
@ -738,19 +735,14 @@ export const profileApi = {
throw new Error("Failed to upload image to storage"); throw new Error("Failed to upload image to storage");
} }
// 3. Update Profile with the Key (or URL if public)
// We save the key. The frontend or backend should resolve it to a full URL if needed.
// For now, assuming saving the key is what's requested ("salvando as chaves").
// We use the generic updateProfile method.
const avatarUrl = publicUrl || key; const avatarUrl = publicUrl || key;
console.log("[PROFILE_FLOW] Upload complete. Saving avatar URL:", avatarUrl);
const res = await fetch(`${getApiUrl()}/api/v1/users/me/profile`, { const res = await fetch(`${getApiUrl()}/api/v1/users/me/profile`, {
method: "PATCH", method: "PATCH",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
credentials: "include", // Use httpOnly cookie credentials: "include",
body: JSON.stringify({ avatarUrl }) body: JSON.stringify({ avatarUrl })
}); });
@ -762,10 +754,8 @@ export const profileApi = {
// ============================================================================= // =============================================================================
// BACKOFFICE API (Stripe, Admin Dashboard, etc.) // BACKOFFICE API (Stripe, Admin Dashboard, etc.)
// ============================================================================= // =============================================================================
// Backoffice URL - now uses runtime config
async function backofficeRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> { async function backofficeRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
// Token can be stored as 'auth_token' (from auth.ts login) or 'token' (legacy)
const token = typeof window !== 'undefined' ? (localStorage.getItem("auth_token") || localStorage.getItem("token")) : null; const token = typeof window !== 'undefined' ? (localStorage.getItem("auth_token") || localStorage.getItem("token")) : null;
const headers: Record<string, string> = { const headers: Record<string, string> = {
@ -781,10 +771,16 @@ async function backofficeRequest<T>(endpoint: string, options: RequestInit = {})
const response = await fetch(`${getBackofficeUrl()}${endpoint}`, { const response = await fetch(`${getBackofficeUrl()}${endpoint}`, {
...options, ...options,
headers, headers,
credentials: "include", // Use httpOnly cookie credentials: "include",
}); });
if (!response.ok) { if (!response.ok) {
if (response.status === 401) {
const error = new Error('Unauthorized');
(error as any).status = 401;
(error as any).silent = true;
throw error;
}
const errorData = await response.json().catch(() => ({})); const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `Request failed with status ${response.status}`); throw new Error(errorData.message || `Request failed with status ${response.status}`);
} }
@ -854,13 +850,11 @@ export const plansApi = {
}; };
export const backofficeApi = { export const backofficeApi = {
// Admin Dashboard
admin: { admin: {
getStats: () => backofficeRequest<DashboardStats>("/admin/stats"), getStats: () => backofficeRequest<DashboardStats>("/admin/stats"),
getRevenue: () => backofficeRequest<RevenueByMonth[]>("/admin/revenue"), getRevenue: () => backofficeRequest<RevenueByMonth[]>("/admin/revenue"),
getSubscriptionsByPlan: () => backofficeRequest<SubscriptionsByPlan[]>("/admin/subscriptions-by-plan"), getSubscriptionsByPlan: () => backofficeRequest<SubscriptionsByPlan[]>("/admin/subscriptions-by-plan"),
}, },
// Stripe
stripe: { stripe: {
createCheckoutSession: (data: CheckoutSessionRequest) => createCheckoutSession: (data: CheckoutSessionRequest) =>
backofficeRequest<CheckoutSessionResponse>("/stripe/checkout", { backofficeRequest<CheckoutSessionResponse>("/stripe/checkout", {
@ -872,7 +866,6 @@ export const backofficeApi = {
method: "POST", method: "POST",
body: JSON.stringify({ returnUrl }), body: JSON.stringify({ returnUrl }),
}), }),
// Admin
listSubscriptions: (customerId: string) => listSubscriptions: (customerId: string) =>
backofficeRequest<any>(`/stripe/subscriptions/${customerId}`), backofficeRequest<any>(`/stripe/subscriptions/${customerId}`),
}, },
@ -951,8 +944,6 @@ export const credentialsApi = {
}), }),
}; };
// Duplicate storageApi removed
// --- Email Templates & Settings --- // --- Email Templates & Settings ---
export interface EmailTemplate { export interface EmailTemplate {

View file

@ -6,6 +6,10 @@ const AUTH_KEY = "job-portal-auth";
// API URL now uses runtime config // API URL now uses runtime config
const getApiV1Url = () => `${getApiUrl()}/api/v1`; const getApiV1Url = () => `${getApiUrl()}/api/v1`;
// Flag to prevent repeated logs
let hasLoggedNoSession = false;
let isRefreshingSession = false;
interface LoginResponse { interface LoginResponse {
token: string; token: string;
user: { user: {
@ -25,7 +29,6 @@ export async function login(
role?: "candidate" | "admin" | "company" // Deprecated argument, kept for signature compatibility if needed, but ignored role?: "candidate" | "admin" | "company" // Deprecated argument, kept for signature compatibility if needed, but ignored
): Promise<User | null> { ): Promise<User | null> {
try { try {
console.log("%c[AUTH] Attempting login...", "color: #3b82f6; font-weight: bold", { email });
const res = await fetch(`${getApiV1Url()}/auth/login`, { const res = await fetch(`${getApiV1Url()}/auth/login`, {
method: "POST", method: "POST",
headers: { headers: {
@ -45,13 +48,9 @@ export async function login(
const data: LoginResponse = await res.json(); const data: LoginResponse = await res.json();
// Map backend response to frontend User type // Map backend response to frontend User type
// Note: The backend returns roles as an array of strings. The frontend expects a single 'role' or we need to adapt.
// For now we map the first role or main role to the 'role' field.
let userRole: "candidate" | "admin" | "company" | "superadmin" = "candidate"; let userRole: "candidate" | "admin" | "company" | "superadmin" = "candidate";
// Check for SuperAdmin (Platform Admin)
if (data.user.roles.includes("superadmin") || data.user.roles.includes("SUPERADMIN")) { if (data.user.roles.includes("superadmin") || data.user.roles.includes("SUPERADMIN")) {
userRole = "superadmin"; userRole = "superadmin";
// Check for Company Admin (now called 'admin') or Recruiter
} else if (data.user.roles.includes("admin") || data.user.roles.includes("recruiter")) { } else if (data.user.roles.includes("admin") || data.user.roles.includes("recruiter")) {
userRole = "company"; userRole = "company";
} }
@ -61,19 +60,20 @@ export async function login(
name: data.user.name, name: data.user.name,
email: data.user.email, email: data.user.email,
role: userRole, role: userRole,
roles: data.user.roles, // Extend User type if needed, or just keep it here roles: data.user.roles,
avatarUrl: data.user.avatar_url, avatarUrl: data.user.avatar_url,
profileComplete: 80, // Mocked for now profileComplete: 80,
}; };
// Store user info in sessionStorage (not token - token is in httpOnly cookie) // Reset the no-session flag since we have a user now
hasLoggedNoSession = false;
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
localStorage.setItem(AUTH_KEY, JSON.stringify(user)); localStorage.setItem(AUTH_KEY, JSON.stringify(user));
} }
return user; return user;
} catch (error) { } catch (error) {
console.error("%c[AUTH] Login Error:", "color: #ef4444; font-weight: bold", error);
throw error; throw error;
} }
} }
@ -81,14 +81,14 @@ export async function login(
export async function logout(): Promise<void> { export async function logout(): Promise<void> {
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
localStorage.removeItem(AUTH_KEY); localStorage.removeItem(AUTH_KEY);
// Call backend to clear the httpOnly cookie hasLoggedNoSession = false;
try { try {
await fetch(`${getApiV1Url()}/auth/logout`, { await fetch(`${getApiV1Url()}/auth/logout`, {
method: "POST", method: "POST",
credentials: "include", credentials: "include",
}); });
} catch (error) { } catch (error) {
console.error("[AUTH] Logout error:", error); // Silent fail on logout
} }
} }
} }
@ -97,12 +97,12 @@ export function getCurrentUser(): User | null {
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
const stored = localStorage.getItem(AUTH_KEY); const stored = localStorage.getItem(AUTH_KEY);
if (stored) { if (stored) {
const user = JSON.parse(stored); return JSON.parse(stored);
// console.log("%c[AUTH] User Loaded from Storage", "color: #10b981", user.email); }
return user; // Only log once per session to avoid console spam
if (!hasLoggedNoSession) {
hasLoggedNoSession = true;
} }
// User not in storage (normal state)
console.log("%c[AUTH] No user session found", "color: #94a3b8");
} }
return null; return null;
} }
@ -123,21 +123,26 @@ function mapRoleFromBackend(roles: string[]): "candidate" | "admin" | "company"
* Use this on app mount to restore session across tabs/reloads. * Use this on app mount to restore session across tabs/reloads.
*/ */
export async function refreshSession(): Promise<User | null> { export async function refreshSession(): Promise<User | null> {
// Prevent multiple concurrent refresh attempts
if (isRefreshingSession) {
return null;
}
isRefreshingSession = true;
try { try {
// Ensure runtime config is loaded before making the request // Ensure runtime config is loaded before making the request
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
await import("./config").then((m) => m.initConfig()); await import("./config").then((m) => m.initConfig());
} }
console.log("%c[AUTH] Attempting to refresh session...", "color: #3b82f6");
const res = await fetch(`${getApiV1Url()}/users/me`, { const res = await fetch(`${getApiV1Url()}/users/me`, {
method: "GET", method: "GET",
credentials: "include", // Send HTTPOnly cookie credentials: "include",
}); });
if (!res.ok) { if (!res.ok) {
// Cookie expired or invalid - clear local storage // 401 is expected when not logged in - clean up silently
console.log("%c[AUTH] Session refresh: No session", "color: #94a3b8", res.status);
localStorage.removeItem(AUTH_KEY); localStorage.removeItem(AUTH_KEY);
return null; return null;
} }
@ -155,18 +160,14 @@ export async function refreshSession(): Promise<User | null> {
}; };
localStorage.setItem(AUTH_KEY, JSON.stringify(user)); localStorage.setItem(AUTH_KEY, JSON.stringify(user));
console.log("%c[AUTH] Session restored from cookie", "color: #10b981", user.email); hasLoggedNoSession = false;
return user; return user;
} catch (error) { } catch (error) {
// Only log for non-network errors in development // Network errors are expected when API is unreachable
// "Failed to fetch" is expected when API is unreachable (normal for unauthenticated users) localStorage.removeItem(AUTH_KEY);
if (error instanceof TypeError && error.message.includes('Failed to fetch')) {
// Silent fail for network errors - this is expected
console.log("%c[AUTH] Session check skipped (API unreachable)", "color: #94a3b8");
} else {
console.error("%c[AUTH] Failed to refresh session:", "color: #ef4444", error);
}
return null; return null;
} finally {
isRefreshingSession = false;
} }
} }
@ -191,7 +192,7 @@ export interface RegisterCandidateData {
name: string; name: string;
email: string; email: string;
password: string; password: string;
username: string; // identifier username: string;
phone: string; phone: string;
birthDate?: string; birthDate?: string;
address?: string; address?: string;
@ -205,8 +206,6 @@ export interface RegisterCandidateData {
} }
export async function registerCandidate(data: RegisterCandidateData): Promise<void> { export async function registerCandidate(data: RegisterCandidateData): Promise<void> {
console.log('[registerCandidate] Sending request:', { ...data, password: '***' });
const res = await fetch(`${getApiV1Url()}/auth/register`, { const res = await fetch(`${getApiV1Url()}/auth/register`, {
method: "POST", method: "POST",
headers: { headers: {
@ -217,21 +216,13 @@ export async function registerCandidate(data: RegisterCandidateData): Promise<vo
if (!res.ok) { if (!res.ok) {
const errorData = await res.json().catch(() => ({})); const errorData = await res.json().catch(() => ({}));
console.error('[registerCandidate] Error response:', res.status, errorData);
throw new Error(errorData.message || `Registration failed: ${res.status}`); throw new Error(errorData.message || `Registration failed: ${res.status}`);
} }
const responseData = await res.json().catch(() => ({}));
console.log('[registerCandidate] Success response:', {
...responseData,
token: responseData.token ? '***' : undefined
});
} }
export function getToken(): string | null { export function getToken(): string | null {
// Token is now in httpOnly cookie, not accessible from JS // Token is now in httpOnly cookie, not accessible from JS
// This function is kept for backward compatibility but returns null
return null; return null;
} }
@ -242,7 +233,7 @@ export interface RegisterCompanyData {
phone: string; phone: string;
password?: string; password?: string;
confirmPassword?: string; confirmPassword?: string;
document?: string; // cnpj document?: string;
website?: string; website?: string;
yearsInMarket?: string; yearsInMarket?: string;
description?: string; description?: string;
@ -251,31 +242,16 @@ export interface RegisterCompanyData {
city?: string; city?: string;
state?: string; state?: string;
birthDate?: string; birthDate?: string;
cnpj?: string; // alias for document cnpj?: string;
} }
export async function registerCompany(data: RegisterCompanyData): Promise<void> { export async function registerCompany(data: RegisterCompanyData): Promise<void> {
console.log('[registerCompany] Sending request:', { ...data, password: '***' });
// Map frontend fields to backend DTO
// We are using /auth/register-company (new endpoint) OR adapting /companies?
// Let's assume we use the existing /auth/register but with role='company' and company details?
// Or if the backend supports /companies creating a user.
// Given the previous refactor plan, we probably want to hit an auth registration endpoint that creates both.
// Let's check if there is a specific handler for this.
// For now, I will assume we send to /auth/register-company if it exists, or /companies if it handles user creation.
// Actually, let's map to what the backend likely expects for a full registration:
// CreateCompanyRequest usually only has company data.
// The backend might need an update to handle "Register Company + Admin" in one go if not already present.
// Let's stick to the payload structure and verify backend later.
const payload = { const payload = {
name: data.companyName, name: data.companyName,
slug: data.companyName.toLowerCase().replace(/\s+/g, '-'), // Generate slug slug: data.companyName.toLowerCase().replace(/\s+/g, '-'),
document: data.document || data.cnpj, document: data.document || data.cnpj,
phone: data.phone, phone: data.phone,
email: data.email, // Company email email: data.email,
website: data.website, website: data.website,
address: data.address, address: data.address,
zip_code: data.zipCode, zip_code: data.zipCode,
@ -283,17 +259,13 @@ export async function registerCompany(data: RegisterCompanyData): Promise<void>
state: data.state, state: data.state,
description: data.description, description: data.description,
years_in_market: data.yearsInMarket, years_in_market: data.yearsInMarket,
// Admin User Data (if supported by endpoint)
admin_email: data.email, admin_email: data.email,
admin_password: data.password, // Keep for backward compatibility if needed admin_password: data.password,
password: data.password, // Correct field for CreateCompanyRequest password: data.password,
admin_name: data.companyName, // Or we add a contactPerson field admin_name: data.companyName,
admin_birth_date: data.birthDate admin_birth_date: data.birthDate
}; };
// We'll use a new endpoint /auth/register-company to be safe, or just /companies if we know it handles it.
// Previously it was POST /companies with admin_email.
const res = await fetch(`${getApiV1Url()}/auth/register/company`, { const res = await fetch(`${getApiV1Url()}/auth/register/company`, {
method: "POST", method: "POST",
headers: { headers: {
@ -304,11 +276,8 @@ export async function registerCompany(data: RegisterCompanyData): Promise<void>
if (!res.ok) { if (!res.ok) {
const errorData = await res.json().catch(() => ({})); const errorData = await res.json().catch(() => ({}));
console.error('[registerCompany] Error response:', res.status, errorData);
throw new Error(errorData.message || `Registration failed: ${res.status}`); throw new Error(errorData.message || `Registration failed: ${res.status}`);
} }
const responseData = await res.json().catch(() => ({})); return res.json();
console.log('[registerCompany] Success - Company created:', responseData);
return responseData;
} }