"use client"; // SupportButton — floating trigger for the on-site МЕРА support chat. // // WHY (original v1, before this window): before the plain-link version of // this button existed, there was no way to reach support from the site at // all. `LeadForm` (LeadForm.tsx) only mounts once `hasEstimate` is true // (app/v2/page.tsx:1064), so a visitor who hasn't finished (or can't finish) // an estimate had no contact path. This was upgraded from a plain external // Telegram link to an in-page chat window (SupportChatPanel.tsx) so a visitor // answers/receives replies without leaving the site — the message still // mirrors into the same Telegram support topic and the operator still // answers from Telegram exactly as before (backend `feat/tradein-web-chat- // backend`, itself layered on the Telegram support bridge, backend PR #2526). // The Telegram link survives as a small fallback INSIDE the chat panel // (SupportChatPanel.tsx) — deliberate, since there's no push notification for // the web chat: a visitor who leaves the page won't see a reply that arrives // an hour later, so the Telegram escape hatch stays available. // // Portal to document.body (pattern mirrors MapPicker.tsx:104-108 and // BuildingListingsDrawer.tsx): `/v2` renders its HUD inside a fixed // 1536×1024 "artboard" that gets `transform: scale(...)` on narrow viewports // (app/v2/page.tsx:860-871). A `position: fixed` descendant of a // `transform`-ed ancestor is positioned relative to that ancestor, not the // viewport — so without a portal the button (and the chat panel it opens) // would drift/scale with the HUD instead of staying pinned to the real // viewport corner. import { useEffect, useState } from "react"; import { createPortal } from "react-dom"; import { tokens } from "./tokens"; import { useSupportChat } from "./SupportChatContext"; import { SupportChatPanel } from "./SupportChatPanel"; import { useSupportUnread } from "@/lib/useSupportChat"; const { accent, accentDeep, onAccent, surface, font, danger } = tokens; const styles = ` .support-btn{background:linear-gradient(90deg,${accentDeep},${accent});transition:box-shadow .18s,transform .18s,background .18s;} .support-btn:hover{background:${accentDeep};box-shadow:0 6px 20px rgba(13,111,214,.4);} .support-btn:active{transform:translateY(1px);} .support-btn:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(46,139,255,.45);} @media (max-width: 480px){ .support-btn{right:14px !important;bottom:14px !important;padding:0 14px !important;} } `; export function SupportButton() { // Portal-mount guard (SSR-safe): `document` only exists after mount // (mirrors MapPicker.tsx:107-108 / BuildingListingsDrawer.tsx:29-30). const [mounted, setMounted] = useState(false); useEffect(() => setMounted(true), []); const { open, toggleChat, closeChat } = useSupportChat(); // Unread badge only matters while the panel is closed — see // useSupportUnread's docstring for why polling stops entirely once open. const unreadQuery = useSupportUnread(!open); const unread = unreadQuery.data?.unread ?? 0; if (!mounted) return null; return createPortal( <> , document.body, ); }