gendesign/tradein-mvp/frontend/src/app/ui-preview/estimate/page.tsx
lekss361 96491352df feat(tradein): dev/CI preview route for a11y+lighthouse audits (#801)
UI-аудит инструментами (axe/lighthouse) требует отрендеренную контентную
страницу. Live trade-in за auth-gate, локальный фронт без бэкенда = белый экран
(RouteGuard висит на pending /me). Добавлен оффлайн preview «денежного экрана».

- src/app/ui-preview/estimate/{page,fixture}.tsx — рендерит реальные компоненты
  результата (HeroSummary + Cian + Distribution + Deals + StreetDeals + Exposure
  + PriceTrend) на статичной фикстуре AggregatedEstimate, без сети: свой
  QueryClient с засеянным фейковым /me (useMe/useBrand резолвятся оффлайн).
- RouteGuard: bypass /ui-preview/* под флагом (RBAC к preview не применяется).
- Гейт: NEXT_PUBLIC_ENABLE_PREVIEW=1 (иначе notFound) — в проде по умолчанию off.
  Папка ui-preview (НЕ _-prefix: Next трактует _* как private/non-routable).

Проверено локально (NEXT_PUBLIC_ENABLE_PREVIEW=1): страница рендерится оффлайн,
axe (wcag2a/aa) дал baseline — 2 serious: color-contrast (teal #14b8a6 на светлом
2.32<4.5) + svg-img-alt (recharts scatter без accessible text). tsc clean.

Refs #801. Followup: фиксы найденных axe-дефектов отдельными issue.
2026-05-30 20:35:22 +03:00

84 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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 { 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}>
<main
className="tradein-results"
style={{ maxWidth: 920, margin: "0 auto", padding: 24 }}
>
<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} />
</main>
</QueryClientProvider>
);
}
export default function PreviewEstimatePage() {
if (!ENABLED) notFound();
return <PreviewContent />;
}