chore: replace native window.confirm with Custom ConfirmModal

This commit is contained in:
Tiago Yamamoto 2026-02-23 11:24:55 -06:00
parent c5f6b1317e
commit fd2fa328ad
5 changed files with 101 additions and 22 deletions

View file

@ -4,6 +4,7 @@ import { useState, useEffect } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { toast } from "sonner"; import { toast } from "sonner";
import { emailTemplatesApi, EmailTemplate } from "@/lib/api"; import { emailTemplatesApi, EmailTemplate } from "@/lib/api";
import { ConfirmModal } from "@/components/confirm-modal";
export default function EmailTemplatesPage() { export default function EmailTemplatesPage() {
const router = useRouter(); const router = useRouter();
@ -11,6 +12,7 @@ export default function EmailTemplatesPage() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [showCreate, setShowCreate] = useState(false); const [showCreate, setShowCreate] = useState(false);
const [newTemplate, setNewTemplate] = useState({ slug: "", subject: "", body_html: "", variables: "" }); const [newTemplate, setNewTemplate] = useState({ slug: "", subject: "", body_html: "", variables: "" });
const [deleteTemplateSlug, setDeleteTemplateSlug] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
fetchTemplates(); fetchTemplates();
@ -50,13 +52,19 @@ export default function EmailTemplatesPage() {
}; };
const handleDelete = async (slug: string) => { const handleDelete = async (slug: string) => {
if (!confirm("Delete this template?")) return; setDeleteTemplateSlug(slug);
};
const confirmDelete = async () => {
if (!deleteTemplateSlug) return;
try { try {
await emailTemplatesApi.delete(slug); await emailTemplatesApi.delete(deleteTemplateSlug);
toast.success("Template deleted"); toast.success("Template deleted");
fetchTemplates(); fetchTemplates();
} catch (err: any) { } catch (err: any) {
toast.error(err.message || "Failed to delete template"); toast.error(err.message || "Failed to delete template");
} finally {
setDeleteTemplateSlug(null);
} }
}; };
@ -178,6 +186,13 @@ export default function EmailTemplatesPage() {
</tbody> </tbody>
</table> </table>
</div> </div>
<ConfirmModal
isOpen={!!deleteTemplateSlug}
onClose={() => setDeleteTemplateSlug(null)}
onConfirm={confirmDelete}
title="Are you sure you want to delete this template?"
description="This action cannot be undone."
/>
</div> </div>
); );
} }

View file

