feat(tradein/ui): вход в поддержку с сайта — плавающая кнопка + живая «Помощь» (#2527)
All checks were successful
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / test (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 2m13s
Deploy Trade-In / deploy (push) Successful in 56s

This commit is contained in:
lekss361 2026-07-16 19:11:57 +00:00
commit d56103219a
3 changed files with 141 additions and 7 deletions

View file

@ -1,6 +1,7 @@
import type { Metadata } from "next";
import { IBM_Plex_Mono, Manrope } from "next/font/google";
import { SupportButton } from "@/components/trade-in/v2/SupportButton";
import { pageBg } from "@/components/trade-in/v2/tokens";
// Manrope — primary sans typeface of the МЕРА HUD. next/font is bundled
@ -41,6 +42,10 @@ export default function TradeInV2Layout({
}}
>
{children}
{/* Mounted here (not the global app/layout.tsx) that layout also
covers the admin `/scrapers/**` area and `/sale-share`, unrelated
products without the МЕРА brand that don't need a support link. */}
<SupportButton />
</div>
);
}

View file

@ -0,0 +1,116 @@
"use client";
// SupportButton — floating link to the МЕРА Telegram support bot.
//
// WHY: before this, there was no way to reach support from the site.
// `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 has no contact path at all. The only other link anywhere in the
// UI is NoAccessScreen.tsx:84, and that only renders on the "access expired"
// screen. The support bot itself is already live on the backend (support
// bridge, PR #2526) — this button is purely the missing entry point on the
// site side. Plain external link, no chat widget / no state / no API calls.
//
// 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 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";
// Same bot as the backend support bridge (PR #2526). A plain module constant
// rather than `NEXT_PUBLIC_*`: the URL is public and stable, and env vars are
// inlined at build time — swapping the value would need a rebuild for no
// benefit over just editing this line.
//
// Exported: this is the single source of truth for the bot URL. The «Помощь»
// item in TopNav.tsx's user menu links to the same bot and imports this
// constant rather than duplicating the string.
export const SUPPORT_BOT_URL = "https://t.me/MERAsupport_bot";
const { accent, accentDeep, onAccent, surface, font } = 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), []);
if (!mounted) return null;
return createPortal(
<>
<style>{styles}</style>
<a
href={SUPPORT_BOT_URL}
target="_blank"
rel="noopener noreferrer"
className="support-btn"
aria-label="Написать в поддержку МЕРА в Telegram"
title="Поддержка МЕРА в Telegram"
style={{
position: "fixed",
right: 20,
bottom: 20,
// z-index 25 — deliberately BELOW every v2 HUD overlay: SectionOverlay
// (29/30), LocationDrawer (40/41), TopNav (50), UserMenu (200), and
// the legacy MapPicker dialog (1000). When a modal/drawer is open the
// button should disappear under it, not float on top and steal
// clicks/tab order. It never collides with TopNav geographically
// (top of screen vs. bottom-right corner here), so this only matters
// for the bottom-sheet-style overlays.
zIndex: 25,
display: "flex",
alignItems: "center",
gap: 8,
height: 44,
minWidth: 44,
boxSizing: "border-box",
padding: "0 16px",
borderRadius: 999,
border: `1px solid ${surface.w40}`,
boxShadow: "0 4px 14px rgba(13,111,214,.3)",
color: onAccent,
fontFamily: font.sans,
fontSize: 12,
fontWeight: 600,
letterSpacing: 0.4,
textDecoration: "none",
whiteSpace: "nowrap",
cursor: "pointer",
}}
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
aria-hidden="true"
focusable="false"
>
<path
d="M21.05 3.16 2.98 10.42c-1.24.51-1.23 1.22-.23 1.53l4.63 1.45 1.79 5.5c.22.6.37.84.75.84.35 0 .5-.16.7-.35l1.7-1.65 3.63 2.68c.67.37 1.15.18 1.32-.62l2.39-11.27c.26-1.03-.39-1.5-1.61-.87z"
fill={onAccent}
/>
</svg>
<span>Поддержка</span>
</a>
</>,
document.body,
);
}

View file

@ -12,6 +12,7 @@ import type { CSSProperties } from "react";
import { tokens } from "./tokens";
import { navLabels, version } from "./fixtures";
import { SUPPORT_BOT_URL } from "./SupportButton";
// Real logged-in user identity, derived by the page from useMe()
// ({username, role, brand}). Deliberately excludes `reports` — the «Мои
@ -56,8 +57,10 @@ const menuItemStyle: CSSProperties = {
transition: "background .12s",
};
// Профиль / Настройки / Помощь have no pages yet — render them dimmed and
// Профиль / Настройки have no pages yet — render them dimmed and
// non-interactive (no hover class, default cursor) so they read as disabled.
// «Помощь» used to be in this group too, but now links out to the Telegram
// support bot (see SUPPORT_BOT_URL below) — it renders as a live item.
const menuItemDisabledStyle: CSSProperties = {
...menuItemStyle,
cursor: "default",
@ -374,7 +377,7 @@ export default function TopNav({
{/* L5 `title` gives the dimmed/non-interactive item a hover
tooltip explaining WHY it's disabled, instead of a silent dead
row (mirrored on Настройки/Помощь below). */}
row (mirrored on Настройки below). */}
<div
style={menuItemDisabledStyle}
aria-disabled="true"
@ -443,10 +446,20 @@ export default function TopNav({
Настройки
</div>
<div
style={menuItemDisabledStyle}
aria-disabled="true"
title="Раздел «Помощь» скоро появится"
{/* Live item (issue: two support entry points the other is
SupportButton.tsx's floating button). Same bot, imported
constant single source of truth for the URL. Styled like
the other live row above ("Мои отчёты"), not the dimmed
Профиль/Настройки rows. */}
<a
href={SUPPORT_BOT_URL}
target="_blank"
rel="noopener noreferrer"
className="tnav-menuitem"
style={{ ...menuItemStyle, textDecoration: "none" }}
aria-label="Написать в поддержку МЕРА в Telegram"
title="Написать в поддержку МЕРА в Telegram"
onClick={() => setUserOpen(false)}
>
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" aria-hidden="true">
<circle
@ -463,7 +476,7 @@ export default function TopNav({
/>
</svg>
Помощь
</div>
</a>
<div
style={{