diff --git a/tradein-mvp/frontend/next.config.ts b/tradein-mvp/frontend/next.config.ts index a3237e8b..37a1f18c 100644 --- a/tradein-mvp/frontend/next.config.ts +++ b/tradein-mvp/frontend/next.config.ts @@ -25,13 +25,6 @@ const nextConfig: NextConfig = { { source: "/health", destination: `${backend}/health` }, ]; }, - - async redirects() { - // basePath применяется автоматически к source — НЕ дублируем. - return [ - { source: "/", destination: "/trade-in", permanent: false }, - ]; - }, }; export default nextConfig; diff --git a/tradein-mvp/frontend/src/app/page.tsx b/tradein-mvp/frontend/src/app/page.tsx index 7b5abe61..4b2d2d0e 100644 --- a/tradein-mvp/frontend/src/app/page.tsx +++ b/tradein-mvp/frontend/src/app/page.tsx @@ -1,5 +1,192 @@ -import { redirect } from "next/navigation"; +"use client"; -export default function HomePage() { - redirect("/trade-in"); +/** + * Trade-In страница в стиле gendsgn.ru/tradein.html mockup. + * С basePath=/trade-in user видит её на gendsgn.ru/trade-in/. + * Использует CSS из /components/trade-in/trade-in.css. + */ +import { useState } from "react"; +import { useRouter } from "next/navigation"; + +import "@/components/trade-in/trade-in.css"; +import type { AggregatedEstimate, TradeInEstimateInput } from "@/types/trade-in"; +import { useEstimateMutation, useEstimate } from "@/lib/trade-in-api"; +import { EstimateForm } from "@/components/trade-in/EstimateForm"; +import { SourcesProgress } from "@/components/trade-in/SourcesProgress"; +import { HeroSummary } from "@/components/trade-in/HeroSummary"; +import { ListingsCard } from "@/components/trade-in/ListingsCard"; +import { DealsCard } from "@/components/trade-in/DealsCard"; +import { OfferCard } from "@/components/trade-in/OfferCard"; +import { TestPresets } from "@/components/trade-in/TestPresets"; + +function useEstimateId() { + if (typeof window === "undefined") return null; + const params = new URLSearchParams(window.location.search); + return params.get("id"); +} + +export default function TradeInPage() { + const router = useRouter(); + const [freshResult, setFreshResult] = useState<{ + estimate: AggregatedEstimate; + input: TradeInEstimateInput; + } | null>(null); + + const urlEstimateId = useEstimateId(); + const restoredEstimate = useEstimate( + freshResult === null ? urlEstimateId : null, + ); + + const mutation = useEstimateMutation(); + + function handleSubmit(input: TradeInEstimateInput) { + mutation.mutate(input, { + onSuccess: (estimate) => { + setFreshResult({ estimate, input }); + // basePath auto-prefixes — passing "/" returns user to /trade-in/?id=... + router.replace(`/?id=${estimate.estimate_id}`, { scroll: false }); + }, + }); + } + + const apiError = mutation.error?.message ?? null; + const resultData = + freshResult ?? + (restoredEstimate.data + ? { + estimate: restoredEstimate.data, + input: { + address: restoredEstimate.data.target_address ?? "—", + area_m2: 0, + rooms: restoredEstimate.data.analogs[0]?.rooms ?? 0, + floor: 0, + total_floors: 0, + } as TradeInEstimateInput, + } + : null); + + const isPending = mutation.isPending; + + return ( + <> + {/* Topbar — белый-лейбл бренд */} +
+
+
+ TI + PRINZIP + + Trade-In +
+ +
+
+ +
+
+ Главная Trade-In Новая оценка +
+ +
+
+

Оценка квартиры на вторичке

+

+ Агрегируем данные из 7 источников + аналоги в продаже + фактические сделки. + Время сбора — 10–30 сек. +

+
+
+ + + Отчёт{" "} + + {resultData?.estimate.estimate_id.slice(0, 8) ?? "—"} + + + + + Дата{" "} + + {new Date().toLocaleDateString("ru-RU", { + day: "2-digit", + month: "2-digit", + year: "numeric", + })} + + + {resultData && ( + + + Действителен до{" "} + + {new Date(resultData.estimate.expires_at).toLocaleDateString("ru-RU", { + day: "2-digit", + month: "2-digit", + year: "numeric", + })} + + + )} +
+
+ +
+ {/* Sticky form left */} + + + {/* Result column */} +
+ + + {/* Тестовые пресеты — показываем только когда нет результата */} + {!resultData && !isPending && ( + handleSubmit(data)} /> + )} + + {resultData ? ( + <> + + + + + + ) : ( +
+
+
🏠
+

+ Введите параметры квартиры +

+

+ Заполните форму слева или выберите тестовую квартиру выше. +

+
+
+ )} +
+
+
+ + + + ); } diff --git a/tradein-mvp/frontend/src/app/trade-in/page.tsx b/tradein-mvp/frontend/src/app/trade-in/page.tsx deleted file mode 100644 index 7df3d25d..00000000 --- a/tradein-mvp/frontend/src/app/trade-in/page.tsx +++ /dev/null @@ -1,190 +0,0 @@ -"use client"; - -/** - * Trade-In страница в стиле gendsgn.ru/tradein.html mockup. - * Использует CSS из /components/trade-in/trade-in.css. - */ -import { useState } from "react"; -import { useRouter } from "next/navigation"; - -import "@/components/trade-in/trade-in.css"; -import type { AggregatedEstimate, TradeInEstimateInput } from "@/types/trade-in"; -import { useEstimateMutation, useEstimate } from "@/lib/trade-in-api"; -import { EstimateForm } from "@/components/trade-in/EstimateForm"; -import { SourcesProgress } from "@/components/trade-in/SourcesProgress"; -import { HeroSummary } from "@/components/trade-in/HeroSummary"; -import { ListingsCard } from "@/components/trade-in/ListingsCard"; -import { DealsCard } from "@/components/trade-in/DealsCard"; -import { OfferCard } from "@/components/trade-in/OfferCard"; -import { TestPresets } from "@/components/trade-in/TestPresets"; - -function useEstimateId() { - if (typeof window === "undefined") return null; - const params = new URLSearchParams(window.location.search); - return params.get("id"); -} - -export default function TradeInPage() { - const router = useRouter(); - const [freshResult, setFreshResult] = useState<{ - estimate: AggregatedEstimate; - input: TradeInEstimateInput; - } | null>(null); - - const urlEstimateId = useEstimateId(); - const restoredEstimate = useEstimate( - freshResult === null ? urlEstimateId : null, - ); - - const mutation = useEstimateMutation(); - - function handleSubmit(input: TradeInEstimateInput) { - mutation.mutate(input, { - onSuccess: (estimate) => { - setFreshResult({ estimate, input }); - router.replace(`/trade-in?id=${estimate.estimate_id}`, { scroll: false }); - }, - }); - } - - const apiError = mutation.error?.message ?? null; - const resultData = - freshResult ?? - (restoredEstimate.data - ? { - estimate: restoredEstimate.data, - input: { - address: restoredEstimate.data.target_address ?? "—", - area_m2: 0, - rooms: restoredEstimate.data.analogs[0]?.rooms ?? 0, - floor: 0, - total_floors: 0, - } as TradeInEstimateInput, - } - : null); - - const isPending = mutation.isPending; - - return ( - <> - {/* Topbar — белый-лейбл бренд */} -
-
-
- TI - PRINZIP - - Trade-In -
- -
-
- -
-
- Главная Trade-In Новая оценка -
- -
-
-

Оценка квартиры на вторичке

-

- Агрегируем данные из 7 источников + аналоги в продаже + фактические сделки. - Время сбора — 10–30 сек. -

-
-
- - - Отчёт{" "} - - {resultData?.estimate.estimate_id.slice(0, 8) ?? "—"} - - - - - Дата{" "} - - {new Date().toLocaleDateString("ru-RU", { - day: "2-digit", - month: "2-digit", - year: "numeric", - })} - - - {resultData && ( - - - Действителен до{" "} - - {new Date(resultData.estimate.expires_at).toLocaleDateString("ru-RU", { - day: "2-digit", - month: "2-digit", - year: "numeric", - })} - - - )} -
-
- -
- {/* Sticky form left */} - - - {/* Result column */} -
- - - {/* Тестовые пресеты — показываем только когда нет результата */} - {!resultData && !isPending && ( - handleSubmit(data)} /> - )} - - {resultData ? ( - <> - - - - - - ) : ( -
-
-
🏠
-

- Введите параметры квартиры -

-

- Заполните форму слева или выберите тестовую квартиру выше. -

-
-
- )} -
-
-
- - - - ); -}