"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. * * Секции (1:1 с page.tsx строки 228-276): * 1. SourcesProgress — estimate prop * 2. WhatIfPanel — baseEstimate + baseInput props * 3. HeroSummary — estimate + input props * 4. IMVBenchmark — benchmark prop (FIXTURE_IMV, available:true) * 5. CianValuationCard — data prop * 6. HouseInfoCard — houses prop (FIXTURE_HOUSES, length>0) * 7. PlacementHistoryCard — estimateId; seed queryKey "placement-history" * 8. HouseAnalyticsSection — estimateId; seed "house-analytics" + "sell-time-sensitivity" * 9. PhotoUpload — estimateId (upload-UI; fetch photo может тихо упасть — ок) * 10. ListingsCard — estimate + estimateId; seed "cian-price-changes" пустым * 11. DealsCard — estimate prop * 12. StreetDealsCard — estimate prop * 13. MapCard — estimate prop (analogs имеют lat/lon → пины есть) * 14. DistributionCard — estimate prop * 15. PriceTrendCard — estimate prop * 16. ExposureCard — estimate prop * 17. OfferCard — estimate + brandSlug=null props */ 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 { HouseAnalyticsSection } from "@/components/trade-in/HouseAnalyticsSection"; import { HouseInfoCard } from "@/components/trade-in/HouseInfoCard"; import { IMVBenchmark } from "@/components/trade-in/IMVBenchmark"; import { ListingsCard } from "@/components/trade-in/ListingsCard"; import { OfferCard } from "@/components/trade-in/OfferCard"; import { PhotoUpload } from "@/components/trade-in/PhotoUpload"; import { PlacementHistoryCard } from "@/components/trade-in/PlacementHistoryCard"; import { PriceTrendCard } from "@/components/trade-in/PriceTrendCard"; import { SourcesProgress } from "@/components/trade-in/SourcesProgress"; import { StreetDealsCard } from "@/components/trade-in/StreetDealsCard"; import { WhatIfPanel } from "@/components/trade-in/WhatIfPanel"; import { ME_QUERY_KEY, type UserScope } from "@/lib/useMe"; import { FIXTURE_ANALYTICS, FIXTURE_ESTIMATE, FIXTURE_HOUSES, FIXTURE_IMV, FIXTURE_INPUT, FIXTURE_PLACEMENT, FIXTURE_SALES, FIXTURE_SELLTIME, } from "./fixture"; const ENABLED = process.env.NEXT_PUBLIC_ENABLE_PREVIEW === "1"; const PREVIEW_ID = FIXTURE_ESTIMATE.estimate_id; // Фейковый 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 }, }, }); // /me — пропускает RouteGuard без сетевого запроса qc.setQueryData(ME_QUERY_KEY, PREVIEW_USER); // Секция 7: PlacementHistoryCard — useEstimatePlacementHistory qc.setQueryData(["trade-in", "estimate", PREVIEW_ID, "placement-history"], FIXTURE_PLACEMENT); // Секция 8: HouseAnalyticsSection — useEstimateHouseAnalytics + useEstimateSellTimeSensitivity qc.setQueryData(["trade-in", "estimate", PREVIEW_ID, "house-analytics"], FIXTURE_ANALYTICS); qc.setQueryData(["trade-in", "estimate", PREVIEW_ID, "sell-time-sensitivity"], FIXTURE_SELLTIME); // Секция 10: ListingsCard — useEstimateCianPriceChanges (пустой = graceful) qc.setQueryData(["trade-in", "estimate", PREVIEW_ID, "cian-price-changes"], []); // Секция 12: StreetDealsCard — useSalesVsListings(address, area_m2, rooms) qc.setQueryData( [ "trade-in", "sales-vs-listings", FIXTURE_ESTIMATE.target_address, FIXTURE_ESTIMATE.area_m2, FIXTURE_ESTIMATE.rooms, ], FIXTURE_SALES, ); return qc; }); return ( {/* Те же layout-классы, что у реальной страницы (page.tsx): .page → .result-col (flex-column gap, --container ширина). Без них карточки рендерились узко/«мобильно». */}
{/* 1. SourcesProgress */} {/* 2. WhatIfPanel */} {/* 3. HeroSummary */} {}} isResubmitting={false} /> {/* 4. IMVBenchmark — передаём пропом напрямую (available:true → не null) */} {/* 5. CianValuationCard */} {/* 6. HouseInfoCard — передаём пропом напрямую (length>0 → не null) */} {/* 7. PlacementHistoryCard — useEstimatePlacementHistory; queryKey засеян выше */} {/* 8. HouseAnalyticsSection — useEstimateHouseAnalytics; queryKeys засеяны выше */} {/* 9. PhotoUpload — рендерит upload-UI; fetch фото может тихо упасть — ок */} {/* 10. ListingsCard — cian-price-changes засеян пустым массивом */} {/* 11. DealsCard */} {/* 12. StreetDealsCard */} {/* 13. MapCard — ОТКЛЮЧЁН в preview: leaflet тянет ~47 тайлов с tile.openstreetmap.org + lib с unpkg → внешний network не даёт navigation-idle → axe/lighthouse MCP таймаутят (30s). Placeholder сохраняет секцию «Карта» (card-структура аудируется), без сети. */}
Аналитика · Карта

Карта аналогов и сделок

Карта отключена в preview-режиме (внешние OSM-тайлы ломают network-idle аудита). На реальной странице здесь leaflet-карта.
{/* 14. DistributionCard */} {/* 15. PriceTrendCard */} {/* 16. ExposureCard */} {/* 17. OfferCard */}
); } export default function PreviewEstimatePage() { if (!ENABLED) notFound(); return ; }