From b0f96d80443d5e5f487640b95f30d5fbe3c61f39 Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Tue, 10 Mar 2026 09:59:24 +0300 Subject: [PATCH 1/3] feat: add delete reference functionality with confirmation modal --- .../src/controllers/ReferenceController.ts | 18 +++ platforms/ereputation/api/src/index.ts | 1 + .../api/src/services/ReferenceService.ts | 17 +++ .../client/client/src/pages/dashboard.tsx | 107 +++++++++++++++++- 4 files changed, 141 insertions(+), 2 deletions(-) diff --git a/platforms/ereputation/api/src/controllers/ReferenceController.ts b/platforms/ereputation/api/src/controllers/ReferenceController.ts index e250c6854..29a27d869 100644 --- a/platforms/ereputation/api/src/controllers/ReferenceController.ts +++ b/platforms/ereputation/api/src/controllers/ReferenceController.ts @@ -252,4 +252,22 @@ export class ReferenceController { res.status(500).json({ error: "Internal server error" }); } }; + + deleteReference = async (req: Request, res: Response) => { + try { + const { referenceId } = req.params; + const userId = req.user!.id; + + const deleted = await this.referenceService.deleteReference(referenceId, userId); + + if (!deleted) { + return res.status(404).json({ error: "Reference not found or not authorized" }); + } + + res.json({ message: "Reference deleted successfully" }); + } catch (error) { + console.error("Error deleting reference:", error); + res.status(500).json({ error: "Internal server error" }); + } + }; } diff --git a/platforms/ereputation/api/src/index.ts b/platforms/ereputation/api/src/index.ts index 2ad365765..16bcb7b1b 100644 --- a/platforms/ereputation/api/src/index.ts +++ b/platforms/ereputation/api/src/index.ts @@ -108,6 +108,7 @@ app.get("/api/references/target/:targetType/:targetId", referenceController.getR app.get("/api/references/my", authGuard, referenceController.getUserReferences); app.get("/api/references", authGuard, referenceController.getAllUserReferences); app.patch("/api/references/:referenceId/revoke", authGuard, referenceController.revokeReference); +app.delete("/api/references/:referenceId", authGuard, referenceController.deleteReference); // Reference signing routes app.post("/api/references/signing/session", authGuard, referenceSigningController.createSigningSession.bind(referenceSigningController)); diff --git a/platforms/ereputation/api/src/services/ReferenceService.ts b/platforms/ereputation/api/src/services/ReferenceService.ts index 17f5a314d..39365bcbc 100644 --- a/platforms/ereputation/api/src/services/ReferenceService.ts +++ b/platforms/ereputation/api/src/services/ReferenceService.ts @@ -94,4 +94,21 @@ export class ReferenceService { reference.status = "revoked"; return await this.referenceRepository.save(reference); } + + async deleteReference(referenceId: string, authorId: string): Promise { + const reference = await this.referenceRepository.findOne({ + where: { id: referenceId, authorId } + }); + + if (!reference) { + return false; + } + + // Delete related signatures first + const signatureRepository = AppDataSource.getRepository("ReferenceSignature"); + await signatureRepository.delete({ referenceId }); + + await this.referenceRepository.remove(reference); + return true; + } } diff --git a/platforms/ereputation/client/client/src/pages/dashboard.tsx b/platforms/ereputation/client/client/src/pages/dashboard.tsx index 342e5b7d8..8e27e2a85 100644 --- a/platforms/ereputation/client/client/src/pages/dashboard.tsx +++ b/platforms/ereputation/client/client/src/pages/dashboard.tsx @@ -1,8 +1,9 @@ import { useEffect, useState } from "react"; -import { useQuery } from "@tanstack/react-query"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useAuth } from "@/hooks/useAuth"; -import { clearAuth } from "@/lib/authUtils"; +import { clearAuth, isUnauthorizedError } from "@/lib/authUtils"; import { apiClient } from "@/lib/apiClient"; +import { useToast } from "@/hooks/use-toast"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { @@ -16,6 +17,7 @@ import { DialogContent, DialogHeader, DialogTitle, + DialogDescription, } from "@/components/ui/dialog"; import OtherCalculationModal from "@/components/modals/other-calculation-modal"; import ReferenceModal from "@/components/modals/reference-modal"; @@ -23,6 +25,8 @@ import ReferenceViewModal from "@/components/modals/reference-view-modal"; export default function Dashboard() { const { user, isAuthenticated, isLoading } = useAuth(); + const { toast } = useToast(); + const queryClient = useQueryClient(); const [otherModalOpen, setOtherModalOpen] = useState(false); const [referenceModalOpen, setReferenceModalOpen] = useState(false); const [viewModalOpen, setViewModalOpen] = useState(false); @@ -30,6 +34,7 @@ export default function Dashboard() { const [referenceViewModal, setReferenceViewModal] = useState(null); const [activeFilter, setActiveFilter] = useState('all'); const [currentPage, setCurrentPage] = useState(1); + const [deleteModalOpen, setDeleteModalOpen] = useState(null); // This page is only rendered when authenticated, no need for redirect logic @@ -72,6 +77,48 @@ export default function Dashboard() { window.location.href = "/"; }; + // Delete reference mutation + const deleteMutation = useMutation({ + mutationFn: async (referenceId: string) => { + return await apiClient.delete(`/api/references/${referenceId}`); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["/api/dashboard/activities"] }); + queryClient.invalidateQueries({ queryKey: ["/api/dashboard/stats"] }); + toast({ + title: "Reference Deleted", + description: "The reference has been successfully deleted.", + }); + }, + onError: (error) => { + if (isUnauthorizedError(error)) { + toast({ + title: "Unauthorized", + description: "You are logged out. Logging in again...", + variant: "destructive", + }); + setTimeout(() => { + window.location.href = "/"; + }, 500); + return; + } + toast({ + title: "Error", + description: "Failed to delete reference. Please try again.", + variant: "destructive", + }); + }, + }); + + const confirmDeleteActivity = () => { + if (deleteModalOpen) { + // Activity IDs are prefixed (e.g. "ref-sent-"), extract the actual reference UUID + const referenceId = deleteModalOpen.id.replace(/^ref-(sent|received)-/, ''); + deleteMutation.mutate(referenceId); + setDeleteModalOpen(null); + } + }; + const handleViewActivity = (activity: any) => { // For reference activities, show reference details modal if (activity.type === 'reference' || activity.activity === 'Reference Provided' || activity.activity === 'Reference Received') { @@ -580,6 +627,17 @@ export default function Dashboard() { View Details + {activity.activity === 'Reference Provided' && ( + setDeleteModalOpen(activity)} + > + + + + Delete + + )} @@ -626,6 +684,17 @@ export default function Dashboard() { View Details + {activity.activity === 'Reference Provided' && ( + setDeleteModalOpen(activity)} + > + + + + Delete + + )}
@@ -915,6 +984,40 @@ export default function Dashboard() { ) : null} + + {/* Delete Reference Confirmation Modal */} + !open && setDeleteModalOpen(null)}> + + +
+ + + +
+ Delete Reference + + Are you sure you want to permanently delete the reference for {deleteModalOpen?.target}? This action cannot be undone. + +
+ +
+ + +
+
+
); } From 0b0de57d5f8756cf4c4c9f43b4fbebf2927bc1d1 Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Tue, 10 Mar 2026 12:34:42 +0300 Subject: [PATCH 2/3] feat: refactor deleteReference to use transaction for cascading deletion --- .../ereputation/api/src/services/ReferenceService.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/platforms/ereputation/api/src/services/ReferenceService.ts b/platforms/ereputation/api/src/services/ReferenceService.ts index 39365bcbc..7ea737248 100644 --- a/platforms/ereputation/api/src/services/ReferenceService.ts +++ b/platforms/ereputation/api/src/services/ReferenceService.ts @@ -104,11 +104,11 @@ export class ReferenceService { return false; } - // Delete related signatures first - const signatureRepository = AppDataSource.getRepository("ReferenceSignature"); - await signatureRepository.delete({ referenceId }); + await AppDataSource.manager.transaction(async (manager) => { + await manager.delete("ReferenceSignature", { referenceId }); + await manager.remove(reference); + }); - await this.referenceRepository.remove(reference); return true; } } From e6710a406b33d6e82bc55e02abc70578c66d8b2c Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Tue, 10 Mar 2026 12:35:06 +0300 Subject: [PATCH 3/3] feat: reset delete modal state after successful deletion --- platforms/ereputation/client/client/src/pages/dashboard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platforms/ereputation/client/client/src/pages/dashboard.tsx b/platforms/ereputation/client/client/src/pages/dashboard.tsx index 8e27e2a85..897cfa6d6 100644 --- a/platforms/ereputation/client/client/src/pages/dashboard.tsx +++ b/platforms/ereputation/client/client/src/pages/dashboard.tsx @@ -89,6 +89,7 @@ export default function Dashboard() { title: "Reference Deleted", description: "The reference has been successfully deleted.", }); + setDeleteModalOpen(null); }, onError: (error) => { if (isUnauthorizedError(error)) { @@ -115,7 +116,6 @@ export default function Dashboard() { // Activity IDs are prefixed (e.g. "ref-sent-"), extract the actual reference UUID const referenceId = deleteModalOpen.id.replace(/^ref-(sent|received)-/, ''); deleteMutation.mutate(referenceId); - setDeleteModalOpen(null); } };