import type { MessageWithMeta } from '@/contexts';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { useUser } from '@/contexts';
import { cn } from '@/lib/utils';
import { AlertCircle, Copy, Edit, GitPullRequest, LayoutDashboard, MoreHorizontal, Reply, Trash2 } from 'lucide-react';
import { useState } from 'react';
import { toast } from 'sonner';
import { getSenderUserUid } from './sender';
interface RoomMessageActionsProps {
message: MessageWithMeta;
onEdit?: () => void;
onRevoke?: () => void;
onReply?: () => void;
}
export function RoomMessageActions({ message, onEdit, onRevoke, onReply }: RoomMessageActionsProps) {
const [isOpen, setIsOpen] = useState(false);
const { user } = useUser();
const isOwner = user?.uid === getSenderUserUid(message);
const handleCopy = async () => {
if (message.content_type !== 'text') return;
try {
await navigator.clipboard.writeText(message.content);
toast.success('Message copied');
} catch {
toast.error('Failed to copy message');
} finally {
setIsOpen(false);
}
};
return (
{onReply && (
)}
{message.content_type === 'text' && (
Copy
)}
{message.content_type === 'text' && (
{
toast.info('Creating issue from message…', {
description: 'This will open the issue creation form with the message content pre-filled.',
action: {
label: 'Create',
onClick: () => {
// TODO: wire to POST /api/issue/{project}/issues/from-message
const title = message.content.split('\n')[0].slice(0, 80);
const body = `Converted from room message (${message.id})\n\n${message.content}`;
const params = new URLSearchParams({ title, body });
window.open(`/project/-/issues/new?${params}`, '_blank');
},
},
});
setIsOpen(false);
}}
>
Create Issue
)}
{message.content_type === 'text' && /\n```[\s\S]*?\n```/.test(message.content) && (
{
setIsOpen(false);
toast.info('Creating PR from message…', {
description: 'Open the PR creation form with the code snippet pre-filled.',
action: {
label: 'Open',
onClick: () => {
// TODO: wire to POST /api/repo_pr/{ns}/{repo}/pulls/from-message
const params = new URLSearchParams({
body: `Converted from room message (${message.id})\n\n${message.content}`,
});
window.open(`/pulls/new?${params}`, '_blank');
},
},
});
}}
>
Create PR
)}
{message.content_type === 'text' && (
{
setIsOpen(false);
toast.info('Adding to board…', {
description: 'Open the kanban board with this message as the card description.',
action: {
label: 'Open Board',
onClick: () => {
// TODO: wire to POST /api/board/cards
const params = new URLSearchParams({
title: message.content.split('\n')[0].slice(0, 80),
description: message.content,
});
window.open(`/boards/new?${params}`, '_blank');
},
},
});
}}
>
Add to Board
)}
{isOwner && onEdit && (
<>
{
onEdit();
setIsOpen(false);
}}
>
Edit
>
)}
{isOwner && onRevoke && (
<>
{
onRevoke();
setIsOpen(false);
}}
>
Delete
>
)}
);
}