Skip to content

Commit e40b4c2

Browse files
committed
Add delete document flow: RPC delete + UI integration + state updates
1 parent c474b0a commit e40b4c2

4 files changed

Lines changed: 115 additions & 19 deletions

File tree

src/components/dashboard/ReadingGrid.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use client";
22

3+
import { useState } from "react";
34
import { Reading } from "@/lib/dummyData";
45
import ReadingItem from "./ReadingItem";
56

@@ -8,7 +9,13 @@ interface ReadingGridProps {
89
}
910

1011
export default function ReadingGrid({ readings }: ReadingGridProps) {
11-
if (readings.length === 0) {
12+
const [items, setItems] = useState(readings);
13+
14+
const handleDelete = (id: string) => {
15+
setItems((prev) => prev.filter((item) => item.id !== id));
16+
};
17+
18+
if (items.length === 0) {
1219
return (
1320
<div className="text-center py-12">
1421
<svg
@@ -36,9 +43,13 @@ export default function ReadingGrid({ readings }: ReadingGridProps) {
3643

3744
return (
3845
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
39-
{readings.map((reading) => (
46+
{items.map((reading) => (
4047
<div key={reading.id} className="neu-outset rounded-lg">
41-
<ReadingItem reading={reading} viewMode="gallery" />
48+
<ReadingItem
49+
reading={reading}
50+
viewMode="gallery"
51+
onDelete={handleDelete}
52+
/>
4253
</div>
4354
))}
4455
</div>

src/components/dashboard/ReadingItem.tsx

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,41 @@
33
import { Reading } from "@/lib/dummyData";
44
import {} from "react";
55
import ShareLinkButton from "@/components/ShareLinkButton";
6+
import { DocumentService } from "@/services/documentService";
7+
import { Trash } from "lucide-react";
68
import { useRouter } from "next/navigation";
79

810
interface ReadingItemProps {
911
reading: Reading;
1012
viewMode: "gallery" | "list";
13+
onDelete?: (id: string) => void;
1114
}
1215

13-
export default function ReadingItem({ reading, viewMode }: ReadingItemProps) {
16+
export default function ReadingItem({ reading, viewMode, onDelete }: ReadingItemProps) {
1417
// Minimal actions (share handled by icon button)
1518
const router = useRouter();
1619

20+
const handleDelete = async (e: React.MouseEvent) => {
21+
e.stopPropagation();
22+
23+
console.log("Attempting to delete:", reading.id);
24+
25+
const confirmed = window.confirm(
26+
`Delete "${reading.title}"? This cannot be undone.`
27+
);
28+
if (!confirmed) return;
29+
30+
try {
31+
await DocumentService.deleteDocument(reading.id);
32+
onDelete?.(reading.id);
33+
router.refresh();
34+
} catch (err) {
35+
const error = err as Error;
36+
// console.error("Delete error →", error);
37+
alert("Failed to delete document: " + (error.message ?? ""));
38+
}
39+
};
40+
1741
const handleNavigate = () => {
1842
if (reading.id) {
1943
router.push(`/documents/${reading.id}`);
@@ -80,7 +104,15 @@ export default function ReadingItem({ reading, viewMode }: ReadingItemProps) {
80104
)}
81105
</div>
82106

83-
<div className="ml-4" onClick={(e) => e.stopPropagation()}>
107+
<div className="ml-4 flex items-center gap-2" onClick={(e) => e.stopPropagation()}>
108+
<button
109+
onClick={handleDelete}
110+
className="p-1 text-red-500 hover:text-red-700"
111+
aria-label="Delete document"
112+
>
113+
<Trash className="w-5 h-5" />
114+
</button>
115+
84116
<ShareLinkButton documentId={reading.id} variant="icon" />
85117
</div>
86118
</div>
@@ -118,7 +150,23 @@ export default function ReadingItem({ reading, viewMode }: ReadingItemProps) {
118150
<h3 className="text-lg font-semibold text-gray-900 line-clamp-2">
119151
{reading.title}
120152
</h3>
121-
<div className="ml-2" onClick={(e) => e.stopPropagation()}>
153+
<div className="ml-2 flex items-center gap-2" onClick={(e) => e.stopPropagation()}>
154+
<button
155+
onClick={handleDelete}
156+
aria-label="Delete document"
157+
className="
158+
rounded-md p-2
159+
bg-white
160+
shadow-[2px_2px_6px_#d1d5db,-2px_-2px_6px_#ffffff]
161+
hover:shadow-[1px_1px_3px_#d1d5db,-1px_-1px_3px_#ffffff]
162+
transition-shadow
163+
text-red-500 hover:text-red-600
164+
flex items-center justify-center
165+
"
166+
>
167+
<Trash className="w-4 h-4" strokeWidth={2} />
168+
</button>
169+
122170
<ShareLinkButton documentId={reading.id} variant="icon" />
123171
</div>
124172
</div>

src/components/dashboard/ReadingListCondensed.tsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Reading } from "@/lib/dummyData";
44
import { useState, useEffect, useRef } from "react";
55
import { useRouter } from "next/navigation";
66
import ShareLinkButton from "@/components/ShareLinkButton";
7+
import { DocumentService } from "@/services/documentService";
78

89
interface ReadingListCondensedProps {
910
readings: Reading[];
@@ -30,9 +31,25 @@ export default function ReadingListCondensed({
3031
};
3132
}, []);
3233

33-
const handleDelete = (id: string) => {
34-
console.log("Delete reading:", id);
35-
// TODO: Implement actual delete functionality
34+
const handleDelete = async (id: string) => {
35+
const confirmed = window.confirm(
36+
"Are you sure you want to permanently delete this document?"
37+
);
38+
if (!confirmed) return;
39+
40+
try {
41+
await DocumentService.deleteDocument(id);
42+
43+
// Remove deleted item from local UI list
44+
// You may be receiving `readings` via props, so update locally.
45+
router.refresh(); // if you use server components OR
46+
// OR update local state (better if readings is state-managed)
47+
// setReadings((prev) => prev.filter((r) => r.id !== id));
48+
49+
} catch (err) {
50+
console.error("Error deleting document:", err);
51+
alert("Failed to delete document. Please try again.");
52+
}
3653
};
3754

3855
const toggleSelection = (id: string) => {

src/services/documentService.ts

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -142,18 +142,38 @@ export class DocumentService {
142142

143143
static async deleteDocument(id: string): Promise<void> {
144144
try {
145-
const { data: { user } } = await supabase.auth.getUser();
146-
if (!user) {
147-
throw new Error('User not authenticated');
148-
}
149-
const { error } = await supabase
150-
.from('documents')
151-
.delete()
152-
.eq('id', id)
153-
.eq('owner_id', user.id);
145+
const { data: { user }, error: authError } = await supabase.auth.getUser();
146+
if (authError) throw authError;
147+
if (!user) throw new Error('User not authenticated');
148+
149+
// 1. RPC delete: deletes the row, cascades document_shares, returns storage info
150+
const { data, error } = await supabase.rpc("delete_document", {
151+
p_document_id: id,
152+
});
153+
console.log("RPC result:", { data, error });
154154

155155
if (error) {
156-
throw error;
156+
console.error("RPC delete_document error:", error);
157+
throw new Error(error.message);
158+
}
159+
160+
if (!data || data.length === 0) {
161+
throw new Error("Document not found or not authorized to delete.");
162+
}
163+
164+
const { storage_bucket, storage_path } = data[0];
165+
166+
// 2. Frontend storage deletion
167+
if (storage_bucket && storage_path) {
168+
const { error: storageError } = await supabase
169+
.storage
170+
.from(storage_bucket)
171+
.remove([storage_path]);
172+
173+
if (storageError) {
174+
console.error("Storage delete failed:", storageError);
175+
// We don't throw here because DB deletion was successful
176+
}
157177
}
158178
} catch (error) {
159179
console.error('Error deleting document:', error);

0 commit comments

Comments
 (0)