+ + {/* The passage is the point, so it leads; the reference follows in + the quieter role a citation normally takes. */} + + {passage} + + {attachment.title && ( + + {attachment.title} + + )} + + + + ); +} diff --git a/components/chat/share-to-chat-dialog.tsx b/components/chat/share-to-chat-dialog.tsx new file mode 100644 index 00000000..b3485113 --- /dev/null +++ b/components/chat/share-to-chat-dialog.tsx @@ -0,0 +1,241 @@ +"use client"; + +/** + * Send the selected passage to someone, from the reading toolbar. + * + * Two ways in, because they answer different questions: + * + * Frequent chats — people you already talk to. A grid of avatars, because + * you recognise these faces and want the shortest possible path. + * Friends — people you follow or who follow you but have never messaged. + * Rows with an explicit button, because starting a conversation is a bigger + * step than continuing one and shouldn't be a mis-tap away. + * + * Anyone with an existing thread is removed from Friends, so nobody appears + * twice and the second list is genuinely "people you haven't written to". + */ + +import { useEffect, useState } from "react"; +import { Check, Loader2, Send } from "lucide-react"; +import { toast } from "sonner"; +import { useAuth } from "@/components/auth/auth-provider"; +import { UserAvatar } from "@/components/ui/user-avatar"; +import { Button } from "@/components/ui/button"; +import { ResponsiveDialog } from "@/components/chat/responsive-dialog"; +import { chat, type ChatPeer, type QuotePayload } from "@/lib/chat"; +import { relationships, type UserSummary } from "@/lib/social"; + +/** How many existing threads to offer before it stops being "frequent". */ +const FREQUENT_LIMIT = 9; +/** Friends fetched from each side of the follow graph. */ +const FRIENDS_PER_SIDE = 20; + +type Frequent = { conversationId: string; peer: ChatPeer }; +/** Per-person send state, keyed by handle. */ +type SendState = "idle" | "sending" | "sent"; + +export function ShareToChatDialog({ + body = "", + quote, + onClose, +}: { + /** Plain message text. Empty when sending a quote on its own. */ + body?: string; + /** A passage from the library, sent as a tappable quotation. */ + quote?: QuotePayload; + onClose: () => void; +}) { + const { user } = useAuth(); + const [frequent, setFrequent] = useState
Loading…
+ ) : frequent.length === 0 && friends.length === 0 ? ( ++ No one to send to yet. Follow someone to start a conversation. +
+ ) : ( + <> + {frequent.length > 0 && ( ++ {u.name} +
++ @{u.handle} +
+