fix: correçao de conflitos

This commit is contained in:
NANDO9322 2026-01-17 18:41:21 -03:00
parent c97aeacf3b
commit c5ec964f78
8 changed files with 183 additions and 474 deletions

View file

@ -143,11 +143,11 @@ export default function ProfilePage() {
try {
toast({ title: "Enviando foto..." });
// 1. Get presigned URL
const { uploadUrl, publicUrl } = await storageApi.getUploadUrl(file.name, file.type);
// 2. Upload
await storageApi.uploadFile(uploadUrl, file);
// 3. Update state
toast({ title: "Enviando foto..." });
// 1. Upload via Proxy (avoids CORS)
const { publicUrl } = await storageApi.uploadFile(file, "avatars");
// 2. Update state
setProfilePic(publicUrl);
toast({ title: "Foto enviada!", description: "Não esqueça de salvar o perfil." });
} catch (err) {

View file

@ -3,7 +3,6 @@
import { useEffect, useState } from "react";
import Link from "next/link";
import { format } from "date-fns";
<<<<<<< HEAD
import { ptBR } from "date-fns/locale";
import {
Building2,
@ -11,7 +10,12 @@ import {
Search,
ExternalLink,
Loader2,
AlertCircle
AlertCircle,
Calendar,
CheckCircle2,
XCircle,
Clock,
FileText
} from "lucide-react";
import { Button } from "@/components/ui/button";
@ -27,15 +31,30 @@ import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { Navbar } from "@/components/navbar";
import { Footer } from "@/components/footer";
import { applicationsApi, ApiApplication } from "@/lib/api";
import { applicationsApi } from "@/lib/api";
type ApplicationWithJob = ApiApplication & {
type Application = {
id: string;
jobId: string;
jobTitle: string;
companyName: string;
companyId: string;
status: string;
createdAt: string;
resumeUrl?: string;
message?: string;
};
const statusConfig: Record<string, { label: string; color: string; icon: any }> = {
pending: { label: "Em Análise", color: "bg-yellow-100 text-yellow-800 hover:bg-yellow-100/80", icon: Clock },
reviewed: { label: "Visualizado", color: "bg-blue-100 text-blue-800 hover:bg-blue-100/80", icon: CheckCircle2 },
shortlisted: { label: "Selecionado", color: "bg-purple-100 text-purple-800 hover:bg-purple-100/80", icon: CheckCircle2 },
hired: { label: "Contratado", color: "bg-green-100 text-green-800 hover:bg-green-100/80", icon: CheckCircle2 },
rejected: { label: "Não Selecionado", color: "bg-red-100 text-red-800 hover:bg-red-100/80", icon: XCircle },
};
export default function MyApplicationsPage() {
const [applications, setApplications] = useState<ApplicationWithJob[]>([]);
const [applications, setApplications] = useState<Application[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
const [searchTerm, setSearchTerm] = useState("");
@ -48,9 +67,9 @@ export default function MyApplicationsPage() {
try {
setLoading(true);
const data = await applicationsApi.listMyApplications();
// The backend now returns ApplicationWithDetails which has jobTitle and companyName
// We cast it to our extended type
setApplications(data as unknown as ApplicationWithJob[]);
// Backend endpoint consistency check: listMyApplications usually returns ApplicationWithDetails
// Casting to ensure type safety if generic
setApplications(data as unknown as Application[]);
} catch (err) {
console.error("Failed to fetch applications", err);
setError("Não foi possível carregar suas candidaturas. Tente novamente.");
@ -59,27 +78,6 @@ export default function MyApplicationsPage() {
}
};
const getStatusColor = (status: string) => {
switch (status) {
case "pending": return "bg-yellow-100 text-yellow-800 hover:bg-yellow-100/80";
case "reviewed": return "bg-blue-100 text-blue-800 hover:bg-blue-100/80";
case "hired": return "bg-green-100 text-green-800 hover:bg-green-100/80";
case "rejected": return "bg-red-100 text-red-800 hover:bg-red-100/80";
default: return "bg-gray-100 text-gray-800 hover:bg-gray-100/80";
}
};
const getStatusLabel = (status: string) => {
const labels: Record<string, string> = {
pending: "Em Análise",
reviewed: "Visualizado",
shortlisted: "Selecionado",
hired: "Contratado",
rejected: "Não Selecionado"
};
return labels[status] || status;
};
const filteredApplications = applications.filter(app =>
app.jobTitle.toLowerCase().includes(searchTerm.toLowerCase()) ||
app.companyName.toLowerCase().includes(searchTerm.toLowerCase())
@ -137,226 +135,71 @@ export default function MyApplicationsPage() {
</div>
) : (
<div className="grid gap-4 md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-2">
{filteredApplications.map((app) => (
<Card key={app.id} className="overflow-hidden hover:shadow-md transition-shadow">
<CardHeader className="flex flex-row items-start justify-between space-y-0 pb-2">
<div className="space-y-1">
<CardTitle className="text-xl">
<Link href={`/vagas/${app.jobId}`} className="hover:text-primary transition-colors">
{app.jobTitle}
</Link>
</CardTitle>
<CardDescription className="flex items-center gap-2">
<Building2 className="h-3.5 w-3.5" />
{app.companyName}
</CardDescription>
</div>
<Badge className={getStatusColor(app.status)} variant="secondary">
{getStatusLabel(app.status)}
</Badge>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 gap-4 text-sm mt-4">
<div className="flex items-center text-muted-foreground">
<CalendarDays className="mr-2 h-4 w-4" />
Aplicado em {format(new Date(app.createdAt), "dd 'de' MMMM, yyyy", { locale: ptBR })}
{filteredApplications.map((app) => {
const status = statusConfig[app.status] || {
label: app.status,
color: "bg-gray-100 text-gray-800 hover:bg-gray-100/80",
icon: AlertCircle
};
const StatusIcon = status.icon;
return (
<Card key={app.id} className="overflow-hidden hover:shadow-md transition-shadow">
<CardHeader className="flex flex-row items-start justify-between space-y-0 pb-2">
<div className="space-y-1">
<CardTitle className="text-xl">
<Link href={`/vagas/${app.jobId}`} className="hover:text-primary transition-colors">
{app.jobTitle}
</Link>
</CardTitle>
<CardDescription className="flex items-center gap-2">
<Building2 className="h-3.5 w-3.5" />
{app.companyName}
</CardDescription>
</div>
</div>
</CardContent>
<CardFooter className="bg-muted/50 p-4 flex justify-between items-center">
<Link href={`/vagas/${app.jobId}`} className="text-sm font-medium text-primary hover:underline flex items-center">
Ver Vaga <ExternalLink className="ml-1 h-3 w-3" />
</Link>
{app.resumeUrl && (
<Link
href={app.resumeUrl}
target="_blank"
className="text-sm text-muted-foreground hover:text-foreground underline underline-offset-4"
>
Ver Currículo Enviado
<Badge className={status.color} variant="secondary">
<div className="flex items-center gap-1">
<StatusIcon className="h-3 w-3" />
{status.label}
</div>
</Badge>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 gap-4 text-sm mt-4">
<div className="flex items-center text-muted-foreground">
<CalendarDays className="mr-2 h-4 w-4" />
Aplicado em {format(new Date(app.createdAt), "dd 'de' MMMM, yyyy", { locale: ptBR })}
</div>
</div>
{app.message && (
<div className="mt-4 bg-muted/50 p-3 rounded-md text-sm italic">
"{app.message.length > 100 ? app.message.substring(0, 100) + "..." : app.message}"
</div>
)}
</CardContent>
<CardFooter className="bg-muted/50 p-4 flex justify-between items-center">
<Link href={`/vagas/${app.jobId}`} className="text-sm font-medium text-primary hover:underline flex items-center">
Ver Vaga <ExternalLink className="ml-1 h-3 w-3" />
</Link>
)}
</CardFooter>
</Card>
))}
{app.resumeUrl && (
<Link
href={app.resumeUrl}
target="_blank"
className="text-sm text-muted-foreground hover:text-foreground underline underline-offset-4 flex items-center"
>
<FileText className="h-3 w-3 mr-1" />
Ver Currículo
</Link>
)}
</CardFooter>
</Card>
);
})}
</div>
)}
</main>
<Footer />
=======
import {
Building2,
MapPin,
Calendar,
Clock,
CheckCircle2,
XCircle,
AlertCircle,
FileText,
ExternalLink
} from "lucide-react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { applicationsApi } from "@/lib/api";
import { useNotify } from "@/contexts/notification-context";
interface Application {
id: string;
jobId: string;
jobTitle: string;
companyName: string;
companyId: string;
status: string;
createdAt: string;
resumeUrl?: string;
message?: string;
}
const statusConfig: Record<string, { label: string; color: string; icon: any }> = {
pending: { label: "Pending", color: "bg-yellow-100 text-yellow-800 border-yellow-200", icon: Clock },
reviewed: { label: "Viewed", color: "bg-blue-100 text-blue-800 border-blue-200", icon: CheckCircle2 },
shortlisted: { label: "Shortlisted", color: "bg-purple-100 text-purple-800 border-purple-200", icon: CheckCircle2 },
hired: { label: "Hired", color: "bg-green-100 text-green-800 border-green-200", icon: CheckCircle2 },
rejected: { label: "Rejected", color: "bg-red-100 text-red-800 border-red-200", icon: XCircle },
};
export default function MyApplicationsPage() {
const [applications, setApplications] = useState<Application[]>([]);
const [loading, setLoading] = useState(true);
const notify = useNotify();
useEffect(() => {
async function fetchApplications() {
try {
const data = await applicationsApi.listMine();
setApplications(data || []);
} catch (error) {
console.error("Failed to fetch applications:", error);
notify.error("Error", "Failed to load your applications.");
} finally {
setLoading(false);
}
}
fetchApplications();
}, [notify]);
if (loading) {
return (
<div className="space-y-4">
<h1 className="text-3xl font-bold">My Applications</h1>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{[1, 2, 3].map((i) => (
<Card key={i}>
<CardHeader>
<Skeleton className="h-6 w-3/4" />
<Skeleton className="h-4 w-1/2" />
</CardHeader>
<CardContent>
<Skeleton className="h-20 w-full" />
</CardContent>
</Card>
))}
</div>
</div>
);
}
return (
<div className="space-y-6">
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div>
<h1 className="text-3xl font-bold">My Applications</h1>
<p className="text-muted-foreground mt-1">
Track the status of your job applications.
</p>
</div>
<Button asChild>
<Link href="/jobs">Find more jobs</Link>
</Button>
</div>
{applications.length === 0 ? (
<Card className="border-dashed">
<CardContent className="flex flex-col items-center justify-center py-12 text-center text-muted-foreground">
<h3 className="text-lg font-semibold mb-2">No applications yet</h3>
<p className="mb-6">You haven't applied to any jobs yet.</p>
<Button asChild variant="secondary">
<Link href="/jobs">Browse Jobs</Link>
</Button>
</CardContent>
</Card>
) : (
<div className="grid gap-4 md:grid-cols-1 lg:grid-cols-2">
{applications.map((app) => {
const status = statusConfig[app.status] || {
label: app.status,
color: "bg-gray-100 text-gray-800 border-gray-200",
icon: AlertCircle
};
const StatusIcon = status.icon;
return (
<Card key={app.id} className="hover:border-primary/50 transition-colors">
<CardHeader>
<div className="flex justify-between items-start gap-4">
<div className="space-y-1">
<CardTitle className="text-xl">
<Link href={`/jobs/${app.jobId}`} className="hover:underline hover:text-primary transition-colors">
{app.jobTitle}
</Link>
</CardTitle>
<div className="flex items-center text-muted-foreground text-sm gap-2">
<Building2 className="h-4 w-4" />
<span>{app.companyName}</span>
</div>
</div>
<Badge variant="outline" className={`${status.color} flex items-center gap-1 shrink-0`}>
<StatusIcon className="h-3 w-3" />
{status.label}
</Badge>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex flex-wrap gap-4 text-sm text-muted-foreground">
<div className="flex items-center gap-1">
<Calendar className="h-4 w-4" />
Applied on {format(new Date(app.createdAt), "MMM d, yyyy")}
</div>
</div>
{app.message && (
<div className="bg-muted/50 p-3 rounded-md text-sm italic">
"{app.message.length > 100 ? app.message.substring(0, 100) + "..." : app.message}"
</div>
)}
<div className="flex items-center gap-2 pt-2">
{app.resumeUrl && (
<Button variant="outline" size="sm" asChild>
<a href={app.resumeUrl} target="_blank" rel="noopener noreferrer">
<FileText className="h-4 w-4 mr-2" />
View Resume
</a>
</Button>
)}
<Button variant="ghost" size="sm" asChild className="ml-auto">
<Link href={`/jobs/${app.jobId}`}>
View Job <ExternalLink className="h-3 w-3 ml-1" />
</Link>
</Button>
</div>
</CardContent>
</Card>
);
})}
</div>
)}
>>>>>>> dev
</div>
);
}

View file

@ -4,11 +4,8 @@ import { useEffect, useState, useCallback } from "react"
import {
Card,
CardContent,
<<<<<<< HEAD
=======
CardHeader,
CardTitle,
>>>>>>> dev
} from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"

View file

@ -8,11 +8,11 @@ import { Mail, ArrowLeft, Loader2, CheckCircle } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from "@/components/ui/card";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Label } from "@/components/ui/label";
import { Navbar } from "@/components/navbar";
import { Footer } from "@/components/footer";
import { useTranslation } from "@/lib/i18n";
import { ArrowLeft, Mail } from "lucide-react";
const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8521";
@ -53,30 +53,6 @@ export default function ForgotPasswordPage() {
};
return (
<<<<<<< HEAD
<div className="min-h-screen flex flex-col bg-gradient-to-b from-muted/30 to-background">
<Navbar />
<div className="flex-1 flex items-center justify-center p-4">
<Card className="w-full max-w-md">
<CardHeader className="text-center">
<CardTitle className="text-2xl">{t("auth.forgot.title")}</CardTitle>
<CardDescription>
{t("auth.forgot.subtitle")}
</CardDescription>
</CardHeader>
<CardContent>
{submitted ? (
<div className="flex flex-col items-center gap-4 py-6">
<CheckCircle className="w-16 h-16 text-green-500" />
<p className="text-center text-muted-foreground">
{t("auth.forgot.success")}
</p>
<Link href="/login">
<Button variant="outline">
<ArrowLeft className="w-4 h-4 mr-2" /> {t("auth.forgot.backLogin")}
</Button>
</Link>
=======
<div className="min-h-screen flex items-center justify-center bg-background px-4 sm:px-6 py-8 sm:py-12">
<div className="w-full max-w-sm sm:max-w-md space-y-4 sm:space-y-6">
{/* Back to Login - Mobile friendly top placement */}
@ -108,80 +84,50 @@ export default function ForgotPasswordPage() {
</Alert>
)}
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email" className="text-sm sm:text-base">
{t("auth.forgot.fields.email")}
</Label>
<Input
id="email"
type="email"
value={email}
onChange={(event) => setEmail(event.target.value)}
placeholder={t("auth.forgot.fields.emailPlaceholder")}
required
className="h-10 sm:h-11"
/>
>>>>>>> dev
</div>
) : (
{!submitted && (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">{t("auth.forgot.fields.email")}</Label>
<div className="relative">
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<Input
id="email"
type="email"
placeholder={t("auth.forgot.fields.emailPlaceholder")}
className="pl-10"
{...register("email", { required: t("validations.required") || "Email is required" })}
/>
</div>
<Label htmlFor="email" className="text-sm sm:text-base">
{t("auth.forgot.fields.email")}
</Label>
<Input
id="email"
type="email"
placeholder={t("auth.forgot.fields.emailPlaceholder")}
className="h-10 sm:h-11"
{...register("email", { required: t("validations.required") || "Email is required" })}
/>
{errors.email && <p className="text-sm text-destructive">{errors.email.message}</p>}
</div>
<<<<<<< HEAD
{error && <p className="text-sm text-destructive text-center">{error}</p>}
<Button type="submit" className="w-full" disabled={loading}>
<Button type="submit" className="w-full h-10 sm:h-11" disabled={loading}>
{loading ? <Loader2 className="w-4 h-4 mr-2 animate-spin" /> : null}
{t("auth.forgot.submit")}
</Button>
</form>
)}
=======
<Button type="submit" className="w-full h-10 sm:h-11">
{t("auth.forgot.submit")}
</Button>
</form>
>>>>>>> dev
</CardContent>
{!submitted && (
<CardFooter className="justify-center">
<Link href="/login" className="text-sm text-muted-foreground hover:underline">
<ArrowLeft className="w-3 h-3 inline mr-1" /> {t("auth.forgot.backLogin")}
</Link>
{/* Back to Login - Desktop */}
<div className="hidden sm:block text-center">
<Link
href="/login"
className="text-sm text-muted-foreground hover:text-foreground transition-colors inline-flex items-center gap-2"
>
<ArrowLeft className="h-4 w-4" />
{t("auth.forgot.backLogin")}
</Link>
</div>
</CardFooter>
)}
</Card>
<<<<<<< HEAD
=======
{/* Back to Login - Desktop */}
<div className="hidden sm:block text-center">
<Link
href="/login"
className="text-sm text-muted-foreground hover:text-foreground transition-colors inline-flex items-center gap-2"
>
<ArrowLeft className="h-4 w-4" />
{t("auth.forgot.backLogin")}
</Link>
</div>
>>>>>>> dev
</div>
<Footer />
</div>
);
}

View file

@ -15,6 +15,7 @@ import {
MessageSquare,
Save,
ArrowLeft,
Loader2,
} from "lucide-react";
import { Button } from "@/components/ui/button";
@ -42,16 +43,11 @@ import { Navbar } from "@/components/navbar";
import { Footer } from "@/components/footer";
import { useNotify } from "@/contexts/notification-context";
import { jobsApi, applicationsApi, storageApi, type ApiJob } from "@/lib/api";
<<<<<<< HEAD
=======
import { formatPhone } from "@/lib/utils";
import { useTranslation } from "@/lib/i18n";
import { getCurrentUser } from "@/lib/auth";
>>>>>>> dev
export const runtime = 'edge';
@ -234,27 +230,9 @@ export default function JobApplicationPage({
const handleSubmit = async () => {
setIsSubmitting(true);
try {
let resumeUrl = "";
// 1. Upload Curriculo if present
if (formData.resume) {
try {
// Check if storageApi is available (it was in HEAD but maybe not hml type definitions?)
// Assuming it exists based on HEAD content.
if (storageApi && storageApi.getUploadUrl) {
const { uploadUrl, publicUrl } = await storageApi.getUploadUrl(
formData.resume.name,
formData.resume.type
);
await storageApi.uploadFile(uploadUrl, formData.resume);
resumeUrl = publicUrl;
}
} catch (err) {
console.error("Upload error:", err);
notify.error("Upload failed", "Could not upload resume, proceeding without it.");
// Proceed or return? proceed for now but warn.
}
}
// 1. Resume is already uploaded via handleResumeUpload, so we use formData.resumeUrl
const resumeUrl = formData.resumeUrl;
// Note: If you want to enforce upload here, you'd need the File object, but we uploaded it earlier.
await applicationsApi.create({
jobId: Number(id), // ID might need number conversion depending on API
@ -262,7 +240,6 @@ export default function JobApplicationPage({
email: formData.email,
phone: formData.phone,
linkedin: formData.linkedin,
<<<<<<< HEAD
coverLetter: formData.coverLetter || formData.whyUs, // Fallback
resumeUrl: resumeUrl,
portfolioUrl: formData.portfolioUrl,
@ -270,18 +247,6 @@ export default function JobApplicationPage({
hasExperience: formData.hasExperience,
whyUs: formData.whyUs,
availability: formData.availability,
=======
resumeUrl: formData.resumeUrl,
coverLetter: formData.coverLetter || undefined,
portfolioUrl: formData.portfolioUrl || undefined,
message: formData.whyUs, // Mapping Why Us to Message/Notes
documents: {}, // TODO: Extra docs
// salaryExpectation: formData.salaryExpectation, // These fields might need to go into Notes or structured JSON if backend doesn't support them specifically?
// hasExperience: formData.hasExperience,
// Backend seems to map "documents" as JSONMap. We can put extra info there?
// Or put in "message" concatenated.
// Let's assume the backend 'message' field is good for "whyUs"
>>>>>>> dev
});
notify.success(
@ -311,7 +276,21 @@ export default function JobApplicationPage({
const progress = (currentStep / steps.length) * 100;
if (!job && !loading) return null; // Or some error state
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin" />
</div>
);
}
if (!job) {
return (
<div className="min-h-screen flex items-center justify-center">
<p>Job not found</p>
</div>
);
}
if (isSubmitted) {
const user = getCurrentUser();
@ -404,11 +383,7 @@ export default function JobApplicationPage({
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div>
<h1 className="text-2xl md:text-3xl font-bold text-foreground">
<<<<<<< HEAD
Application: {job?.title}
=======
{t("application.title", { jobTitle: job.title })}
>>>>>>> dev
</h1>
<p className="text-muted-foreground mt-1">
{job?.companyName || 'Company'} {job?.location || 'Remote'}
@ -604,34 +579,12 @@ export default function JobApplicationPage({
disabled={isUploading}
/>
<div className="flex flex-col items-center gap-2">
<<<<<<< HEAD
{/* TODO: Implement real file input handler */}
<div className="relative">
<div className="p-3 bg-primary/10 rounded-full text-primary">
<Upload className="h-6 w-6" />
</div>
<Input
type="file"
className="absolute inset-0 opacity-0 cursor-pointer w-full h-full"
accept=".pdf,.doc,.docx"
onChange={(e) => {
if (e.target.files && e.target.files[0]) {
handleInputChange("resume", e.target.files[0])
}
}}
/>
</div>
<div className="space-y-1">
<p className="text-sm font-medium">
{formData.resume ? formData.resume.name : "Click to upload or drag the file here"}
=======
<div className="p-3 bg-primary/10 rounded-full text-primary">
{isUploading ? <Loader2 className="h-6 w-6 animate-spin" /> : <Upload className="h-6 w-6" />}
</div>
<div className="space-y-1">
<p className="text-sm font-medium">
{formData.resumeName || t("application.form.upload.click")}
>>>>>>> dev
</p>
<p className="text-xs text-muted-foreground">
{formData.resumeName ? t("application.form.upload.change") : t("application.form.upload.formats")}
@ -757,11 +710,7 @@ export default function JobApplicationPage({
<div className="space-y-6">
<div className="space-y-2">
<Label htmlFor="whyUs">
<<<<<<< HEAD
Why do you want to work at {job?.companyName || 'this company'}? *
=======
{t("application.form.whyUs", { company: job.companyName || 'this company' })}
>>>>>>> dev
{t("application.form.whyUs", { company: job?.companyName || 'this company' })}
</Label>
<Textarea
id="whyUs"

View file

@ -96,7 +96,7 @@ function JobsContent() {
})
// Transform the raw API response to frontend format
const mappedJobs = (response.data || []).map(transformApiJobToFrontend)
const mappedJobs = (response.data || []).map(job => transformApiJobToFrontend(job));
if (isMounted) {
setJobs(mappedJobs)

View file

@ -44,15 +44,15 @@ export function CandidateDashboardContent() {
try {
// Fetch recommended jobs (latest ones for now)
const jobsRes = await jobsApi.list({ limit: 3, sortBy: "created_at" });
const appsRes = await applicationsApi.listMine();
const appsRes = await applicationsApi.listMyApplications();
if (jobsRes && jobsRes.data) {
const mappedJobs = jobsRes.data.map(transformApiJobToFrontend);
const mappedJobs = jobsRes.data.map(job => transformApiJobToFrontend(job));
setJobs(mappedJobs);
}
if (appsRes) {
setApplications(appsRes);
setApplications(appsRes as unknown as ApplicationWithDetails[]);
}
} catch (error) {

View file

@ -16,21 +16,16 @@ function logCrudAction(action: string, entity: string, details?: any) {
* Generic API Request Wrapper
*/
async function apiRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
<<<<<<< HEAD
// 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 headers = {
// 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> = {
"Content-Type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),
...options.headers,
=======
// Ensure config is loaded before making request
await initConfig();
// Token is now in httpOnly cookie, sent automatically via credentials: include
const headers: Record<string, string> = {
...options.headers as Record<string, string>,
>>>>>>> dev
};
if (options.body) {
@ -439,18 +434,10 @@ export const applicationsApi = {
if (params.companyId) query.append("companyId", params.companyId);
return apiRequest<any[]>(`/api/v1/applications?${query.toString()}`);
},
<<<<<<< HEAD
listMyApplications: () => {
// Backend should support /applications/me or similar. Using /applications/me for now.
return apiRequest<ApiApplication[]>("/api/v1/applications/me");
},
=======
listMine: () => {
return apiRequest<any[]>("/api/v1/applications/me");
},
>>>>>>> dev
delete: (id: string) => {
return apiRequest<void>(`/api/v1/applications/${id}`, {
method: "DELETE"
@ -469,18 +456,41 @@ export const storageApi = {
}
),
uploadFile: async (uploadUrl: string, file: File) => {
const res = await fetch(uploadUrl, {
method: "PUT",
body: file,
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 formData = new FormData();
formData.append('file', file);
formData.append('folder', folder);
const response = await fetch(`${getApiUrl()}/api/v1/storage/upload`, {
method: 'POST',
body: formData,
headers: {
"Content-Type": file.type,
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
mode: "cors" // Important for S3
credentials: 'include',
});
if (!res.ok) throw new Error("Falha no upload para S3");
return true;
}
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Failed to upload file to storage: ${errorText}`);
}
const data = await response.json();
return {
key: data.key,
publicUrl: data.publicUrl || data.url
};
},
testConnection: () => apiRequest<{ message: string }>("/api/v1/admin/storage/test-connection", {
method: "POST"
}),
};
// --- Helper Functions ---
@ -670,20 +680,13 @@ export const profileApi = {
// Backoffice URL - now uses runtime config
async function backofficeRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
<<<<<<< HEAD
const token = typeof window !== 'undefined' ? localStorage.getItem("token") : null;
const headers = {
"Content-Type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),
...options.headers,
=======
// Ensure config is loaded before making request
await initConfig();
// 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;
// Token is now in httpOnly cookie, sent automatically via credentials: include
const headers: Record<string, string> = {
"Content-Type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),
...options.headers as Record<string, string>,
>>>>>>> dev
};
if (options.body) {
@ -818,9 +821,9 @@ export interface Conversation {
lastMessageAt: string;
participantName: string;
participantAvatar?: string;
unreadCount?: number;
}
<<<<<<< HEAD
=======
export const chatApi = {
listConversations: () => apiRequest<Conversation[]>("/api/v1/conversations"),
@ -863,37 +866,8 @@ export const credentialsApi = {
}),
};
export const storageApi = {
testConnection: () => apiRequest<{ message: string }>("/api/v1/admin/storage/test-connection", {
method: "POST"
}),
async uploadFile(file: File, folder = "uploads") {
await initConfig();
// Use backend proxy to avoid CORS/403
const formData = new FormData();
formData.append('file', file);
formData.append('folder', folder);
// Duplicate storageApi removed
// We use the proxy route
const response = await fetch(`${getApiUrl()}/api/v1/storage/upload`, {
method: 'POST',
body: formData,
// Credentials include is important if we need cookies (though for guest it might not matter, but good practice)
credentials: 'include',
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Failed to upload file to storage: ${errorText}`);
}
const data = await response.json();
return {
key: data.key,
publicUrl: data.publicUrl || data.url
};
},
};
// --- Email Templates & Settings ---
export interface EmailTemplate {
@ -995,4 +969,4 @@ export const locationsApi = {
return res || [];
},
};
>>>>>>> dev