feat(mera/b2c): результат проверки в облике отчёта (макет 29.08 «МЕРА отчёт») #3221
4 changed files with 558 additions and 42 deletions
|
|
@ -37,6 +37,8 @@ import {
|
|||
import type { AddressSuggestion } from "../../public-api";
|
||||
import styles from "../../landing-v3.module.css";
|
||||
|
||||
import { ReportResult } from "./ReportResult";
|
||||
|
||||
/** Задержка перед запросом подсказок. Каждый вызов платный (DaData-тир). */
|
||||
const SUGGEST_DEBOUNCE_MS = 300;
|
||||
const SUGGEST_MIN_CHARS = 3;
|
||||
|
|
@ -442,49 +444,20 @@ export function EstimateFlow() {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{phase.kind === "result" && <CoverageResult verdict={phase.verdict} />}
|
||||
{phase.kind === "result" && (
|
||||
<ReportResult
|
||||
verdict={phase.verdict}
|
||||
object={{
|
||||
address: query,
|
||||
roomsLabel:
|
||||
ROOM_OPTIONS.find((o) => String(o.value) === rooms)?.label ?? null,
|
||||
area: area || null,
|
||||
floor: null,
|
||||
}}
|
||||
onEdit={() => setPhase(FORM)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CoverageResult({ verdict }: { verdict: CoverageVerdict }) {
|
||||
return (
|
||||
<div className={styles.estResult}>
|
||||
<p className={styles.heroResultEyebrow}>
|
||||
{verdict.tone === "none" ? "ПРОВЕРИЛИ" : "ГОТОВО · БЕСПЛАТНАЯ ЧАСТЬ"}
|
||||
</p>
|
||||
<p className={styles.heroFormFeedbackTitle}>{verdict.title}</p>
|
||||
|
||||
{verdict.tiles.length > 0 && (
|
||||
<div className={styles.heroResultGrid}>
|
||||
{verdict.tiles.map((tile) => (
|
||||
<div key={tile.label} className={styles.heroResultTile}>
|
||||
<div className={styles.heroResultTileValue}>{tile.value}</div>
|
||||
<div className={styles.heroResultTileLabel}>{tile.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{verdict.lines.map((line) => (
|
||||
<p key={line} className={styles.heroFormFeedbackText}>
|
||||
{line}
|
||||
</p>
|
||||
))}
|
||||
|
||||
{verdict.offerPaidStep && (
|
||||
<div className={styles.estPaidStep}>
|
||||
<p className={styles.heroResultOverlayTitle}>
|
||||
Цена и срок продажи — следующий шаг
|
||||
</p>
|
||||
<p className={styles.heroFormFeedbackText}>
|
||||
Полный расчёт стоит 150 ₽: диапазон цены, цена за квадратный
|
||||
метр и список квартир, на которых он построен. Приём оплаты сейчас
|
||||
подключается — как только заработает, кнопка появится здесь.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,214 @@
|
|||
/**
|
||||
* ReportResult — результат пробы покрытия в облике «отчёта» (макет 29.08,
|
||||
* «МЕРА отчёт.dc.html»): шапка объекта с чипами параметров, бесплатные
|
||||
* карточки, тизер платной части, честные отказы «город не покрыт» и
|
||||
* «мало данных».
|
||||
*
|
||||
* ЧЕСТНЫЕ АДАПТАЦИИ МАКЕТА (каждая — сознательное расхождение):
|
||||
*
|
||||
* 1. Макетная плашка «✓ РАСЧЁТ ПО ЭТОЙ КВАРТИРЕ ГОТОВ» и «✓ рассчитана» у
|
||||
* shimmer-строк НЕ воспроизводятся: платного расчёта для анонима не
|
||||
* существует (анонимный /estimate закрыт, платёжного контура нет — #2896).
|
||||
* Утверждать «готов и ждёт вас» было бы ложью. Вместо этого тизер честно
|
||||
* называется «ЧТО БУДЕТ В ПОЛНОМ ОТЧЁТЕ», а вместо кнопки оплаты — та же
|
||||
* формулировка, что жила здесь раньше: «приём оплаты подключается».
|
||||
* 2. Бейджи МИР/СБП/VISA из макета не рендерятся до платёжного контура —
|
||||
* ровно тот же TODO, что в FooterV3.
|
||||
* 3. Email-подписки «сообщим, когда добавим город/данные» из макета нет:
|
||||
* для неё нужны хранение и согласие (#2895), которых нет. Вместо неё —
|
||||
* живой канал поддержки (SUPPORT_TELEGRAM_URL).
|
||||
* 4. Кнопка «Расширить радиус до 3 км» не рендерится: `/coverage` радиус не
|
||||
* параметризует, а обещать перепроверку, которой не будет, нельзя.
|
||||
* 5. Подписи бесплатных плиток — из coverage-copy (запинены тестом): там
|
||||
* «возраст активного объявления», а НЕ макетное «срок продажи по дому» —
|
||||
* выборка цензурирована, честное имя величины важнее красоты.
|
||||
*
|
||||
* Возврат 150 ₽ при ненадёжном расчёте — политика с /refund; в тизере она
|
||||
* упоминается той же строкой, что в макете, со ссылкой на полный текст.
|
||||
*/
|
||||
|
||||
import { SUPPORT_TELEGRAM_LABEL, SUPPORT_TELEGRAM_URL } from "../../content";
|
||||
import type { CoverageVerdict } from "../../coverage-copy";
|
||||
import styles from "../../landing-v3.module.css";
|
||||
|
||||
import { PublicLink } from "../PublicLink";
|
||||
|
||||
export interface ReportObjectParams {
|
||||
readonly address: string;
|
||||
readonly roomsLabel: string | null;
|
||||
readonly area: string | null;
|
||||
readonly floor: string | null;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
readonly verdict: CoverageVerdict;
|
||||
readonly object: ReportObjectParams;
|
||||
readonly onEdit: () => void;
|
||||
}
|
||||
|
||||
function ObjectHeader({ object, onEdit }: { object: ReportObjectParams; onEdit: () => void }) {
|
||||
const chips = [
|
||||
object.roomsLabel,
|
||||
object.area ? `${object.area} м²` : null,
|
||||
object.floor ? `Этаж ${object.floor}` : null,
|
||||
].filter((c): c is string => c !== null);
|
||||
return (
|
||||
<header className={styles.repHead}>
|
||||
<div className={styles.repHeadTop}>
|
||||
<div>
|
||||
<div className={styles.repEyebrow}>БЕСПЛАТНАЯ ПРОВЕРКА</div>
|
||||
<h2 className={styles.repAddress}>{object.address}</h2>
|
||||
</div>
|
||||
</div>
|
||||
{(
|
||||
<div className={styles.repChipsBar}>
|
||||
<div className={styles.repChips}>
|
||||
{chips.map((c, i) => (
|
||||
<span key={c} className={styles.repChip}>
|
||||
{i > 0 && (
|
||||
<span className={styles.repChipDot} aria-hidden="true">
|
||||
·
|
||||
</span>
|
||||
)}
|
||||
{c}
|
||||
</span>
|
||||
))}
|
||||
{chips.length === 0 && (
|
||||
<span className={styles.repChipMuted}>параметры не указаны</span>
|
||||
)}
|
||||
</div>
|
||||
<button type="button" onClick={onEdit} className={styles.repEditBtn}>
|
||||
Изменить данные →
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
/** Отказ «город не покрыт» / «дом не найден» (tone none). */
|
||||
function RefusalCard({ verdict }: { verdict: CoverageVerdict }) {
|
||||
return (
|
||||
<div className={styles.repRefusal}>
|
||||
<div className={styles.repRefusalEyebrow}>ЧЕСТНЫЙ ОТВЕТ</div>
|
||||
<h3 className={styles.repRefusalTitle}>{verdict.title}</h3>
|
||||
{verdict.lines.map((line) => (
|
||||
<p key={line} className={styles.repRefusalText}>
|
||||
{line}
|
||||
</p>
|
||||
))}
|
||||
<p className={styles.repRefusalText}>
|
||||
Продавать отчёт «на глазок» мы не будем — поэтому и кнопки оплаты здесь
|
||||
нет. Если хотите, чтобы мы сообщили, когда данные появятся, — напишите в
|
||||
поддержку:{" "}
|
||||
<a href={SUPPORT_TELEGRAM_URL} target="_blank" rel="noreferrer">
|
||||
{SUPPORT_TELEGRAM_LABEL}
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** Бесплатные карточки (tone ok/thin) — числа и подписи из verdict.tiles. */
|
||||
function FreeCards({ verdict }: { verdict: CoverageVerdict }) {
|
||||
return (
|
||||
<div className={styles.repFreeGrid}>
|
||||
{verdict.tiles.map((tile) => (
|
||||
<div key={tile.label} className={styles.repFreeCard}>
|
||||
<div className={styles.repFreeEyebrow}>✓ БЕСПЛАТНО</div>
|
||||
<div className={styles.repFreeRow}>
|
||||
<span className={styles.repFreeValue}>{tile.value}</span>
|
||||
<span className={styles.repFreeLabel}>{tile.label}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const PAID_ROWS = [
|
||||
{ label: "РЕКОМЕНДОВАННАЯ ЦЕНА", w: "60%" },
|
||||
{ label: "ПРОГНОЗ СРОКА ПРОДАЖИ ПРИ ЭТОЙ ЦЕНЕ", w: "45%" },
|
||||
{ label: "ДИАПАЗОН И ПОЛОЖЕНИЕ НА РЫНКЕ", w: "70%" },
|
||||
{ label: "АНАЛОГИ С АДРЕСАМИ И ЦЕНАМИ", w: "88%" },
|
||||
] as const;
|
||||
|
||||
/** Тизер платной части (tone ok): shimmer-строки + честная правая панель. */
|
||||
function PaidTeaser() {
|
||||
return (
|
||||
<div className={styles.repPaid}>
|
||||
<div className={styles.repPaidBar}>
|
||||
<span className={styles.repPaidBarText}>ПОЛНЫЙ ОТЧЁТ · 150 ₽</span>
|
||||
</div>
|
||||
<div className={styles.repPaidGrid}>
|
||||
<div className={styles.repPaidLeft}>
|
||||
{PAID_ROWS.map((row) => (
|
||||
<div key={row.label}>
|
||||
<div className={styles.repPaidRowLabel}>{row.label}</div>
|
||||
<div className={styles.repPaidRowLine}>
|
||||
<span
|
||||
className={styles.repShimmer}
|
||||
style={{ width: row.w }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className={styles.repPaidRowChip}>в отчёте</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className={styles.repPaidRight}>
|
||||
<div className={styles.repPaidTitle}>
|
||||
Цена и срок продажи — следующий шаг
|
||||
</div>
|
||||
<p className={styles.repPaidText}>
|
||||
Полный расчёт стоит 150 ₽: диапазон цены, цена за квадратный
|
||||
метр и список квартир, на которых он построен. Один платёж, без
|
||||
подписки. Приём оплаты сейчас подключается — как только заработает,
|
||||
кнопка появится здесь.
|
||||
</p>
|
||||
<p className={styles.repPaidFine}>
|
||||
Если надёжный расчёт не получится — вернём 150 ₽:{" "}
|
||||
<PublicLink to="refund">условия возврата</PublicLink>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ReportResult({ verdict, object, onEdit }: Props) {
|
||||
return (
|
||||
<div className={styles.repRoot}>
|
||||
<ObjectHeader object={object} onEdit={onEdit} />
|
||||
{verdict.tone === "none" ? (
|
||||
<RefusalCard verdict={verdict} />
|
||||
) : (
|
||||
<>
|
||||
{verdict.tiles.length > 0 && <FreeCards verdict={verdict} />}
|
||||
{verdict.lines.length > 0 && (
|
||||
<div className={styles.repNotes}>
|
||||
{verdict.lines.map((line) => (
|
||||
<p key={line}>{line}</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{verdict.tone === "thin" && (
|
||||
<div className={styles.repRefusal}>
|
||||
<div className={styles.repRefusalEyebrow}>ЧЕСТНЫЙ ОТВЕТ</div>
|
||||
<h3 className={styles.repRefusalTitle}>
|
||||
Для платного расчёта данных пока мало
|
||||
</h3>
|
||||
<p className={styles.repRefusalText}>
|
||||
Прогноз на паре точек был бы гаданием, поэтому кнопки оплаты
|
||||
здесь нет. Когда объявлений рядом станет больше, полный отчёт
|
||||
откроется по этому же адресу.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{verdict.offerPaidStep && <PaidTeaser />}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -2164,3 +2164,249 @@
|
|||
font-size: 17.5px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* --- Отчёт (макет 29.08): шапка объекта, бесплатные карточки, тизер ------- */
|
||||
.repRoot {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
.repHead {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
.repHeadTop {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.repEyebrow {
|
||||
margin-bottom: 10px;
|
||||
font-family: var(--b2c-font-mono);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.22em;
|
||||
color: var(--b2c-accent-text);
|
||||
}
|
||||
.repAddress {
|
||||
margin: 0;
|
||||
font-size: clamp(24px, 3.4vw, 34px);
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.02em;
|
||||
font-weight: 800;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.repChipsBar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px 28px;
|
||||
flex-wrap: wrap;
|
||||
padding: 16px 22px;
|
||||
background: var(--b2c-surface);
|
||||
border: 1px solid var(--b2c-line);
|
||||
border-radius: 10px;
|
||||
}
|
||||
.repChips {
|
||||
display: flex;
|
||||
gap: 10px 14px;
|
||||
flex-wrap: wrap;
|
||||
font-size: 14.5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.repChip {
|
||||
display: inline-flex;
|
||||
gap: 14px;
|
||||
}
|
||||
.repChipDot {
|
||||
color: var(--b2c-line);
|
||||
}
|
||||
.repChipMuted {
|
||||
color: var(--b2c-muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
.repEditBtn {
|
||||
margin-left: auto;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--b2c-accent-text);
|
||||
font-family: var(--b2c-font-sans);
|
||||
font-weight: 700;
|
||||
font-size: 13.5px;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
.repEditBtn:hover {
|
||||
color: var(--b2c-ink);
|
||||
}
|
||||
.repRefusal {
|
||||
max-width: 720px;
|
||||
padding: clamp(24px, 4vw, 40px);
|
||||
background: var(--b2c-surface);
|
||||
border: 1px solid var(--b2c-line);
|
||||
border-radius: 14px;
|
||||
}
|
||||
.repRefusalEyebrow {
|
||||
margin-bottom: 14px;
|
||||
font-family: var(--b2c-font-mono);
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.16em;
|
||||
color: var(--b2c-muted);
|
||||
}
|
||||
.repRefusalTitle {
|
||||
margin: 0 0 12px;
|
||||
font-size: clamp(20px, 2.6vw, 27px);
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.015em;
|
||||
font-weight: 800;
|
||||
}
|
||||
.repRefusalText {
|
||||
margin: 0 0 14px;
|
||||
font-size: 15px;
|
||||
line-height: 1.65;
|
||||
color: var(--b2c-muted);
|
||||
text-wrap: pretty;
|
||||
}
|
||||
.repRefusalText:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.repFreeGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
.repFreeCard {
|
||||
padding: clamp(22px, 3vw, 30px);
|
||||
background: var(--b2c-surface);
|
||||
border: 1px solid var(--b2c-line);
|
||||
border-radius: 12px;
|
||||
}
|
||||
.repFreeEyebrow {
|
||||
margin-bottom: 12px;
|
||||
font-family: var(--b2c-font-mono);
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.16em;
|
||||
color: var(--b2c-accent-text);
|
||||
}
|
||||
.repFreeRow {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.repFreeValue {
|
||||
font-size: clamp(32px, 4.4vw, 46px);
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.repFreeLabel {
|
||||
font-size: 14.5px;
|
||||
color: var(--b2c-muted);
|
||||
max-width: 30ch;
|
||||
}
|
||||
.repNotes p {
|
||||
margin: 0 0 8px;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: var(--b2c-muted);
|
||||
}
|
||||
.repNotes p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.repPaid {
|
||||
overflow: hidden;
|
||||
background: var(--b2c-surface);
|
||||
border: 1px solid var(--b2c-line);
|
||||
border-radius: 14px;
|
||||
}
|
||||
.repPaidBar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
padding: 12px 24px;
|
||||
background: var(--b2c-surface-soft);
|
||||
}
|
||||
.repPaidBarText {
|
||||
font-family: var(--b2c-font-mono);
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.16em;
|
||||
color: var(--b2c-accent-text);
|
||||
}
|
||||
.repPaidGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
|
||||
}
|
||||
.repPaidLeft {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
padding: clamp(22px, 3.5vw, 34px);
|
||||
}
|
||||
.repPaidRowLabel {
|
||||
margin-bottom: 8px;
|
||||
font-family: var(--b2c-font-mono);
|
||||
font-size: 9.5px;
|
||||
letter-spacing: 0.14em;
|
||||
color: var(--b2c-muted);
|
||||
}
|
||||
.repPaidRowLine {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.repShimmer {
|
||||
display: inline-block;
|
||||
height: 26px;
|
||||
border-radius: 6px;
|
||||
background: linear-gradient(90deg, #eaf0f1 25%, #f4f7f8 50%, #eaf0f1 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: repShimmer 1.8s linear infinite;
|
||||
}
|
||||
@keyframes repShimmer {
|
||||
from { background-position: 200% 0; }
|
||||
to { background-position: -200% 0; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.repShimmer { animation: none; }
|
||||
}
|
||||
.repPaidRowChip {
|
||||
font-family: var(--b2c-font-mono);
|
||||
font-size: 10.5px;
|
||||
color: var(--b2c-accent-text);
|
||||
white-space: nowrap;
|
||||
border: 1px solid var(--b2c-line);
|
||||
border-radius: 4px;
|
||||
padding: 3px 8px;
|
||||
}
|
||||
.repPaidRight {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 14px;
|
||||
padding: clamp(22px, 3.5vw, 34px);
|
||||
border-left: 1px solid var(--b2c-line-soft);
|
||||
background: var(--b2c-page-bg);
|
||||
}
|
||||
.repPaidTitle {
|
||||
font-weight: 800;
|
||||
font-size: clamp(18px, 2.1vw, 22px);
|
||||
line-height: 1.3;
|
||||
}
|
||||
.repPaidText {
|
||||
margin: 0;
|
||||
font-size: 14.5px;
|
||||
line-height: 1.6;
|
||||
color: var(--b2c-muted);
|
||||
}
|
||||
.repPaidFine {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
color: #9fb0b4;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
"use client";
|
||||
|
||||
/**
|
||||
* Фикстурное превью ReportResult — все три тона вердикта на одной странице.
|
||||
*
|
||||
* Зачем: результат пробы покрытия зависит от живого бэкенда за публичным
|
||||
* периметром Caddy — локально его не воспроизвести, не подделывая секрет
|
||||
* периметра. Превью рендерит компонент на фикстурах (та же конвенция, что
|
||||
* `app/ui-preview/estimate` у B2B-части: NEXT_PUBLIC_ENABLE_PREVIEW=1, иначе
|
||||
* notFound; в прод-сборке страница недостижима и в Caddy-allowlist публичного
|
||||
* домена не входит).
|
||||
*/
|
||||
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
import { ReportResult } from "../../_components/estimate/ReportResult";
|
||||
import type { CoverageVerdict } from "../../coverage-copy";
|
||||
|
||||
const ENABLED = process.env.NEXT_PUBLIC_ENABLE_PREVIEW === "1";
|
||||
|
||||
const OK: CoverageVerdict = {
|
||||
tone: "ok",
|
||||
title: "Данных достаточно",
|
||||
lines: [
|
||||
"Проверили по вашему дому и окружению в радиусе 1 км.",
|
||||
],
|
||||
tiles: [
|
||||
{ value: "14", label: "похожих квартир продаётся в радиусе 1 км" },
|
||||
{ value: "47 дн.", label: "медианный возраст активного объявления" },
|
||||
],
|
||||
offerPaidStep: true,
|
||||
};
|
||||
|
||||
const THIN: CoverageVerdict = {
|
||||
tone: "thin",
|
||||
title: "Похожие квартиры рядом есть, но их мало",
|
||||
lines: ["Нашли 2 похожие квартиры в радиусе 1 км."],
|
||||
tiles: [{ value: "2", label: "похожие квартиры продаются в радиусе 1 км" }],
|
||||
offerPaidStep: false,
|
||||
};
|
||||
|
||||
const NONE: CoverageVerdict = {
|
||||
tone: "none",
|
||||
title: "Ваш город мы пока не оцениваем",
|
||||
lines: [
|
||||
"Чтобы прогноз был честным, нужен объём данных по сделкам и объявлениям, которого в этом городе у нас пока нет.",
|
||||
],
|
||||
tiles: [],
|
||||
offerPaidStep: false,
|
||||
};
|
||||
|
||||
const OBJECT = {
|
||||
address: "Екатеринбург, ул. Инженерная, 14",
|
||||
roomsLabel: "2 комнаты",
|
||||
area: "54,2",
|
||||
floor: null,
|
||||
};
|
||||
|
||||
export default function ReportPreviewPage() {
|
||||
if (!ENABLED) notFound();
|
||||
return (
|
||||
<main
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 48,
|
||||
maxWidth: 960,
|
||||
margin: "0 auto",
|
||||
padding: "32px 20px 96px",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<ReportResult verdict={OK} object={OBJECT} onEdit={() => {}} />
|
||||
</div>
|
||||
<div>
|
||||
<ReportResult verdict={THIN} object={OBJECT} onEdit={() => {}} />
|
||||
</div>
|
||||
<div>
|
||||
<ReportResult verdict={NONE} object={OBJECT} onEdit={() => {}} />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue