mirror of
https://github.com/lumina-rocks/lumina.git
synced 2026-06-06 02:31:16 +02:00
feat: Add CardOptionsDropdown component for sharing and viewing raw (#118)
* feat: Add CardOptionsDropdown component for sharing and viewing raw event data in NoteCard and KIND20Card * Update components/CardOptionsDropdown.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update components/CardOptionsDropdown.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: Import useMemo in CardOptionsDropdown for optimized rendering --------- Co-authored-by: highperfocused <highperfocused@pm.me> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
148
components/CardOptionsDropdown.tsx
Normal file
148
components/CardOptionsDropdown.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from "@/components/ui/drawer";
|
||||
import { Textarea } from "./ui/textarea";
|
||||
import { DotsVerticalIcon, CodeIcon, Share1Icon } from "@radix-ui/react-icons";
|
||||
import { Input } from "./ui/input";
|
||||
import { useRef, useState } from 'react';
|
||||
import { useToast } from "./ui/use-toast";
|
||||
import { Event as NostrEvent, nip19 } from "nostr-tools";
|
||||
|
||||
interface CardOptionsDropdownProps {
|
||||
event: NostrEvent;
|
||||
}
|
||||
|
||||
export default function CardOptionsDropdown({ event }: CardOptionsDropdownProps) {
|
||||
const jsonEvent = useMemo(() => JSON.stringify(event, null, 2), [event]);
|
||||
const inputRef = useRef(null);
|
||||
const inputRefID = useRef(null);
|
||||
const { toast } = useToast();
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const [shareDrawerOpen, setShareDrawerOpen] = useState(false);
|
||||
const [rawDrawerOpen, setRawDrawerOpen] = useState(false);
|
||||
|
||||
const handleCopyLink = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(window.location.href);
|
||||
toast({
|
||||
description: 'URL copied to clipboard',
|
||||
title: 'Copied'
|
||||
});
|
||||
} catch (err) {
|
||||
toast({
|
||||
description: 'Error copying URL to clipboard',
|
||||
title: 'Error',
|
||||
variant: 'destructive'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopyNoteId = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(nip19.noteEncode(event.id));
|
||||
toast({
|
||||
description: 'Note ID copied to clipboard',
|
||||
title: 'Copied'
|
||||
});
|
||||
} catch (err) {
|
||||
toast({
|
||||
description: 'Error copying Note ID to clipboard',
|
||||
title: 'Error',
|
||||
variant: 'destructive'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="ml-auto" aria-label="Options">
|
||||
<DotsVerticalIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{/* Share option */}
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setDropdownOpen(false);
|
||||
setTimeout(() => setShareDrawerOpen(true), 100);
|
||||
}}
|
||||
>
|
||||
<Share1Icon className="mr-2 h-4 w-4" />
|
||||
Share
|
||||
</DropdownMenuItem>
|
||||
|
||||
{/* View Raw option */}
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setDropdownOpen(false);
|
||||
setTimeout(() => setRawDrawerOpen(true), 100);
|
||||
}}
|
||||
>
|
||||
<CodeIcon className="mr-2 h-4 w-4" />
|
||||
View Raw
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
{/* Share Drawer */}
|
||||
<Drawer open={shareDrawerOpen} onOpenChange={setShareDrawerOpen}>
|
||||
<DrawerContent>
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Share this Note</DrawerTitle>
|
||||
<DrawerDescription>Share this Note with others.</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<div className="px-4">
|
||||
<div className="flex items-center mb-4">
|
||||
<Input ref={inputRef} value={window.location.href} readOnly className="mr-2" />
|
||||
<Button variant="outline" onClick={handleCopyLink}>Copy Link</Button>
|
||||
</div>
|
||||
<div className="flex items-center mb-4">
|
||||
<Input ref={inputRefID} value={nip19.noteEncode(event.id)} readOnly className="mr-2" />
|
||||
<Button variant="outline" onClick={handleCopyNoteId}>Copy Note ID</Button>
|
||||
</div>
|
||||
</div>
|
||||
<DrawerFooter>
|
||||
<DrawerClose asChild>
|
||||
<Button variant="outline">Close</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
{/* Raw Event Drawer */}
|
||||
<Drawer open={rawDrawerOpen} onOpenChange={setRawDrawerOpen}>
|
||||
<DrawerContent>
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Raw Event</DrawerTitle>
|
||||
<DrawerDescription>This shows the raw event data.</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<div className="px-4 pb-4">
|
||||
<Textarea rows={20} readOnly value={jsonEvent} />
|
||||
</div>
|
||||
<DrawerFooter>
|
||||
<DrawerClose asChild>
|
||||
<Button variant="outline">Close</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -7,13 +7,12 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/comp
|
||||
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel"
|
||||
import ReactionButton from "@/components/ReactionButton"
|
||||
import { Avatar, AvatarImage } from "@/components/ui/avatar"
|
||||
import ViewRawButton from "@/components/ViewRawButton"
|
||||
import ViewNoteButton from "./ViewNoteButton"
|
||||
import Link from "next/link"
|
||||
import ViewCopyButton from "./ViewCopyButton"
|
||||
import type { Event as NostrEvent } from "nostr-tools"
|
||||
import ZapButton from "./ZapButton"
|
||||
import Image from "next/image"
|
||||
import CardOptionsDropdown from "./CardOptionsDropdown"
|
||||
import { renderTextWithLinkedTags } from "@/utils/textUtils"
|
||||
|
||||
// Function to extract all images from a kind 20 event's imeta tags
|
||||
@@ -103,8 +102,8 @@ const KIND20Card: React.FC<KIND20CardProps> = ({
|
||||
<>
|
||||
<div key={event.id}>
|
||||
<Card className="my-4">
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<CardHeader className="flex flex-row items-center">
|
||||
<CardTitle className="flex-1">
|
||||
<Link href={hrefProfile} style={{ textDecoration: "none" }}>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
@@ -125,6 +124,7 @@ const KIND20Card: React.FC<KIND20CardProps> = ({
|
||||
</TooltipProvider>
|
||||
</Link>
|
||||
</CardTitle>
|
||||
<CardOptionsDropdown event={event} />
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<div className="w-full">
|
||||
@@ -177,10 +177,6 @@ const KIND20Card: React.FC<KIND20CardProps> = ({
|
||||
<ZapButton event={event} />
|
||||
{showViewNoteCardButton && <ViewNoteButton event={event} />}
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<ViewCopyButton event={event} />
|
||||
<ViewRawButton event={event} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
@@ -25,12 +25,11 @@ import {
|
||||
} from "@/components/ui/carousel"
|
||||
import ReactionButton from '@/components/ReactionButton';
|
||||
import { Avatar, AvatarImage } from '@/components/ui/avatar';
|
||||
import ViewRawButton from '@/components/ViewRawButton';
|
||||
import ViewNoteButton from './ViewNoteButton';
|
||||
import Link from 'next/link';
|
||||
import ViewCopyButton from './ViewCopyButton';
|
||||
import { Event as NostrEvent } from "nostr-tools";
|
||||
import ZapButton from './ZapButton';
|
||||
import CardOptionsDropdown from './CardOptionsDropdown';
|
||||
import { renderTextWithLinkedTags } from '@/utils/textUtils';
|
||||
|
||||
interface NoteCardProps {
|
||||
@@ -60,8 +59,8 @@ const NoteCard: React.FC<NoteCardProps> = ({ pubkey, text, eventId, tags, event,
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<CardHeader className="flex flex-row items-center">
|
||||
<CardTitle className="flex-1">
|
||||
<Link href={hrefProfile} style={{ textDecoration: 'none' }}>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
@@ -80,6 +79,7 @@ const NoteCard: React.FC<NoteCardProps> = ({ pubkey, text, eventId, tags, event,
|
||||
</TooltipProvider>
|
||||
</Link>
|
||||
</CardTitle>
|
||||
<CardOptionsDropdown event={event} />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className='py-4'>
|
||||
@@ -147,16 +147,12 @@ const NoteCard: React.FC<NoteCardProps> = ({ pubkey, text, eventId, tags, event,
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div className='py-4 space-x-4 flex justify-between items-start'>
|
||||
<div className='py-4 space-x-4 flex'>
|
||||
<div className='flex space-x-4'>
|
||||
<ReactionButton event={event} />
|
||||
<ZapButton event={event} />
|
||||
{showViewNoteCardButton && <ViewNoteButton event={event} />}
|
||||
</div>
|
||||
<div className='flex space-x-2'>
|
||||
<ViewCopyButton event={event} />
|
||||
<ViewRawButton event={event} />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
|
||||
Reference in New Issue
Block a user