feat(mera-public): публичная страница /business вместо ссылки на форму входа
Кнопка «Для бизнеса» в шапке и «МЕРА для бизнеса» в подвале вели на B2B_URL. Реальная цепочка на проде: 308 → /trade-in → 307 → /trade-in/v2 → клиентский редирект → /trade-in/login. Посетитель публичного сайта упирался в форму «Логин / Пароль» от аккаунта, которого у него нет. Заведена страница /business: кому полезен бизнес-контур, чем он отличается от бесплатной проверки, и как связаться (почта и телеграм — те же константы, что в подвале и в документах, не выдуманные для страницы). Ни цен, ни сроков, ни объёмов: этих величин никто не мерил. Ссылка на вход оставлена внутри страницы второстепенной — «уже работаете с нами». Периметр согласован во всех четырёх местах: PUBLIC_ROUTES, три матчера Caddy (@meraPages, @meraShortSlash, @meraLongPages), PUBLIC_SHORT_PATHS в RouteGuard, плюс строка в smoke-mera-perimeter.sh. Гейт периметра проверен фальсификацией: удаление /business из @meraShortSlash роняет public-perimeter.test.ts. noindex — тем же способом, что у /docs (metadata.robots). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
189ae63e53
commit
4d51d19f7c
8 changed files with 188 additions and 16 deletions
|
|
@ -347,7 +347,7 @@ meraocenka.ru {
|
|||
# `trailingSlash: false` ответил бы на такой путь 308-редиректом на вариант
|
||||
# без слэша — то есть на ДЛИННЫЙ адрес, который handle ниже отправит 301 на
|
||||
# «/», и запрос закольцуется.
|
||||
@meraPages path /estimate /oferta /refund /privacy /articles /articles/kak-ocenit-kvartiru /docs
|
||||
@meraPages path /estimate /oferta /refund /privacy /articles /articles/kak-ocenit-kvartiru /docs /business
|
||||
handle @meraPages {
|
||||
rewrite * /trade-in/mera-public{path}
|
||||
reverse_proxy tradein-frontend:3000 {
|
||||
|
|
@ -361,7 +361,7 @@ meraocenka.ru {
|
|||
# голый 404 (так было и до этого PR, с момента #2615). Заодно это
|
||||
# замыкает цепочку для длинных адресов со слэшем: они приходят на короткий
|
||||
# со слэшем и здесь нормализуются.
|
||||
@meraShortSlash path_regexp shortslash ^/(estimate|oferta|refund|privacy|articles|articles/kak-ocenit-kvartiru|docs)/$
|
||||
@meraShortSlash path_regexp shortslash ^/(estimate|oferta|refund|privacy|articles|articles/kak-ocenit-kvartiru|docs|business)/$
|
||||
handle @meraShortSlash {
|
||||
redir * /{re.shortslash.1} permanent
|
||||
}
|
||||
|
|
@ -446,7 +446,7 @@ meraocenka.ru {
|
|||
# порядок директив внутри `handle` определяет Caddy, и без неё `redir`
|
||||
# выполняется РАНЬШЕ `uri`, отдавая Location, равный исходному адресу
|
||||
# (бесконечный цикл; поймано на локальном стенде).
|
||||
@meraLongPages path /trade-in/mera-public/estimate /trade-in/mera-public/estimate/ /trade-in/mera-public/oferta /trade-in/mera-public/oferta/ /trade-in/mera-public/refund /trade-in/mera-public/refund/ /trade-in/mera-public/privacy /trade-in/mera-public/privacy/ /trade-in/mera-public/articles /trade-in/mera-public/articles/ /trade-in/mera-public/articles/kak-ocenit-kvartiru /trade-in/mera-public/articles/kak-ocenit-kvartiru/ /trade-in/mera-public/docs /trade-in/mera-public/docs/
|
||||
@meraLongPages path /trade-in/mera-public/estimate /trade-in/mera-public/estimate/ /trade-in/mera-public/oferta /trade-in/mera-public/oferta/ /trade-in/mera-public/refund /trade-in/mera-public/refund/ /trade-in/mera-public/privacy /trade-in/mera-public/privacy/ /trade-in/mera-public/articles /trade-in/mera-public/articles/ /trade-in/mera-public/articles/kak-ocenit-kvartiru /trade-in/mera-public/articles/kak-ocenit-kvartiru/ /trade-in/mera-public/docs /trade-in/mera-public/docs/ /trade-in/mera-public/business /trade-in/mera-public/business/
|
||||
handle @meraLongPages {
|
||||
route {
|
||||
uri strip_prefix /trade-in/mera-public
|
||||
|
|
|
|||
|
|
@ -90,6 +90,11 @@ check "meraocenka.ru/privacy — public 200" "$BASE_MERA/privacy" 200
|
|||
# лэндинга. Отвалится handle — кнопки «Проверить» станут ссылками в 404.
|
||||
check "meraocenka.ru/estimate — public 200" "$BASE_MERA/estimate" 200
|
||||
|
||||
# 1d2. Страница «МЕРА для бизнеса» — короткий адрес, на который ведут пункт
|
||||
# шапки и подвала. До 31.08.2026 они вели в закрытый контур и приводили
|
||||
# человека на форму входа; отвалится handle — вернётся 404 вместо неё.
|
||||
check "meraocenka.ru/business — public 200" "$BASE_MERA/business" 200
|
||||
|
||||
# 1e. Длинные адреса поддерева отдают 301 на короткие: у страницы один
|
||||
# канонический адрес, а старые ссылки и закладки продолжают работать.
|
||||
# ГОЛЫЙ /trade-in/mera-public — регресс на баг 15.08.2026: прежний матчер
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { B2B_URL } from "../content";
|
||||
import styles from "../landing-v3.module.css";
|
||||
import { PublicLink } from "./PublicLink";
|
||||
|
||||
|
|
@ -57,9 +56,9 @@ export function InnerHeader() {
|
|||
</PublicLink>
|
||||
|
||||
<div className={styles.hdrRight}>
|
||||
<a href={B2B_URL} className={styles.hdrBizLink}>
|
||||
<PublicLink to="business" className={styles.hdrBizLink}>
|
||||
Для бизнеса
|
||||
</a>
|
||||
</PublicLink>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
|
|
|||
|
|
@ -21,16 +21,20 @@
|
|||
* а добавляет одну точку входа с тарифами и порядком возврата.
|
||||
*
|
||||
* ССЫЛКИ — КОРОТКИЕ (15.08.2026). «Проверить квартиру» ведёт на `/estimate` —
|
||||
* отдельный экран проверки, а не на якорь формы в герое. «МЕРА для бизнеса»
|
||||
* стала абсолютной: относительный `/` на публичном домене вёл на сам лэндинг,
|
||||
* то есть пункт не работал. Все переходы — обычным `<a>` через `PublicLink`,
|
||||
* потому что `next/link` подставляет basePath (см. его шапку).
|
||||
* отдельный экран проверки, а не на якорь формы в герое. Все переходы —
|
||||
* обычным `<a>` через `PublicLink`, потому что `next/link` подставляет
|
||||
* basePath (см. его шапку).
|
||||
*
|
||||
* «МЕРА ДЛЯ БИЗНЕСА» ВЕДЁТ НА `/business`, А НЕ НА `B2B_URL` (31.08.2026).
|
||||
* Абсолютный адрес B2B-контура заканчивался формой входа `/trade-in/login` —
|
||||
* посетителю публичного сайта предлагали залогиниться в аккаунт, которого у
|
||||
* него нет. Теперь пункт ведёт на публичную страницу связи, а ссылка на вход
|
||||
* живёт внутри неё, второстепенной.
|
||||
*/
|
||||
|
||||
import { safeUrl } from "@/lib/safeUrl";
|
||||
|
||||
import {
|
||||
B2B_URL,
|
||||
LEGAL_ENTITY,
|
||||
REGION_NAME,
|
||||
SUPPORT_EMAIL,
|
||||
|
|
@ -70,9 +74,9 @@ export function FooterV3() {
|
|||
<PublicLink to="articles" className={styles.ftrLink}>
|
||||
Статьи
|
||||
</PublicLink>
|
||||
<a href={B2B_URL} className={styles.ftrLink}>
|
||||
<PublicLink to="business" className={styles.ftrLink}>
|
||||
МЕРА для бизнеса
|
||||
</a>
|
||||
</PublicLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@
|
|||
* был `CityPicker`).
|
||||
*/
|
||||
|
||||
import { B2B_URL } from "../../content";
|
||||
import styles from "../../landing-v3.module.css";
|
||||
import { PublicLink } from "../PublicLink";
|
||||
|
||||
|
|
@ -87,9 +86,9 @@ export function HeaderV3() {
|
|||
</nav>
|
||||
|
||||
<div className={styles.hdrRight}>
|
||||
<a href={B2B_URL} className={styles.hdrBizLink}>
|
||||
<PublicLink to="business" className={styles.hdrBizLink}>
|
||||
Для бизнеса
|
||||
</a>
|
||||
</PublicLink>
|
||||
<a href="#check" className={styles.hdrCta}>
|
||||
Проверить квартиру
|
||||
</a>
|
||||
|
|
|
|||
154
tradein-mvp/frontend/src/app/mera-public/business/page.tsx
Normal file
154
tradein-mvp/frontend/src/app/mera-public/business/page.tsx
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
import type { Metadata } from "next";
|
||||
|
||||
import { safeUrl } from "@/lib/safeUrl";
|
||||
|
||||
import { InnerHeader } from "../_components/InnerHeader";
|
||||
import { PublicLink } from "../_components/PublicLink";
|
||||
import { FooterV3 } from "../_components/v3/FooterV3";
|
||||
import {
|
||||
B2B_URL,
|
||||
SUPPORT_EMAIL,
|
||||
SUPPORT_TELEGRAM_LABEL,
|
||||
SUPPORT_TELEGRAM_URL,
|
||||
} from "../content";
|
||||
import styles from "../landing-v3.module.css";
|
||||
|
||||
/**
|
||||
* «МЕРА для бизнеса» — страница связи, а не вход.
|
||||
*
|
||||
* ЗАЧЕМ ЗАВЕДЕНА. Кнопка «Для бизнеса» в шапке и «МЕРА для бизнеса» в подвале
|
||||
* вели на `B2B_URL`. Реальная цепочка на проде: 308 → `/trade-in` → 307 →
|
||||
* `/trade-in/v2` → клиентский редирект → `/trade-in/login`. Посетитель
|
||||
* публичного сайта упирался в форму «Логин / Пароль» от аккаунта, которого у
|
||||
* него нет и завести который на этом экране нельзя. Владелец просил починить
|
||||
* это дважды.
|
||||
*
|
||||
* ЧЕГО ЗДЕСЬ НЕТ И ПОЧЕМУ. Ни цен, ни сроков ответа, ни объёмов, ни числа
|
||||
* клиентов: таких величин никто не мерил, а на странице они читались бы как
|
||||
* обязательство (то же решение, что в `docs/page.tsx` про часы поддержки).
|
||||
* Формы заявки тоже нет — принимать её в продукте нечем: анонимной ручки для
|
||||
* B2B-лида не существует, и форма, которая никуда не пишет, хуже честной
|
||||
* ссылки на живой канал. Каналы взяты из `content.ts` — те же, что в подвале и
|
||||
* в документах, а не заведены для этой страницы отдельно.
|
||||
*
|
||||
* ССЫЛКА НА ВХОД ОСТАВЛЕНА, но второстепенной: у кого доступ уже есть, тот
|
||||
* пришёл именно за ней, и убрать её значило бы чинить один дефект вторым.
|
||||
*/
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "МЕРА для бизнеса — связаться",
|
||||
// Тот же noindex, что у остального публичного дерева (см. layout.tsx и
|
||||
// docs/page.tsx): страница открыта по адресу, но в выдачу не идёт.
|
||||
robots: { index: false, follow: false },
|
||||
};
|
||||
|
||||
export default function BusinessPage() {
|
||||
const telegramHref = safeUrl(SUPPORT_TELEGRAM_URL);
|
||||
|
||||
return (
|
||||
<>
|
||||
<InnerHeader />
|
||||
<main id="content" className={styles.artPage} tabIndex={-1}>
|
||||
<nav className={styles.artCrumbs} aria-label="Хлебные крошки">
|
||||
<PublicLink to="home" className={styles.artCrumbLink}>
|
||||
Главная
|
||||
</PublicLink>
|
||||
<span aria-hidden="true">/</span>
|
||||
<span aria-current="page">Для бизнеса</span>
|
||||
</nav>
|
||||
|
||||
<header className={styles.artPageHead}>
|
||||
<div className={styles.repEyebrow}>ДЛЯ БИЗНЕСА</div>
|
||||
<h1 className={styles.artPageTitle}>МЕРА для бизнеса</h1>
|
||||
<p className={styles.artPageLead}>
|
||||
Оценка квартиры по живым объявлениям — не для одной сделки, а
|
||||
потоком: для агентства, застройщика или компании, которая принимает
|
||||
квартиры в зачёт.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section className={styles.docsSection} aria-labelledby="biz-who">
|
||||
<h2 id="biz-who" className={styles.docsH2}>
|
||||
Кому это нужно
|
||||
</h2>
|
||||
<p className={styles.artP}>
|
||||
Тем, кто считает не одну квартиру, а много и регулярно: агентствам
|
||||
недвижимости, отделам продаж застройщиков, компаниям с программами
|
||||
трейд-ин. Расчёт идёт по тем же источникам, что и бесплатная
|
||||
проверка на сайте, — но по вашему потоку адресов и под вашими
|
||||
учётными записями.
|
||||
</p>
|
||||
<p className={styles.artP}>
|
||||
Если задача разовая — квартира одна, и нужно понять цену, — идти
|
||||
сюда незачем: бесплатная проверка на сайте отвечает на этот вопрос
|
||||
без договорённостей и без регистрации.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.docsSection} aria-labelledby="biz-diff">
|
||||
<h2 id="biz-diff" className={styles.docsH2}>
|
||||
Чем отличается от бесплатной проверки
|
||||
</h2>
|
||||
<div className={styles.docsLinks}>
|
||||
<PublicLink to="estimate" className={styles.docsLinkCard}>
|
||||
<span className={styles.docsLinkTitle}>Бесплатная проверка</span>
|
||||
<span className={styles.docsLinkNote}>
|
||||
Одна квартира, здесь и сейчас, без регистрации. Картина рынка
|
||||
вокруг дома: сколько похожих квартир продаётся рядом и как давно
|
||||
висят объявления
|
||||
</span>
|
||||
</PublicLink>
|
||||
<span className={styles.docsLinkCard}>
|
||||
<span className={styles.docsLinkTitle}>Бизнес-контур</span>
|
||||
<span className={styles.docsLinkNote}>
|
||||
Закрытый контур с доступом по учётной записи: расчёты по потоку
|
||||
адресов и история по ним. Доступ выдаётся вручную, после
|
||||
разговора, — самостоятельной регистрации в нём нет
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<p className={styles.artP}>
|
||||
Что именно войдёт в работу — обсуждается: задача разная у
|
||||
агентства, у застройщика и у трейд-ин программы. Поэтому на этой
|
||||
странице нет ни тарифа, ни срока: назвать их до разговора значило бы
|
||||
их придумать.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.docsSection} aria-labelledby="biz-contact">
|
||||
<h2 id="biz-contact" className={styles.docsH2}>
|
||||
Как связаться
|
||||
</h2>
|
||||
<p className={styles.artP}>
|
||||
Напишите в любой из каналов — коротко, что за компания и какая
|
||||
задача: сколько адресов, как часто, что должно быть на выходе.
|
||||
Этого хватает, чтобы ответить по существу с первого письма.
|
||||
</p>
|
||||
<div className={styles.artKv}>
|
||||
<div className={styles.artKvRow}>
|
||||
<span className={styles.artKvKey}>ПОЧТА</span>
|
||||
<span className={styles.artKvVal}>
|
||||
<a href={`mailto:${SUPPORT_EMAIL}`}>{SUPPORT_EMAIL}</a>
|
||||
</span>
|
||||
</div>
|
||||
{telegramHref ? (
|
||||
<div className={styles.artKvRow}>
|
||||
<span className={styles.artKvKey}>TELEGRAM</span>
|
||||
<span className={styles.artKvVal}>
|
||||
<a href={telegramHref} target="_blank" rel="noreferrer">
|
||||
{SUPPORT_TELEGRAM_LABEL}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<p className={styles.artFootnote}>
|
||||
Уже работаете с нами? <a href={B2B_URL}>Вход в бизнес-контур</a> —
|
||||
по логину и паролю, которые вам выдали.
|
||||
</p>
|
||||
</section>
|
||||
</main>
|
||||
<FooterV3 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -195,6 +195,10 @@ export const PUBLIC_ROUTES = {
|
|||
articles: "/articles",
|
||||
articleHowTo: "/articles/kak-ocenit-kvartiru",
|
||||
docs: "/docs",
|
||||
// Страница B2B-контура: связаться и оставить заявку. Заведена 31.08.2026
|
||||
// взамен ссылки на `B2B_URL` в шапке и подвале — та вела в закрытый контур
|
||||
// и заканчивалась формой входа для аккаунта, которого у посетителя нет.
|
||||
business: "/business",
|
||||
} as const;
|
||||
|
||||
/** Канонический публичный origin сервиса — база для canonical-ссылок. */
|
||||
|
|
@ -209,6 +213,12 @@ export const PUBLIC_ORIGIN = "https://meraocenka.ru";
|
|||
* осознанно: это ссылка НАРУЖУ, из одного продукта в другой, и подставлять её
|
||||
* из текущего хоста было бы неверно.
|
||||
*/
|
||||
/**
|
||||
* ⚠️ ЭТО ВХОД В ЗАКРЫТЫЙ КОНТУР, А НЕ ВИТРИНА. Цепочка редиректов приводит на
|
||||
* `/trade-in/login` — форму «Логин / Пароль». Человеку с улицы показывать её
|
||||
* нельзя, поэтому шапка и подвал ведут на `PUBLIC_ROUTES.business`, а этот
|
||||
* адрес остался ссылкой «у меня уже есть доступ» внутри той страницы.
|
||||
*/
|
||||
export const B2B_URL = "https://gendsgn.ru/trade-in/";
|
||||
|
||||
/** Короткие публичные адреса тех же страниц (то, что напечатано в документах). */
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ const PUBLIC_SHORT_PATHS = [
|
|||
"/articles",
|
||||
"/articles/kak-ocenit-kvartiru",
|
||||
"/docs",
|
||||
"/business",
|
||||
];
|
||||
|
||||
export function isPublicPath(rawPath: string): boolean {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue