Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
375 changes: 375 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@prisma/client": "^6.2.1",
"@radix-ui/react-dialog": "^1.1.5",
"@radix-ui/react-separator": "^1.1.1",
"@radix-ui/react-slot": "^1.1.1",
"@uiw/react-codemirror": "^4.23.7",
Expand Down
5 changes: 3 additions & 2 deletions src/app/api/collections/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,12 @@ export async function PUT(request: Request, props: { params: Promise<{ id: strin

export async function DELETE(request: Request, props: { params: Promise<{ id: string }> }) {
const params = await props.params;

try {
await prisma.collection.delete({
const deletedCollection = await prisma.collection.delete({
where: { id: params.id }
});
return new NextResponse(null, { status: 204 });
return NextResponse.json(deletedCollection);
} catch (error) {
console.error('Failed to delete collection:', error);
return NextResponse.json({ error: 'Failed to delete collection' }, { status: 500 });
Expand Down
15 changes: 15 additions & 0 deletions src/app/api/topics/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export async function GET(request: Request, props: TopicParams) {

export async function PUT(request: Request, props: TopicParams) {
const params = await props.params;

try {
const body = await request.json();
const updatedTopic = await prisma.topic.update({
Expand All @@ -45,4 +46,18 @@ export async function PUT(request: Request, props: TopicParams) {
{ status: 500 }
);
}
}

export async function DELETE(request: Request, props:TopicParams){
const params = await props.params;
try{
const deletedTopic = await prisma.topic.delete({
where: {
id: params.id,
},
})
return NextResponse.json(deletedTopic);
}catch(error){
return NextResponse.json({error: 'Failed to delete topic'},{status:500})
}
}
22 changes: 19 additions & 3 deletions src/components/Collections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
MinusCircle
} from 'lucide-react';
import Layout from '@/components/Layout';
import ConfirmationDialog from './ConfirmationDialog';

interface Topic {
id: string;
Expand Down Expand Up @@ -136,18 +137,19 @@ const TopicSelector = ({ topic, isSelected, onToggle }: {
</div>
);
};

export default function Collections() {
const pathname = usePathname();
const { theme } = useTheme();
const { collections, loading, error, createCollection, updateCollection, publishCollection, unpublishCollection } = useCollections();
const { collections, loading, error, createCollection, updateCollection, publishCollection, unpublishCollection, deleteCollection } = useCollections();
const { topics } = useTopics();
const [mounted, setMounted] = useState(false);
const [showNewCollectionForm, setShowNewCollectionForm] = useState(false);
const [editingCollection, setEditingCollection] = useState<Collection | null>(null);
const [newCollectionName, setNewCollectionName] = useState('');
const [newCollectionDesc, setNewCollectionDesc] = useState('');
const [selectedTopicIds, setSelectedTopicIds] = useState<string[]>([]);

const [isConfirmDialogOpen, setIsConfirmDialog] = useState(false);
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
Expand Down Expand Up @@ -320,6 +322,15 @@ export default function Collections() {
setSelectedTopicIds([...collection.topicIds]);
};

const handleDeleteDocument = async (docId: string) => {
try {
await deleteCollection(docId);
setIsConfirmDialog(false);
} catch (error) {
console.error('Failed to delete collection:', error);
}
}

if (!mounted) return null;

if (loading) {
Expand Down Expand Up @@ -510,6 +521,11 @@ export default function Collections() {
<Globe className="h-4 w-4" />
</Button>
)}

<Button variant="ghost" size="sm">
<ConfirmationDialog action={handleDeleteDocument} id={collection.id} isDialogOpen={isConfirmDialogOpen} setIsDialogOpen={setIsConfirmDialog} title='Collection'/>
</Button>

</div>
</CardHeader>
<CardContent>
Expand All @@ -528,7 +544,7 @@ export default function Collections() {
{new Date(collection.lastEdited).toLocaleDateString()}
</div>
{collection.publishedUrl && (
<a
<a
href={collection.publishedUrl}
target="_blank"
rel="noopener noreferrer"
Expand Down
48 changes: 48 additions & 0 deletions src/components/ConfirmationDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Trash2 } from "lucide-react"

interface ConfirmationDialogProps {
isDialogOpen: boolean
setIsDialogOpen: React.Dispatch<React.SetStateAction<boolean>>
id:string
action: (id:string)=>Promise<void>
title: string

}
export default function ConfirmationDialog({isDialogOpen,setIsDialogOpen,id,action,title}:ConfirmationDialogProps) {
return (
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogTrigger asChild>
<Trash2 className="h-4 w-4" />
</DialogTrigger>
<DialogContent className="sm:max-w-[425px] bg-slate-50 dark:bg-slate-900">
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
{`This action cannot be undone. Are you sure you want to permanently
delete this ${title} from our servers?`}
</DialogDescription>
</DialogHeader>

<DialogFooter>
<DialogClose asChild>
<Button type="button" variant='ghost'>
Cancel
</Button>
</DialogClose>
<Button variant='destructive' onClick={()=>action(id)}>Delete</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
10 changes: 7 additions & 3 deletions src/components/MarkdownCMS.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
Link as LinkIcon,
Code
} from 'lucide-react';
import ConfirmationDialog from './ConfirmationDialog';

type SortOption = 'newest' | 'oldest' | 'az' | 'za' | 'longest' | 'shortest';

Expand Down Expand Up @@ -277,6 +278,7 @@ export default function MarkdownCMS() {
const [newDocDescription, setNewDocDescription] = useState('');
const [showNewDocForm, setShowNewDocForm] = useState(false);
const [sortBy, setSortBy] = useState<SortOption>('newest');
const [isConfirmDialogOpen, setIsConfirmDialog] = useState(false);
const [editingDoc, setEditingDoc] = useState<{
id: string;
name: string;
Expand Down Expand Up @@ -393,6 +395,7 @@ export default function MarkdownCMS() {
try {
await deleteTopic(docId);
setSelectedDocs(prev => prev.filter(id => id !== docId));
setIsConfirmDialog(false);
} catch (error) {
console.error('Failed to delete topic:', error);
}
Expand Down Expand Up @@ -556,14 +559,15 @@ export default function MarkdownCMS() {
description: topic.description
})}
>
<Pencil className="h-4 w-4" />
<Pencil className="h-4 w-4" />
</Button>
<Button

variant="ghost"
size="sm"
onClick={() => handleDeleteDocument(topic.id)}

>
<Trash2 className="h-4 w-4" />
<ConfirmationDialog action={handleDeleteDocument} id={topic.id} isDialogOpen={isConfirmDialogOpen} setIsDialogOpen={setIsConfirmDialog} title='Topic'/>
</Button>
</div>
</CardHeader>
Expand Down
122 changes: 122 additions & 0 deletions src/components/ui/dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"use client"

import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"

import { cn } from "@/lib/utils"

const Dialog = DialogPrimitive.Root

const DialogTrigger = DialogPrimitive.Trigger

const DialogPortal = DialogPrimitive.Portal

const DialogClose = DialogPrimitive.Close

const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName

const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName

const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"

const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"

const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName

const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName

export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}