Indywidualna strona dla premium

This commit is contained in:
dm
2025-12-18 08:22:03 +01:00
parent 669aea3078
commit a1740fcc31
7 changed files with 174 additions and 45 deletions

View File

@@ -8,6 +8,7 @@ const links = [
{ name: "INTERNET ŚWIATŁOWODOWY", href: "/internet-swiatlowodowy" }, { name: "INTERNET ŚWIATŁOWODOWY", href: "/internet-swiatlowodowy" },
{ name: "INTERNET I TELEWIZJA", href: "/internet-telewizja" }, { name: "INTERNET I TELEWIZJA", href: "/internet-telewizja" },
{ name: "TELEFON", href: "/telefon" }, { name: "TELEFON", href: "/telefon" },
{ name: "PAKIETY PREMIUM", href: "/premium" },
{ name: "ZASIĘG SIECI", href: "/mapa-zasiegu" }, { name: "ZASIĘG SIECI", href: "/mapa-zasiegu" },
{ name: "KONTAKT", href: "/kontakt" }, { name: "KONTAKT", href: "/kontakt" },
{ name: "DOKUMENTY", href: "/dokumenty" }, { name: "DOKUMENTY", href: "/dokumenty" },

View File

@@ -1,6 +1,7 @@
tytul: Pakiety premium tytul: Pakiety premium
opis: | opis: |
Rozbuduj telewizję o kanały premium i dodatkowe serwisy VOD. Dobierz pakiet filmowy, sportowy lub rozrywkowy — dokładnie pod to, co oglądasz. Rozbuduj telewizję o kanały premium i dodatkowe serwisy VOD.
Dobierz pakiet filmowy, sportowy lub rozrywkowy — dokładnie pod to, co oglądasz.
cena_opis: zł/mies. cena_opis: zł/mies.
dodatki: dodatki:

View File

@@ -424,7 +424,8 @@ export default function JamboxChannelsSearch() {
type="button" type="button"
class="f-chsearch-pkg" class="f-chsearch-pkg"
onClick={() => window.open( onClick={() => window.open(
`/premium#tid-${encodeURIComponent(p.tid)}`, // `/premium#tid-${encodeURIComponent(p.tid)}`,
`/premium/${p.tid}`,
"_blank", "_blank",
"noopener,noreferrer" "noopener,noreferrer"
)} )}

View File

@@ -99,7 +99,8 @@ export default function TvAddonsSection({
{a.tid ? ( {a.tid ? (
<a <a
class="f-addon-more" class="f-addon-more"
href={`/premium#tid-${encodeURIComponent(a.tid)}`} // href={`/premium#tid-${encodeURIComponent(a.tid)}`}
href={`/premium/${a.tid}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
aria-label={`Więcej informacji o pakiecie ${a.nazwa ?? ""} (otwiera się w nowej karcie)`} aria-label={`Więcej informacji o pakiecie ${a.nazwa ?? ""} (otwiera się w nowej karcie)`}

View File

@@ -0,0 +1,157 @@
---
import DefaultLayout from "../../layouts/DefaultLayout.astro";
import yaml from "js-yaml";
import fs from "node:fs";
import Markdown from "../../islands/Markdown.jsx";
import AddonChannelsGrid from "../../islands/jambox/AddonChannelsModal.jsx";
import "../../styles/jambox-tematyczne.css";
/** Typy minimalne */
type AddonPriceRow = {
pakiety?: string[] | any;
"12m"?: number | string;
bezterminowo?: number | string;
};
type TvAddon = {
id?: string;
nazwa?: string;
tid?: number;
typ?: string;
opis?: string;
image?: string;
cena?: AddonPriceRow[];
group?: string;
group_mode?: string;
};
type TvAddonsDoc = {
tytul?: string;
opis?: string;
cena_opis?: string;
dodatki?: TvAddon[];
};
const doc = yaml.load(
fs.readFileSync("./src/content/internet-telewizja/tv-addons.yaml", "utf8"),
) as TvAddonsDoc;
const pageTitle = doc?.tytul ?? "Dodatkowe pakiety TV";
const pageDesc = doc?.opis ?? "";
const addons: TvAddon[] = Array.isArray(doc?.dodatki) ? doc.dodatki : [];
// --- dynamic param ---
const tidParam = Astro.params.tid; // np. "49"
const tid = Number(tidParam);
const picked =
Number.isFinite(tid)
? addons.find((a) => Number(a?.tid) === tid)
: null;
if (!picked) {
return new Response("Nie znaleziono pakietu dla podanego tid.", { status: 404 });
}
// --- jeśli jest group => bierzemy całą grupę ---
const pickedGroup = String(picked?.group ?? "").trim();
let viewAddons: TvAddon[] = [];
if (pickedGroup) {
viewAddons = addons.filter((a) => String(a?.group ?? "").trim() === pickedGroup);
} else {
viewAddons = [picked];
}
// stabilne sortowanie (np. po tid, żeby HBO/Disney nie skakały)
viewAddons = viewAddons
.slice()
.sort((a, b) => Number(a?.tid ?? 0) - Number(b?.tid ?? 0));
// tytuł/description strony
const singleTitle = pickedGroup ? (pickedGroup.toUpperCase() || pageTitle) : (String(picked?.nazwa ?? "").trim() || pageTitle);
const singleDesc = pageDesc;
---
<DefaultLayout title={singleTitle} description={singleDesc}>
<!-- (opcjonalnie) nagłówek strony -->
<!-- <section class="f-section">
<div class="f-section-grid-single">
<h1 class="f-section-title">{singleTitle}</h1>
{singleDesc && <Markdown text={singleDesc} />}
</div>
</section> -->
{
viewAddons.map((addon: TvAddon, index: number) => {
const isAboveFold = index === 0;
const pkgName = String(addon?.nazwa ?? "").trim();
const hasYamlImage = !!String(addon?.image ?? "").trim();
const assumeHasMedia = pkgName ? true : hasYamlImage;
const anchorId = addon?.tid != null ? `tid-${addon.tid}` : undefined;
return (
<section class="f-section" id={anchorId}>
<div
class={`f-section-grid f-addon-grid f-addon-section ${
assumeHasMedia ? "md:grid-cols-2" : "md:grid-cols-1"
}`}
data-addon-section
data-has-media={assumeHasMedia ? "1" : "0"}
>
<div class="f-addon-text">
{pkgName && <h2 class="f-section-title">{pkgName}</h2>}
{addon?.opis && <Markdown text={addon.opis} />}
</div>
<div class="f-addon-media">
{pkgName ? (
<AddonChannelsGrid
client:idle
packageName={pkgName}
fallbackImage={String(addon?.image ?? "")}
aboveFold={isAboveFold}
title={pkgName}
/>
) : null}
</div>
</div>
</section>
);
})
}
<script is:inline>
function __scrollToHashWithOffset(behavior = "auto") {
if (!location.hash) return;
const id = decodeURIComponent(location.hash.slice(1));
const el = document.getElementById(id);
if (!el) return;
const nav =
document.querySelector(".f-navbar") ||
document.querySelector("header") ||
document.querySelector("[data-navbar]");
const navH = nav ? nav.getBoundingClientRect().height : 84;
const extra = 8;
const top = el.getBoundingClientRect().top + window.pageYOffset - navH - extra;
window.scrollTo({ top: Math.max(0, top), behavior });
}
function __runFix() {
__scrollToHashWithOffset("auto");
requestAnimationFrame(() => __scrollToHashWithOffset("auto"));
setTimeout(() => __scrollToHashWithOffset("auto"), 120);
setTimeout(() => __scrollToHashWithOffset("auto"), 600);
}
window.addEventListener("load", __runFix);
window.addEventListener("hashchange", () => __scrollToHashWithOffset("smooth"));
</script>
</DefaultLayout>

View File

@@ -37,6 +37,9 @@ const doc = yaml.load(
const pageTitle = doc?.tytul ?? "Dodatkowe pakiety TV"; const pageTitle = doc?.tytul ?? "Dodatkowe pakiety TV";
const pageDesc = doc?.opis ?? ""; const pageDesc = doc?.opis ?? "";
const addons: TvAddon[] = Array.isArray(doc?.dodatki) ? doc.dodatki : []; const addons: TvAddon[] = Array.isArray(doc?.dodatki) ? doc.dodatki : [];
// ustaw tu bazowy URL dla stron pakietów
const detailsBase = "/pakiety-premium"; // => /pakiety-premium/{tid}
--- ---
<DefaultLayout title={pageTitle} description={pageDesc}> <DefaultLayout title={pageTitle} description={pageDesc}>
@@ -55,19 +58,20 @@ const addons: TvAddon[] = Array.isArray(doc?.dodatki) ? doc.dodatki : [];
const pkgName = String(addon?.nazwa ?? "").trim(); const pkgName = String(addon?.nazwa ?? "").trim();
const assumeHasMedia = pkgName ? true : hasYamlImage; const assumeHasMedia = pkgName ? true : hasYamlImage;
const anchorId = addon?.tid != null ? `tid-${addon.tid}` : undefined;
// link do strony pakietu (jeśli jest tid)
const href = addon?.tid != null ? `${detailsBase}/${addon.tid}` : null;
return ( return (
<section class="f-section" id={anchorId}> <section class="f-section">
<div <div
class={`f-section-grid f-addon-grid f-addon-section ${ class={`f-section-grid f-addon-grid f-addon-section ${
assumeHasMedia ? "md:grid-cols-2" : "md:grid-cols-1" assumeHasMedia ? "md:grid-cols-2" : "md:grid-cols-1"
}`} }`}
data-addon-section
data-has-media={assumeHasMedia ? "1" : "0"}
> >
<div class="f-addon-text"> <div class="f-addon-text">
{pkgName && <h2 class="f-section-title">{pkgName}</h2>} {pkgName && <h2 class="f-section-title">{pkgName}</h2>}
{addon?.opis && <Markdown text={addon.opis} />} {addon?.opis && <Markdown text={addon.opis} />}
</div> </div>
@@ -87,40 +91,4 @@ const addons: TvAddon[] = Array.isArray(doc?.dodatki) ? doc.dodatki : [];
); );
}) })
} }
<script is:inline>
function __scrollToHashWithOffset(behavior = "auto") {
if (!location.hash) return;
const id = decodeURIComponent(location.hash.slice(1));
const el = document.getElementById(id);
if (!el) return;
// znajdź navbar (dopasuj selektor do siebie)
const nav =
document.querySelector(".f-navbar") ||
document.querySelector("header") ||
document.querySelector("[data-navbar]");
const navH = nav ? nav.getBoundingClientRect().height : 84;
const extra = 8;
const top = el.getBoundingClientRect().top + window.pageYOffset - navH - extra;
window.scrollTo({ top: Math.max(0, top), behavior });
}
function __runFix() {
// po wejściu na stronę z hashem przeglądarka może już coś przewinąć,
// więc poprawiamy po renderze i po dociągnięciu treści
__scrollToHashWithOffset("auto");
requestAnimationFrame(() => __scrollToHashWithOffset("auto"));
setTimeout(() => __scrollToHashWithOffset("auto"), 120);
setTimeout(() => __scrollToHashWithOffset("auto"), 600);
}
window.addEventListener("load", __runFix);
window.addEventListener("hashchange", () => __scrollToHashWithOffset("smooth"));
</script>
</DefaultLayout> </DefaultLayout>

View File

@@ -3,7 +3,7 @@
} }
.f-navbar-inner { .f-navbar-inner {
@apply max-w-7xl mx-auto flex items-center justify-between py-1 px-4; @apply max-w-screen-2xl mx-auto flex items-center justify-between py-1 px-4;
} }
.f-navbar-links { .f-navbar-links {
@@ -42,7 +42,7 @@
.f-navbar-logo { .f-navbar-logo {
@apply block; @apply block;
height: 60px; /* testowo: 44px / 40px jak chcesz ciaśniej */ height: 60px;
width: auto; width: auto;
} }