diff options
| author | MaheshtheDev <[email protected]> | 2025-10-03 07:51:00 +0000 |
|---|---|---|
| committer | MaheshtheDev <[email protected]> | 2025-10-03 07:51:00 +0000 |
| commit | 56590e93297a1e25304a7fd1b255d26386bb3b71 (patch) | |
| tree | 38761295561f8887f166b2bd2f3ddd99014b8446 /apps | |
| parent | feat(raycast-extension): initial version of supermemory extension for raycast... (diff) | |
| download | supermemory-56590e93297a1e25304a7fd1b255d26386bb3b71.tar.xz supermemory-56590e93297a1e25304a7fd1b255d26386bb3b71.zip | |
feat: delete memories and theme issues across the app (#449)10-02-fix_ui_theme_issues_across_the_app
# 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.
Diffstat (limited to 'apps')
| -rw-r--r-- | apps/web/components/content-cards/google-docs.tsx | 88 | ||||
| -rw-r--r-- | apps/web/components/content-cards/note.tsx | 96 | ||||
| -rw-r--r-- | apps/web/components/content-cards/tweet.tsx | 66 | ||||
| -rw-r--r-- | apps/web/components/content-cards/website.tsx | 88 | ||||
| -rw-r--r-- | apps/web/components/masonry-memory-list.tsx | 8 | ||||
| -rw-r--r-- | apps/web/components/views/add-memory/action-buttons.tsx | 118 | ||||
| -rw-r--r-- | apps/web/components/views/add-memory/index.tsx | 7 |
7 files changed, 361 insertions, 110 deletions
diff --git a/apps/web/components/content-cards/google-docs.tsx b/apps/web/components/content-cards/google-docs.tsx index 22f06f77..0306876d 100644 --- a/apps/web/components/content-cards/google-docs.tsx +++ b/apps/web/components/content-cards/google-docs.tsx @@ -2,8 +2,18 @@ import { Card, CardContent } from "@repo/ui/components/card" import { Badge } from "@repo/ui/components/badge" -import { ExternalLink, FileText, Brain } from "lucide-react" -import { useState } from "react" +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@repo/ui/components/alert-dialog" +import { ExternalLink, FileText, Brain, Trash2 } from "lucide-react" import { cn } from "@lib/utils" import { colors } from "@repo/ui/memory-graph/constants" import { getPastelBackgroundColor } from "../memories-utils" @@ -14,6 +24,7 @@ interface GoogleDocsCardProps { description?: string | null className?: string onClick?: () => void + onDelete?: () => void showExternalLink?: boolean activeMemories?: Array<{ id: string; isForgotten?: boolean }> lastModified?: string | Date @@ -25,12 +36,11 @@ export const GoogleDocsCard = ({ description, className, onClick, + onDelete, showExternalLink = true, activeMemories, lastModified, }: GoogleDocsCardProps) => { - const [imageError, setImageError] = useState(false) - const handleCardClick = () => { if (onClick) { onClick() @@ -57,6 +67,54 @@ export const GoogleDocsCard = ({ backgroundColor: getPastelBackgroundColor(url || title || "googledocs"), }} > + {onDelete && ( + <AlertDialog> + <AlertDialogTrigger asChild> + <button + className="absolute top-2 right-2 z-20 opacity-0 group-hover:opacity-100 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() + }} + > + Delete + </AlertDialogAction> + </AlertDialogFooter> + </AlertDialogContent> + </AlertDialog> + )} + <CardContent className="p-0"> <div className="px-4 border-b border-white/10"> <div className="flex items-center justify-between"> @@ -99,16 +157,18 @@ export const GoogleDocsCard = ({ </span> </div> </div> - {showExternalLink && ( - <button - onClick={handleExternalLinkClick} - className="opacity-0 group-hover:opacity-100 transition-opacity p-1 rounded hover:bg-white/10 flex-shrink-0" - type="button" - aria-label="Open in Google Docs" - > - <ExternalLink className="w-4 h-4" /> - </button> - )} + <div className="flex items-center gap-1"> + {showExternalLink && ( + <button + onClick={handleExternalLinkClick} + className="opacity-0 group-hover:opacity-100 transition-opacity p-1 rounded hover:bg-white/10 flex-shrink-0" + type="button" + aria-label="Open in Google Docs" + > + <ExternalLink className="w-4 h-4" /> + </button> + )} + </div> </div> </div> diff --git a/apps/web/components/content-cards/note.tsx b/apps/web/components/content-cards/note.tsx index e7703d9b..b0014bf6 100644 --- a/apps/web/components/content-cards/note.tsx +++ b/apps/web/components/content-cards/note.tsx @@ -1,8 +1,19 @@ 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 } from "lucide-react" +import { Brain, ExternalLink, Trash2 } from "lucide-react" import { cn } from "@lib/utils" import { formatDate, @@ -32,6 +43,7 @@ export const NoteCard = ({ activeMemories, forgottenMemories, onOpenDetails, + onDelete, }: NoteCardProps) => { return ( <Card @@ -47,6 +59,52 @@ export const NoteCard = ({ 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"> @@ -59,23 +117,25 @@ export const NoteCard = ({ {document.title || "Untitled Document"} </p> </div> - {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 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> diff --git a/apps/web/components/content-cards/tweet.tsx b/apps/web/components/content-cards/tweet.tsx index 3f46d6cc..34db9eb5 100644 --- a/apps/web/components/content-cards/tweet.tsx +++ b/apps/web/components/content-cards/tweet.tsx @@ -14,7 +14,18 @@ import { enrichTweet, } from "react-tweet" import { Badge } from "@repo/ui/components/badge" -import { Brain } from "lucide-react" +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@repo/ui/components/alert-dialog" +import { Brain, Trash2 } from "lucide-react" import { colors } from "@repo/ui/memory-graph/constants" import { getPastelBackgroundColor } from "../memories-utils" @@ -71,18 +82,69 @@ const CustomTweet = ({ export const TweetCard = ({ data, activeMemories, + onDelete, }: { data: Tweet activeMemories?: Array<{ id: string; isForgotten?: boolean }> + onDelete?: () => void }) => { return ( <div - className="relative transition-all" + className="relative transition-all group" style={{ backgroundColor: getPastelBackgroundColor(data.id_str || "tweet"), }} > <CustomTweet components={{}} tweet={data} /> + + {onDelete && ( + <AlertDialog> + <AlertDialogTrigger asChild> + <button + className="absolute top-2 right-2 z-20 opacity-0 group-hover:opacity-100 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() + }} + > + Delete + </AlertDialogAction> + </AlertDialogFooter> + </AlertDialogContent> + </AlertDialog> + )} + {activeMemories && activeMemories.length > 0 && ( <div className="absolute bottom-2 left-4 z-10"> <Badge diff --git a/apps/web/components/content-cards/website.tsx b/apps/web/components/content-cards/website.tsx index f36cd247..b3ee7df6 100644 --- a/apps/web/components/content-cards/website.tsx +++ b/apps/web/components/content-cards/website.tsx @@ -1,10 +1,22 @@ "use client" import { Card, CardContent } from "@repo/ui/components/card" -import { ExternalLink } from "lucide-react" +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@repo/ui/components/alert-dialog" +import { ExternalLink, Trash2 } from "lucide-react" import { useState } from "react" import { cn } from "@lib/utils" import { getPastelBackgroundColor } from "../memories-utils" +import { colors } from "@repo/ui/memory-graph/constants" interface WebsiteCardProps { title: string @@ -13,6 +25,7 @@ interface WebsiteCardProps { description?: string className?: string onClick?: () => void + onDelete?: () => void showExternalLink?: boolean } @@ -23,6 +36,7 @@ export const WebsiteCard = ({ description, className, onClick, + onDelete, showExternalLink = true, }: WebsiteCardProps) => { const [imageError, setImageError] = useState(false) @@ -51,7 +65,7 @@ export const WebsiteCard = ({ return ( <Card className={cn( - "cursor-pointer transition-all hover:shadow-md group overflow-hidden py-0", + "cursor-pointer transition-all hover:shadow-md group overflow-hidden py-0 relative", className, )} onClick={handleCardClick} @@ -59,6 +73,54 @@ export const WebsiteCard = ({ backgroundColor: getPastelBackgroundColor(url || title || "website"), }} > + {onDelete && ( + <AlertDialog> + <AlertDialogTrigger asChild> + <button + className="absolute top-2 right-2 z-20 opacity-0 group-hover:opacity-100 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() + }} + > + Delete + </AlertDialogAction> + </AlertDialogFooter> + </AlertDialogContent> + </AlertDialog> + )} + <CardContent className="p-0"> {image && !imageError && ( <div className="relative h-38 bg-gray-100 overflow-hidden"> @@ -75,16 +137,18 @@ export const WebsiteCard = ({ <div className="px-4 py-2 space-y-2"> <div className="font-semibold text-sm line-clamp-2 leading-tight flex items-center justify-between"> {title} - {showExternalLink && ( - <button - onClick={handleExternalLinkClick} - className="opacity-0 group-hover:opacity-100 transition-opacity p-1 rounded hover:bg-gray-100 flex-shrink-0" - type="button" - aria-label="Open in new tab" - > - <ExternalLink className="w-3 h-3" /> - </button> - )} + <div className="flex items-center gap-1"> + {showExternalLink && ( + <button + onClick={handleExternalLinkClick} + className="opacity-0 group-hover:opacity-100 transition-opacity p-1 rounded hover:bg-gray-100 flex-shrink-0" + type="button" + aria-label="Open in new tab" + > + <ExternalLink className="w-3 h-3" /> + </button> + )} + </div> </div> {description && ( diff --git a/apps/web/components/masonry-memory-list.tsx b/apps/web/components/masonry-memory-list.tsx index 2f634f74..93326e49 100644 --- a/apps/web/components/masonry-memory-list.tsx +++ b/apps/web/components/masonry-memory-list.tsx @@ -63,6 +63,7 @@ const DocumentCard = memo( description={document.content} activeMemories={activeMemories} lastModified={document.updatedAt || document.createdAt} + onDelete={() => onDelete(document)} /> ) } @@ -77,6 +78,7 @@ const DocumentCard = memo( document.metadata?.sm_internal_twitter_metadata as unknown as Tweet } activeMemories={activeMemories} + onDelete={() => onDelete(document)} /> ) } @@ -87,6 +89,7 @@ const DocumentCard = memo( url={document.url} title={document.title || "Untitled Document"} image={document.ogImage} + onDelete={() => onDelete(document)} /> ) } @@ -212,9 +215,7 @@ export const MasonryMemoryList = ({ ) : isLoading ? ( <div className="h-full flex items-center justify-center p-4"> <div className="rounded-xl overflow-hidden"> - <div - className="relative z-10 px-6 py-4" - > + <div className="relative z-10 px-6 py-4"> <div className="flex items-center gap-2"> <Sparkles className="w-4 h-4 animate-spin text-blue-400" /> <span>Loading memory list...</span> @@ -232,6 +233,7 @@ export const MasonryMemoryList = ({ data-theme="light" > <Masonry + key={`masonry-${filteredDocuments.length}-${filteredDocuments.map((d) => d.id).join(",")}`} items={filteredDocuments} render={renderDocumentCard} columnGutter={16} diff --git a/apps/web/components/views/add-memory/action-buttons.tsx b/apps/web/components/views/add-memory/action-buttons.tsx index fc901ba9..3f93fe17 100644 --- a/apps/web/components/views/add-memory/action-buttons.tsx +++ b/apps/web/components/views/add-memory/action-buttons.tsx @@ -1,67 +1,67 @@ -import { Button } from '@repo/ui/components/button'; -import { Loader2, type LucideIcon } from 'lucide-react'; -import { motion } from 'motion/react'; +import { Button } from "@repo/ui/components/button" +import { Loader2, type LucideIcon } from "lucide-react" +import { motion } from "motion/react" interface ActionButtonsProps { - onCancel: () => void; - onSubmit?: () => void; - submitText: string; - submitIcon?: LucideIcon; - isSubmitting?: boolean; - isSubmitDisabled?: boolean; - submitType?: 'button' | 'submit'; - className?: string; + onCancel: () => void + onSubmit?: () => void + submitText: string + submitIcon?: LucideIcon + isSubmitting?: boolean + isSubmitDisabled?: boolean + submitType?: "button" | "submit" + className?: string } export function ActionButtons({ - onCancel, - onSubmit, - submitText, - submitIcon: SubmitIcon, - isSubmitting = false, - isSubmitDisabled = false, - submitType = 'submit', - className = '', + onCancel, + onSubmit, + submitText, + submitIcon: SubmitIcon, + isSubmitting = false, + isSubmitDisabled = false, + submitType = "submit", + className = "", }: ActionButtonsProps) { - return ( - <div className={`flex gap-3 order-1 sm:order-2 justify-end ${className}`}> - <Button - className="hover:bg-foreground/10 border-none flex-1 sm:flex-initial" - onClick={onCancel} - type="button" - variant="ghost" - > - Cancel - </Button> + return ( + <div className={`flex gap-3 order-1 sm:order-2 justify-end ${className}`}> + <Button + className="hover:bg-foreground/10 border-none flex-1 sm:flex-initial" + onClick={onCancel} + type="button" + variant="ghost" + > + Cancel + </Button> - <motion.div - whileHover={{ scale: 1.05 }} - whileTap={{ scale: 0.95 }} - className="flex-1 sm:flex-initial" - > - <Button - className="bg-foreground hover:bg-foreground/20 border-foreground/20 w-full" - disabled={isSubmitting || isSubmitDisabled} - onClick={submitType === 'button' ? onSubmit : undefined} - type={submitType} - > - {isSubmitting ? ( - <> - <Loader2 className="h-4 w-4 animate-spin mr-2" /> - {submitText.includes('Add') - ? 'Adding...' - : submitText.includes('Upload') - ? 'Uploading...' - : 'Processing...'} - </> - ) : ( - <> - {SubmitIcon && <SubmitIcon className="h-4 w-4 mr-2" />} - {submitText} - </> - )} - </Button> - </motion.div> - </div> - ); + <motion.div + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.95 }} + className="flex-1 sm:flex-initial" + > + <Button + className="w-full" + disabled={isSubmitting || isSubmitDisabled} + onClick={submitType === "button" ? onSubmit : undefined} + type={submitType} + > + {isSubmitting ? ( + <> + <Loader2 className="h-4 w-4 animate-spin mr-2" /> + {submitText.includes("Add") + ? "Adding..." + : submitText.includes("Upload") + ? "Uploading..." + : "Processing..."} + </> + ) : ( + <> + {SubmitIcon && <SubmitIcon className="h-4 w-4 mr-2" />} + {submitText} + </> + )} + </Button> + </motion.div> + </div> + ) } diff --git a/apps/web/components/views/add-memory/index.tsx b/apps/web/components/views/add-memory/index.tsx index a78e7629..d9c6aef8 100644 --- a/apps/web/components/views/add-memory/index.tsx +++ b/apps/web/components/views/add-memory/index.tsx @@ -88,7 +88,10 @@ export function AddMemoryView({ const [newProjectName, setNewProjectName] = useState("") // Check memory limits - const { data: memoriesCheck } = fetchMemoriesFeature(autumn, !autumn.isLoading) + const { data: memoriesCheck } = fetchMemoriesFeature( + autumn, + !autumn.isLoading, + ) const memoriesUsed = memoriesCheck?.usage ?? 0 const memoriesLimit = memoriesCheck?.included_usage ?? 0 @@ -757,7 +760,7 @@ export function AddMemoryView({ {({ state, handleChange, handleBlur }) => ( <> <Input - className={`bg-black/5 border-black/10 text-black ${ + className={`bg-black/5 border-black/10 ${ addContentMutation.isPending ? "opacity-50" : "" }`} disabled={addContentMutation.isPending} |