@ -37,13 +37,14 @@ import {
} from "lucide-react" } from "lucide-react"
import { applicationsApi, notificationsApi } from "@/lib/api" import { applicationsApi, notificationsApi } from "@/lib/api"
import { toast } from "sonner" import { toast } from "sonner"
import { ConfirmModal } from "@/components/confirm-modal"
const statusConfig = { const statusConfig = {
pending: { label: "Pendente", color: "bg-yellow-100 text-yellow-800 border-yellow-200", icon: Clock }, pending: { label: "Pendente", color: "bg-yellow-100 text-yellow-800 border-yellow-200", icon: Clock },
reviewed: { label: "Em análise", color: "bg-blue-100 text-blue-800 border-blue-200", icon: Eye }, reviewed: { label: "Em análise", color: "bg-blue-100 text-blue-800 border-blue-200", icon: Eye },
shortlisted: { label: "Selecionado", color: "bg-purple-100 text-purple-800 border-purple-200", icon: Star }, shortlisted: { label: "Selecionado", color: "bg-purple-100 text-purple-800 border-purple-200", icon: Star },
hired: { label: "Contratado", color: "bg-green-100 text-green-800 border-green-200", icon: Check }, hired: { label: "Contratado", color: "bg-green-100 text-green-800 border-green-200", icon: Check },
rejected: { label: "Rejeitado", color: "bg-red-100 text-red-800 border-red-200", icon: X }, rejected: { label: "Rejeitado", color: "bg-red-100 text-red-800 border-red-200", icon: X },
} }
interface Application { interface Application {
@ -64,6 +65,7 @@ export default function ApplicationsPage() {
const [statusFilter, setStatusFilter] = useState("all") const [statusFilter, setStatusFilter] = useState("all")
const [searchTerm, setSearchTerm] = useState("") const [searchTerm, setSearchTerm] = useState("")
const [selectedApp, setSelectedApp] = useState<Application | null>(null) const [selectedApp, setSelectedApp] = useState<Application | null>(null)
const [deleteConfirmId, setDeleteConfirmId] = useState<string | null>(null)
useEffect(() => { useEffect(() => {
async function fetchApplications() { async function fetchApplications() {
@ -81,16 +83,22 @@ export default function ApplicationsPage() {
const handleDelete = async (id: string, e?: React.MouseEvent) => { const handleDelete = async (id: string, e?: React.MouseEvent) => {
e?.stopPropagation() e?.stopPropagation()
if (!confirm("Are you sure you want to delete this application?")) return setDeleteConfirmId(id)
}
const confirmDelete = async () => {
if (!deleteConfirmId) return
try { try {
await applicationsApi.delete(id) await applicationsApi.delete(deleteConfirmId)
setApplications(applications.filter(a => a.id !== id)) setApplications(applications.filter(a => a.id !== deleteConfirmId))
toast.success("Application deleted") toast.success("Application deleted")
if (selectedApp?.id === id) setSelectedApp(null) if (selectedApp?.id === deleteConfirmId) setSelectedApp(null)
} catch (error) { } catch (error) {
console.error("Delete error:", error) console.error("Delete error:", error)
toast.error("Failed to delete application") toast.error("Failed to delete application")
} finally {
setDeleteConfirmId(null)
} }
} }
@ -168,7 +176,7 @@ export default function ApplicationsPage() {
<Filter className="h-4 w-4 mr-2" /> <Filter className="h-4 w-4 mr-2" />
<SelectValue placeholder="Filter by status" /> <SelectValue placeholder="Filter by status" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="all">All Status</SelectItem> <SelectItem value="all">All Status</SelectItem>
<SelectItem value="pending">Pendente</SelectItem> <SelectItem value="pending">Pendente</SelectItem>
<SelectItem value="reviewed">Em análise</SelectItem> <SelectItem value="reviewed">Em análise</SelectItem>
@ -346,6 +354,13 @@ export default function ApplicationsPage() {
</div> </div>
) )
} }
<ConfirmModal
isOpen={!!deleteConfirmId}
onClose={() => setDeleteConfirmId(null)}
onConfirm={confirmDelete}
title="Are you sure you want to delete this application?"
description="This action cannot be undone."
/>
</div > </div >
) )
} }

View file

@ -43,6 +43,7 @@ import {
import { getCurrentUser, isAdminUser } from "@/lib/auth" import { getCurrentUser, isAdminUser } from "@/lib/auth"
import { toast } from "sonner" import { toast } from "sonner"
import { Archive, CheckCircle, Copy, ExternalLink, PauseCircle, Plus, RefreshCw, XCircle } from "lucide-react" import { Archive, CheckCircle, Copy, ExternalLink, PauseCircle, Plus, RefreshCw, XCircle } from "lucide-react"
import { ConfirmModal } from "@/components/confirm-modal"
const auditDateFormatter = new Intl.DateTimeFormat("pt-BR", { const auditDateFormatter = new Intl.DateTimeFormat("pt-BR", {
dateStyle: "short", dateStyle: "short",
@ -94,6 +95,7 @@ export default function BackofficePage() {
const [isPlanDialogOpen, setIsPlanDialogOpen] = useState(false) const [isPlanDialogOpen, setIsPlanDialogOpen] = useState(false)
const [planForm, setPlanForm] = useState<any>({ name: "", description: "", monthlyPrice: 0, yearlyPrice: 0, features: [] }) const [planForm, setPlanForm] = useState<any>({ name: "", description: "", monthlyPrice: 0, yearlyPrice: 0, features: [] })
const [editingPlanId, setEditingPlanId] = useState<string | null>(null) const [editingPlanId, setEditingPlanId] = useState<string | null>(null)
const [deletePlanId, setDeletePlanId] = useState<string | null>(null)
const loadBackoffice = async (silent = false) => { const loadBackoffice = async (silent = false) => {
try { try {
@ -223,13 +225,19 @@ export default function BackofficePage() {
} }
const handleDeletePlan = async (id: string) => { const handleDeletePlan = async (id: string) => {
if (!confirm("Delete this plan?")) return setDeletePlanId(id)
}
const confirmDeletePlan = async () => {
if (!deletePlanId) return
try { try {
await plansApi.delete(id) await plansApi.delete(deletePlanId)
toast.success("Plan deleted") toast.success("Plan deleted")
loadBackoffice(true) loadBackoffice(true)
} catch (error) { } catch (error) {
toast.error("Failed to delete plan") toast.error("Failed to delete plan")
} finally {
setDeletePlanId(null)
} }
} }
@ -534,6 +542,13 @@ export default function BackofficePage() {
</Card> </Card>
</TabsContent> </TabsContent>
</Tabs> </Tabs>
<ConfirmModal
isOpen={!!deletePlanId}
onClose={() => setDeletePlanId(null)}
onConfirm={confirmDeletePlan}
title="Are you sure you want to delete this plan?"
description="This action cannot be undone."
/>
</div> </div>
) )
} }

View file

@ -23,6 +23,7 @@ import { Switch } from "@/components/ui/switch"
import { adminCompaniesApi, type AdminCompany } from "@/lib/api" import { adminCompaniesApi, type AdminCompany } from "@/lib/api"
import { getCurrentUser, isAdminUser } from "@/lib/auth" import { getCurrentUser, isAdminUser } from "@/lib/auth"
import { toast } from "sonner" import { toast } from "sonner"
import { ConfirmModal } from "@/components/confirm-modal"
import { Skeleton } from "@/components/ui/skeleton" import { Skeleton } from "@/components/ui/skeleton"
import { useTranslation } from "@/lib/i18n" import { useTranslation } from "@/lib/i18n"
@ -89,6 +90,7 @@ export default function AdminCompaniesPage() {
const [isDialogOpen, setIsDialogOpen] = useState(false) const [isDialogOpen, setIsDialogOpen] = useState(false)
const [isViewDialogOpen, setIsViewDialogOpen] = useState(false) const [isViewDialogOpen, setIsViewDialogOpen] = useState(false)
const [selectedCompany, setSelectedCompany] = useState<AdminCompany | null>(null) const [selectedCompany, setSelectedCompany] = useState<AdminCompany | null>(null)
const [companyToDelete, setCompanyToDelete] = useState<AdminCompany | null>(null)
const [creating, setCreating] = useState(false) const [creating, setCreating] = useState(false)
const [updating, setUpdating] = useState(false) const [updating, setUpdating] = useState(false)
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false) const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
@ -223,16 +225,24 @@ export default function AdminCompaniesPage() {
} }
const handleDelete = async (company: AdminCompany) => { const handleDelete = async (company: AdminCompany) => {
if (!window.confirm(t('admin.companies.deleteConfirm', { name: company.name }))) return setCompanyToDelete(company)
}
const confirmDelete = async () => {
if (!companyToDelete) return
try { try {
await adminCompaniesApi.delete(company.id) await adminCompaniesApi.delete(companyToDelete.id)
toast.success(t('admin.companies.success.deleted')) toast.success(t('admin.companies.success.deleted'))
setIsViewDialogOpen(false) if (selectedCompany?.id === companyToDelete.id) {
setIsViewDialogOpen(false)
}
loadCompanies() loadCompanies()
} catch (error) { } catch (error) {
console.error("Error deleting company:", error) console.error("Error deleting company:", error)
toast.error("Failed to delete company") toast.error("Failed to delete company")
} finally {
setCompanyToDelete(null)
} }
} }
@ -888,6 +898,14 @@ export default function AdminCompaniesPage() {
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div>
<ConfirmModal
isOpen={!!companyToDelete}
onClose={() => setCompanyToDelete(null)}
onConfirm={confirmDelete}
title={companyToDelete ? t('admin.companies.deleteConfirm', { name: companyToDelete.name }) : ""}
description="This action cannot be undone."
/>
</div >
) )
} }

View file

@ -22,6 +22,7 @@ import { Switch } from "@/components/ui/switch"
import { Plus, Search, Edit, Trash2, Eye, ChevronLeft, ChevronRight, Loader2 } from "lucide-react" import { Plus, Search, Edit, Trash2, Eye, ChevronLeft, ChevronRight, Loader2 } from "lucide-react"
import { adminJobsApi, jobsApi, type AdminJob } from "@/lib/api" import { adminJobsApi, jobsApi, type AdminJob } from "@/lib/api"
import { useTranslation } from "@/lib/i18n" import { useTranslation } from "@/lib/i18n"
import { ConfirmModal } from "@/components/confirm-modal"
type EditForm = { type EditForm = {
title: string title: string
@ -70,6 +71,7 @@ export default function AdminJobsPage() {
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
const [isSaving, setIsSaving] = useState(false) const [isSaving, setIsSaving] = useState(false)
const [errorMessage, setErrorMessage] = useState<string | null>(null) const [errorMessage, setErrorMessage] = useState<string | null>(null)
const [deleteConfirmDialog, setDeleteConfirmDialog] = useState<{ isOpen: boolean, jobId: string | null }>({ isOpen: false, jobId: null })
const [page, setPage] = useState(1) const [page, setPage] = useState(1)
const [limit] = useState(10) const [limit] = useState(10)
@ -144,12 +146,18 @@ export default function AdminJobsPage() {
} }
const handleDeleteJob = async (id: string) => { const handleDeleteJob = async (id: string) => {
if (!confirm(t('admin.jobs.deleteConfirm'))) return setDeleteConfirmDialog({ isOpen: true, jobId: id })
}
const confirmDeleteJob = async () => {
if (!deleteConfirmDialog.jobId) return
try { try {
await jobsApi.delete(id) await jobsApi.delete(deleteConfirmDialog.jobId)
setJobs((prev) => prev.filter((job) => job.id !== id)) setJobs((prev) => prev.filter((job) => job.id !== deleteConfirmDialog.jobId))
} catch { } catch {
alert(t('admin.jobs.deleteError')) alert(t('admin.jobs.deleteError'))
} finally {
setDeleteConfirmDialog({ isOpen: false, jobId: null })
} }
} }
@ -208,6 +216,14 @@ export default function AdminJobsPage() {
</Link> </Link>
</div> </div>
<ConfirmModal
isOpen={deleteConfirmDialog.isOpen}
onClose={() => setDeleteConfirmDialog({ isOpen: false, jobId: null })}
onConfirm={confirmDeleteJob}
title={t('admin.jobs.deleteConfirm')}
description="Esta ação não pode ser desfeita e irá excluir a vaga permanentemente."
/>
{/* View Job Dialog */} {/* View Job Dialog */}
<Dialog open={isViewDialogOpen} onOpenChange={setIsViewDialogOpen}> <Dialog open={isViewDialogOpen} onOpenChange={setIsViewDialogOpen}>
<DialogContent className="max-w-2xl max-h-[85vh] overflow-y-auto"> <DialogContent className="max-w-2xl max-h-[85vh] overflow-y-auto">
@ -363,7 +379,7 @@ export default function AdminJobsPage() {
<Select value={editForm.currency} onValueChange={(v) => setEditForm({ ...editForm, currency: v })}> <Select value={editForm.currency} onValueChange={(v) => setEditForm({ ...editForm, currency: v })}>
<SelectTrigger><SelectValue /></SelectTrigger> <SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent> <SelectContent>
{["BRL","USD","EUR","GBP","JPY","CNY","AED","CAD","AUD","CHF"].map(c => ( {["BRL", "USD", "EUR", "GBP", "JPY", "CNY", "AED", "CAD", "AUD", "CHF"].map(c => (
<SelectItem key={c} value={c}>{c}</SelectItem> <SelectItem key={c} value={c}>{c}</SelectItem>
))} ))}
</SelectContent> </SelectContent>