Голый <main maxWidth:920> без layout-классов реальной страницы → карточки рендерились узко/одной колонкой («мобильный» вид). Обёрнуто в .page > .result-col (как page.tsx) — flex-column gap, --container ширина. Refs #801.
86 lines
3.8 KiB
TypeScript
86 lines
3.8 KiB
TypeScript
"use client";
|
||
|
||
/**
|
||
* #801: dev/CI-only preview-маршрут «денежного экрана» на статичной фикстуре.
|
||
*
|
||
* Зачем: a11y (axe) и lighthouse требуют отрендеренную контентную страницу.
|
||
* Live trade-in за auth-gate, а локальный фронт без бэкенда = белый экран
|
||
* (RouteGuard висит на pending /me). Эта страница рендерит реальные компоненты
|
||
* результата (HeroSummary + карточки/чарты) полностью оффлайн — без сети.
|
||
*
|
||
* Включается ТОЛЬКО при NEXT_PUBLIC_ENABLE_PREVIEW=1 (иначе notFound) — в проде
|
||
* по умолчанию недоступна. RouteGuard пропускает /ui-preview/* под тем же флагом.
|
||
* (Папка НЕ может начинаться с "_" — Next трактует такие как private/non-routable.)
|
||
*
|
||
* Локально: echo NEXT_PUBLIC_ENABLE_PREVIEW=1 > .env.local && npm run dev
|
||
* → http://localhost:3000/ui-preview/estimate
|
||
* CI: собрать с флагом, поднять next start, навести axe/lighthouse.
|
||
*/
|
||
|
||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||
import { notFound } from "next/navigation";
|
||
import { useState } from "react";
|
||
|
||
import "@/components/trade-in/trade-in.css";
|
||
import { CianValuationCard } from "@/components/trade-in/CianValuationCard";
|
||
import { DealsCard } from "@/components/trade-in/DealsCard";
|
||
import { DistributionCard } from "@/components/trade-in/DistributionCard";
|
||
import { ExposureCard } from "@/components/trade-in/ExposureCard";
|
||
import { HeroSummary } from "@/components/trade-in/HeroSummary";
|
||
import { PriceTrendCard } from "@/components/trade-in/PriceTrendCard";
|
||
import { StreetDealsCard } from "@/components/trade-in/StreetDealsCard";
|
||
import { ME_QUERY_KEY, type UserScope } from "@/lib/useMe";
|
||
|
||
import { FIXTURE_ESTIMATE, FIXTURE_INPUT } from "./fixture";
|
||
|
||
const ENABLED = process.env.NEXT_PUBLIC_ENABLE_PREVIEW === "1";
|
||
|
||
// Фейковый authorized-юзер: засеивается в QueryClient → useMe/useBrand резолвятся
|
||
// оффлайн (никакого /me, /quota, /brand), generic-бренд (brand=null).
|
||
const PREVIEW_USER: UserScope = {
|
||
username: "preview",
|
||
role: "admin",
|
||
allowed_paths: ["/**"],
|
||
deny_paths: [],
|
||
brand: null,
|
||
};
|
||
|
||
function PreviewContent() {
|
||
const [client] = useState(() => {
|
||
const qc = new QueryClient({
|
||
defaultOptions: {
|
||
queries: { retry: false, staleTime: Infinity, refetchOnWindowFocus: false },
|
||
},
|
||
});
|
||
qc.setQueryData(ME_QUERY_KEY, PREVIEW_USER);
|
||
return qc;
|
||
});
|
||
|
||
return (
|
||
<QueryClientProvider client={client}>
|
||
{/* Те же layout-классы, что у реальной страницы (page.tsx): .page → .result-col
|
||
(flex-column gap, --container ширина). Без них карточки рендерились узко/«мобильно». */}
|
||
<main className="page">
|
||
<section className="result-col">
|
||
<HeroSummary
|
||
estimate={FIXTURE_ESTIMATE}
|
||
input={FIXTURE_INPUT}
|
||
onResubmit={() => {}}
|
||
isResubmitting={false}
|
||
/>
|
||
<CianValuationCard data={FIXTURE_ESTIMATE.cian_valuation} />
|
||
<DistributionCard estimate={FIXTURE_ESTIMATE} />
|
||
<DealsCard estimate={FIXTURE_ESTIMATE} />
|
||
<StreetDealsCard estimate={FIXTURE_ESTIMATE} />
|
||
<ExposureCard estimate={FIXTURE_ESTIMATE} />
|
||
<PriceTrendCard estimate={FIXTURE_ESTIMATE} />
|
||
</section>
|
||
</main>
|
||
</QueryClientProvider>
|
||
);
|
||
}
|
||
|
||
export default function PreviewEstimatePage() {
|
||
if (!ENABLED) notFound();
|
||
return <PreviewContent />;
|
||
}
|