Files
netrunner-landing/app/[lang]/page.tsx
T
nineap 4b1ef9f951
Build & Push Image / Build and push to Gitea Registry (push) Successful in 2m28s
Цены тарифов — с бэкенда (регион/валюта), не из PocketBase
PocketBase pricing_{lang} остаётся источником переводного маркетингового
контента (название, тэглайн, описание, фичи, порядок) — но сами цифры
price1m/6m/1y теперь подменяются данными с GET /billing/plans нового
бэкенда (см. netrunner-backend::modules::geoip): валюта определяется по
IP посетителя, VPN/датацентр всегда получает базовый USD.

Соответствие тарифа PocketBase к тарифу бэкенда — по полю `name`
(ожидается "GHOST"/"NETRUNNER"/"MAINFRAME"). Бэкенд недоступен или для
конкретного тарифа не нашлось соответствия — тихий фолбэк на цену как
она есть в PocketBase, страница не ломается (тот же принцип, что уже
был у offline-кеша).
2026-07-11 00:55:15 +07:00

73 lines
3.4 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.
import { Suspense } from "react";
import dynamic from "next/dynamic";
import { getDictionary } from "@/lib/dictionaries";
import { Hero } from "@/components/sections/Hero";
import { Features } from "@/components/sections/Features";
const SectionSkeleton = () => (
<div className="h-[500px] w-full animate-pulse bg-card/20 rounded-3xl my-10" />
);
// Тяжёлые интерактивные секции (framer-motion анимации, клиентские запросы к PocketBase,
// realtime-подписки) грузятся через next/dynamic, а не обычным import, чтобы не раздувать
// первый JS-бандл главной страницы: Hero и Features рендерятся сразу (статично, легче),
// а Pricing/FAQ/TerminalGuestbook/BlogPreview подгружаются отдельными чанками с плейсхолдером
// SectionSkeleton, пока грузится JS.
const Pricing = dynamic(
() => import("@/components/sections/Pricing").then((mod) => mod.Pricing),
{ loading: () => <SectionSkeleton /> },
);
const FAQ = dynamic(
() => import("@/components/sections/Faq").then((mod) => mod.FAQ),
{ loading: () => <SectionSkeleton /> },
);
const TerminalGuestbook = dynamic(
() =>
import("@/components/sections/TerminalGuestbook").then(
(mod) => mod.TerminalGuestbook,
),
{ loading: () => <SectionSkeleton /> },
);
const BlogPreview = dynamic(() =>
import("@/components/sections/BlogPreview").then((mod) => mod.BlogPreview),
);
export default async function Home({
params,
}: {
params: Promise<{ lang: string }>;
}) {
const { lang } = await params;
const dict = await getDictionary(lang);
// Читаем PB_PUBLIC_URL, а не NEXT_PUBLIC_PB_URL: Next.js статически подставляет
// NEXT_PUBLIC_*-переменные везде, где они встречаются в коде (в т.ч. внутри серверных
// компонентов), на этапе `next build` — так что чтение NEXT_PUBLIC_PB_URL здесь давало бы
// то же самое собранное-в-образ значение, что и в клиентском бандле. PB_PUBLIC_URL —
// обычная (не NEXT_PUBLIC_) переменная, поэтому читается по-настоящему заново при каждом
// запросе (см. подробный комментарий в app/[lang]/auth/auth-client.tsx про ACCOUNT_ORIGIN).
const pbUrl =
process.env.PB_PUBLIC_URL || "https://netrunner-vpn.com/cms-api";
// Те же соображения про NEXT_PUBLIC_ vs обычную переменную, что и у pbUrl
// выше (см. комментарий) — см. также app/[lang]/auth/page.tsx, тот же
// паттерн для того же самого бэкенда.
const accountOrigin =
process.env.ACCOUNT_ORIGIN || "https://account.netrunner-vpn.com";
const apiUrl = process.env.API_URL || `${accountOrigin}/api/v1`;
return (
<>
<Hero dict={dict.hero} lang={lang} />
<Features dict={dict.features} />
<Pricing dict={dict.pricing} lang={lang} pbUrl={pbUrl} apiUrl={apiUrl} />
<TerminalGuestbook dict={dict.guestbook} pbUrl={pbUrl} />
<Suspense fallback={<SectionSkeleton />}>
<BlogPreview lang={lang} dict={dict.blog} />
</Suspense>
<FAQ dict={dict.faq} lang={lang} />
</>
);
}