supermemory/apps/web/components/content-cards/note.tsx
MaheshtheDev 56590e9329 feat: delete memories and theme issues across the app (#449)
# Add document deletion functionality and fix UI theme issues

This PR adds the ability to delete documents and their associated memories across all content card types (Google Docs, Notes, Tweets, and Websites). Each card now includes:

- A delete button that appears on hover
- A confirmation dialog to prevent accidental deletions
- Proper event handling to avoid triggering card clicks when using delete controls

Additionally, this PR fixes various UI theme issues:

- Updates button styling in the ActionButtons component
- Improves theme consistency by replacing hardcoded colors with theme variables
- Fixes text color issues in login page components
- Ensures proper color contrast in various UI elements

The masonry layout was also improved to properly re-render when documents are removed.
2025-10-03 07:51:00 +00:00

193 lines
5.3 KiB
TypeScript

import { Badge } from "@repo/ui/components/badge"
import { Card, CardContent, CardHeader } from "@repo/ui/components/card"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@repo/ui/components/alert-dialog"
import { colors } from "@repo/ui/memory-graph/constants"
import { Brain, ExternalLink, Trash2 } from "lucide-react"
import { cn } from "@lib/utils"
import {
formatDate,
getPastelBackgroundColor,
getSourceUrl,
} from "../memories-utils"
import { MCPIcon } from "../menu"
import { analytics } from "@/lib/analytics"
import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api"
import type { z } from "zod"
type DocumentsResponse = z.infer<typeof DocumentsWithMemoriesResponseSchema>
type DocumentWithMemories = DocumentsResponse["documents"][0]
interface NoteCardProps {
document: DocumentWithMemories
width: number
activeMemories: Array<{ id: string; isForgotten?: boolean }>
forgottenMemories: Array<{ id: string; isForgotten?: boolean }>
onOpenDetails: (document: DocumentWithMemories) => void
onDelete: (document: DocumentWithMemories) => void
}
export const NoteCard = ({
document,
width,
activeMemories,
forgottenMemories,
onOpenDetails,
onDelete,
}: NoteCardProps) => {
return (
<Card
className="w-full p-4 transition-all cursor-pointer group relative overflow-hidden gap-2 shadow-xs"
onClick={() => {
analytics.documentCardClicked()
onOpenDetails(document)
}}
style={{
backgroundColor: getPastelBackgroundColor(
document.id || document.title || "note",
),
width: width,
}}
>
<AlertDialog>
<AlertDialogTrigger asChild>
<button
className="absolute top-2 right-2 z-20 opacity-0 group-hover:opacity-100 group-hover:cursor-pointer transition-opacity p-1.5 rounded-md hover:bg-red-500/20"
onClick={(e) => {
e.stopPropagation()
}}
style={{
color: colors.text.muted,
backgroundColor: "rgba(255, 255, 255, 0.1)",
backdropFilter: "blur(4px)",
}}
type="button"
>
<Trash2 className="w-3.5 h-3.5" />
</button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete Document</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete this document and all its related
memories? This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel
onClick={(e) => {
e.stopPropagation()
}}
>
Cancel
</AlertDialogCancel>
<AlertDialogAction
className="bg-red-600 hover:bg-red-700 text-white"
onClick={(e) => {
e.stopPropagation()
onDelete(document)
}}
>
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<CardHeader className="relative z-10 px-0 pb-0">
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-1">
<p
className={cn(
"text-sm font-medium line-clamp-1",
document.url ? "max-w-[190px]" : "max-w-[200px]",
)}
>
{document.title || "Untitled Document"}
</p>
</div>
<div className="flex items-center gap-1">
{document.url && (
<button
className="opacity-0 group-hover:opacity-100 transition-opacity p-1 rounded"
onClick={(e) => {
e.stopPropagation()
const sourceUrl = getSourceUrl(document)
window.open(sourceUrl ?? undefined, "_blank")
}}
style={{
backgroundColor: "rgba(255, 255, 255, 0.05)",
color: colors.text.secondary,
}}
type="button"
>
<ExternalLink className="w-3 h-3" />
</button>
)}
</div>
<div className="flex items-center gap-2 text-[10px] text-muted-foreground">
<span>{formatDate(document.createdAt)}</span>
</div>
</div>
</CardHeader>
<CardContent className="relative z-10 px-0">
{document.content && (
<p
className="text-xs line-clamp-6"
style={{ color: colors.text.muted }}
>
{document.content}
</p>
)}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2 flex-wrap">
{activeMemories.length > 0 && (
<Badge
className="text-xs text-accent-foreground mt-2"
style={{
backgroundColor: colors.memory.secondary,
}}
variant="secondary"
>
<Brain className="w-3 h-3 mr-1" />
{activeMemories.length}{" "}
{activeMemories.length === 1 ? "memory" : "memories"}
</Badge>
)}
{forgottenMemories.length > 0 && (
<Badge
className="text-xs mt-2"
style={{
borderColor: "rgba(255, 255, 255, 0.2)",
color: colors.text.muted,
}}
variant="outline"
>
{forgottenMemories.length} forgotten
</Badge>
)}
{document.source === "mcp" && (
<Badge variant="outline" className="mt-2">
<MCPIcon className="w-3 h-3 mr-1" />
MCP
</Badge>
)}
</div>
</div>
</CardContent>
</Card>
)
}
NoteCard.displayName = "NoteCard"