feat: integrate compose/reply dialog into UI

Add compose and reply actions to the user interface:

## User Menu Integration

- Add "Compose Note" menu item to user menu (when logged in)
- Opens ComposeDialog for creating new kind 1 notes
- Positioned prominently after user profile section

## Event Menu Integration

- Add "Reply" menu item to EventMenu dropdown (when logged in)
- Opens ComposeDialog with replyTo context
- Automatically handles NIP-10/NIP-22 threading
- Available on all event renderers via BaseEventRenderer

## Changes

- src/components/nostr/user-menu.tsx:
  - Import ComposeDialog and PenSquare icon
  - Add showCompose state
  - Add "Compose Note" menu item

- src/components/nostr/kinds/BaseEventRenderer.tsx:
  - Import ComposeDialog, Reply icon, and account manager
  - Add showReply state to EventMenu
  - Add conditional "Reply" menu item (only when account exists)
  - Render ComposeDialog with replyTo event

Users can now compose notes and reply to events directly from the UI!
This commit is contained in:
Claude
2026-01-12 15:02:06 +00:00
parent 337bc65756
commit d6aa39b818
2 changed files with 35 additions and 2 deletions

View File

@@ -10,7 +10,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Menu, Copy, Check, FileJson, ExternalLink } from "lucide-react";
import { Menu, Copy, Check, FileJson, ExternalLink, Reply } from "lucide-react";
import { useGrimoire } from "@/core/state";
import { useCopy } from "@/hooks/useCopy";
import { JsonViewer } from "@/components/JsonViewer";
@@ -21,6 +21,9 @@ import { getSeenRelays } from "applesauce-core/helpers/relays";
import { EventFooter } from "@/components/EventFooter";
import { cn } from "@/lib/utils";
import { isAddressableKind } from "@/lib/nostr-kinds";
import { ComposeDialog } from "@/components/compose";
import { use$ } from "applesauce-react/hooks";
import accountManager from "@/services/accounts";
/**
* Universal event properties and utilities shared across all kind renderers
@@ -101,9 +104,11 @@ function ReplyPreview({
* Event menu - universal actions for any event
*/
export function EventMenu({ event }: { event: NostrEvent }) {
const account = use$(accountManager.active$);
const { addWindow } = useGrimoire();
const { copy, copied } = useCopy();
const [jsonDialogOpen, setJsonDialogOpen] = useState(false);
const [showReply, setShowReply] = useState(false);
const openEventDetail = () => {
let pointer;
@@ -181,6 +186,12 @@ export function EventMenu({ event }: { event: NostrEvent }) {
<ExternalLink className="size-4 mr-2" />
Open
</DropdownMenuItem>
{account && (
<DropdownMenuItem onClick={() => setShowReply(true)}>
<Reply className="size-4 mr-2" />
Reply
</DropdownMenuItem>
)}
<DropdownMenuSeparator />
<DropdownMenuItem onClick={copyEventId}>
{copied ? (
@@ -201,6 +212,12 @@ export function EventMenu({ event }: { event: NostrEvent }) {
onOpenChange={setJsonDialogOpen}
title={`Event ${event.id.slice(0, 8)}... - Raw JSON`}
/>
<ComposeDialog
open={showReply}
onOpenChange={setShowReply}
replyTo={event}
kind={1}
/>
</DropdownMenu>
);
}

View File

@@ -1,4 +1,4 @@
import { User } from "lucide-react";
import { User, PenSquare } from "lucide-react";
import accounts from "@/services/accounts";
import { useProfile } from "@/hooks/useProfile";
import { use$ } from "applesauce-react/hooks";
@@ -19,6 +19,7 @@ import Nip05 from "./nip05";
import { RelayLink } from "./RelayLink";
import SettingsDialog from "@/components/SettingsDialog";
import LoginDialog from "./LoginDialog";
import { ComposeDialog } from "@/components/compose";
import { useState } from "react";
function UserAvatar({ pubkey }: { pubkey: string }) {
@@ -56,6 +57,7 @@ export default function UserMenu() {
const relays = state.activeAccount?.relays;
const [showSettings, setShowSettings] = useState(false);
const [showLogin, setShowLogin] = useState(false);
const [showCompose, setShowCompose] = useState(false);
function openProfile() {
if (!account?.pubkey) return;
@@ -75,6 +77,11 @@ export default function UserMenu() {
<>
<SettingsDialog open={showSettings} onOpenChange={setShowSettings} />
<LoginDialog open={showLogin} onOpenChange={setShowLogin} />
<ComposeDialog
open={showCompose}
onOpenChange={setShowCompose}
kind={1}
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
@@ -101,6 +108,15 @@ export default function UserMenu() {
</DropdownMenuLabel>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => setShowCompose(true)}
className="cursor-pointer"
>
<PenSquare className="mr-2 size-4" />
Compose Note
</DropdownMenuItem>
{relays && relays.length > 0 && (
<>
<DropdownMenuSeparator />