?brand=praktika re-skin: useBrand hook (reads ?brand= slug, fetches
/api/v1/brand/{slug}), runtime CSS-var override of accent palette +
Manrope font (open replacement for paid HalvarMittel), client logo in
Topbar, ?brand passthrough on the PDF link. No-op when ?brand= absent.
Migration 084 fills the praktika brand row (#235F49 / #1FCECB, logo, footer).
Closes #657
198 lines
9.1 KiB
TypeScript
198 lines
9.1 KiB
TypeScript
"use client";
|
||
|
||
/**
|
||
* OfferCard — Секция 4 «Оффер» из mockup tradein.html.
|
||
* Сравнение trade-in vs самостоятельная продажа + 4 преимущества + CTA.
|
||
*/
|
||
import type { AggregatedEstimate } from "@/types/trade-in";
|
||
import { API_BASE_URL } from "@/lib/api";
|
||
|
||
interface Props {
|
||
estimate: AggregatedEstimate;
|
||
/** #657 white-label: активный ?brand= slug — прокидывается в PDF endpoint. */
|
||
brandSlug?: string | null;
|
||
}
|
||
|
||
function fmtRub(v: number): string {
|
||
return Math.round(v).toLocaleString("ru-RU");
|
||
}
|
||
|
||
export function OfferCard({ estimate, brandSlug }: Props) {
|
||
// PDF endpoint (api/v1/trade_in.py) уже honor'ит ?brand= для white-label.
|
||
// Без бренда query пустой → стандартный PDF.
|
||
const pdfQuery = brandSlug ? `?brand=${encodeURIComponent(brandSlug)}` : "";
|
||
const basePrice = estimate.median_price_rub;
|
||
const baseMln = basePrice / 1_000_000;
|
||
// Расчёт издержек самостоятельной продажи (от рыночной цены)
|
||
const torgLo = Math.round(basePrice * 0.05);
|
||
const torgHi = Math.round(basePrice * 0.15);
|
||
const realtorLo = Math.round((basePrice - torgHi) * 0.02);
|
||
const realtorHi = Math.round((basePrice - torgLo) * 0.03);
|
||
const totalLo = torgLo + realtorLo + 90_000 + 19_250 + 4_000;
|
||
const totalHi = torgHi + realtorHi + 150_000 + 50_000 + 36_000;
|
||
const totalPct = (((totalLo + totalHi) / 2) / basePrice) * 100;
|
||
|
||
return (
|
||
<article className="card" id="section-offer">
|
||
<div className="card-head">
|
||
<div>
|
||
<div className="section-kicker">Секция 4 · Оффер</div>
|
||
<h2>Формирование выкупной стоимости</h2>
|
||
</div>
|
||
<div className="card-meta">
|
||
<div>
|
||
База расчёта: <b className="mono">{baseMln.toFixed(2)} млн ₽</b>
|
||
</div>
|
||
<div style={{ marginTop: 4 }}>при стоимости квартиры в объявлении</div>
|
||
</div>
|
||
</div>
|
||
|
||
<table className="offer-tbl" aria-label="Сравнение trade-in и самостоятельной продажи">
|
||
<thead>
|
||
<tr>
|
||
<th>Статья расходов</th>
|
||
<th className="col-trade">Через Trade-In</th>
|
||
<th className="col-self">Самостоятельная продажа</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>
|
||
<div className="item-name">Торг покупателей</div>
|
||
<div className="item-sub">5–15% от цены объявления</div>
|
||
</td>
|
||
<td className="col-trade">не применимо</td>
|
||
<td className="col-self">
|
||
{fmtRub(torgLo)} – {fmtRub(torgHi)} ₽
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
<div className="item-name">Услуги риелтора</div>
|
||
<div className="item-sub">2–3% от цены сделки</div>
|
||
</td>
|
||
<td className="col-trade">бесплатно</td>
|
||
<td className="col-self">
|
||
{fmtRub(realtorLo)} – {fmtRub(realtorHi)} ₽
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
<div className="item-name">Аренда жилья после сделки</div>
|
||
<div className="item-sub">1-к квартира на 3 месяца</div>
|
||
</td>
|
||
<td className="col-trade">бесплатно</td>
|
||
<td className="col-self">90 000 – 150 000 ₽</td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
<div className="item-name">Юридическое сопровождение</div>
|
||
<div className="item-sub">проверка, договор, регистрация</div>
|
||
</td>
|
||
<td className="col-trade">бесплатно</td>
|
||
<td className="col-self">от 19 250 ₽</td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
<div className="item-name">Расходы на рекламу</div>
|
||
<div className="item-sub">платные показы Циан / Авито, 3 мес</div>
|
||
</td>
|
||
<td className="col-trade">бесплатно</td>
|
||
<td className="col-self">4 000 – 36 000 ₽</td>
|
||
</tr>
|
||
</tbody>
|
||
<tfoot>
|
||
<tr>
|
||
<td>Общие финансовые потери</td>
|
||
<td className="col-trade">— 0 ₽</td>
|
||
<td className="col-self">
|
||
{fmtRub(totalLo)} – {fmtRub(totalHi)} ₽
|
||
</td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
|
||
<div className="warn-banner">
|
||
<span className="ic">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
|
||
<path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
|
||
<line x1="12" y1="9" x2="12" y2="13" />
|
||
<line x1="12" y1="17" x2="12.01" y2="17" />
|
||
</svg>
|
||
</span>
|
||
<div>
|
||
Издержки при самостоятельной продаже сопоставимы со скидкой <b>{totalPct.toFixed(0)}%</b> от рыночной цены.{" "}
|
||
<b>Trade-In закрывает их полностью</b>, а цена в новостройке фиксируется на момент оценки.
|
||
</div>
|
||
</div>
|
||
|
||
<div className="advantages">
|
||
<div className="advantage">
|
||
<div className="adv-ic">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||
<circle cx="7.5" cy="15.5" r="5.5" />
|
||
<path d="m21 2-9.6 9.6" />
|
||
<path d="m15.5 7.5 3 3L22 7l-3-3" />
|
||
</svg>
|
||
</div>
|
||
<h4>Экономия времени</h4>
|
||
<p>Берём на себя показы, переговоры и поиск покупателей. Менеджеру остаётся только подписать ДКП.</p>
|
||
</div>
|
||
<div className="advantage">
|
||
<div className="adv-ic">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
|
||
<polyline points="9 22 9 12 15 12 15 22" />
|
||
</svg>
|
||
</div>
|
||
<h4>Юридическая безопасность</h4>
|
||
<p>Проверяем чистоту документов, ЕГРН, прописанных и обременения. Исключаем риски.</p>
|
||
</div>
|
||
<div className="advantage">
|
||
<div className="adv-ic">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||
<line x1="19" y1="5" x2="5" y2="19" />
|
||
<circle cx="6.5" cy="6.5" r="2.5" />
|
||
<circle cx="17.5" cy="17.5" r="2.5" />
|
||
</svg>
|
||
</div>
|
||
<h4>Фиксированная цена новостройки</h4>
|
||
<p>Сохраняем планировку в каталоге по цене на момент оценки — до закрытия сделки.</p>
|
||
</div>
|
||
<div className="advantage">
|
||
<div className="adv-ic">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||
<circle cx="12" cy="12" r="10" />
|
||
<circle cx="12" cy="12" r="6" />
|
||
<circle cx="12" cy="12" r="2" />
|
||
</svg>
|
||
</div>
|
||
<h4>Гарантия цены</h4>
|
||
<p>Снимаем риск колебаний рынка — выкупная стоимость зафиксирована в оффере на 30 дней.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="offer-cta">
|
||
<div className="offer-cta-text">
|
||
<b>Готовы отправить оффер клиенту?</b>
|
||
<br />
|
||
PDF-отчёт + персональный оффер по выкупной цене — за 1 клик.
|
||
</div>
|
||
<div className="offer-cta-buttons">
|
||
<a
|
||
href={`${API_BASE_URL}/api/v1/trade-in/estimate/${estimate.estimate_id}/pdf${pdfQuery}`}
|
||
download={`trade-in-${estimate.estimate_id}.pdf`}
|
||
className="btn btn-ghost"
|
||
>
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
||
<polyline points="7 10 12 15 17 10" />
|
||
<line x1="12" y1="15" x2="12" y2="3" />
|
||
</svg>
|
||
Скачать PDF
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
);
|
||
}
|