1426 lines
38 KiB
Plaintext
1426 lines
38 KiB
Plaintext
ASTRO DUMP (root: D:\DARIUSZM\Desktop\fuz-site\src\pages)
|
||
Found files: 11
|
||
================================================================================
|
||
|
||
FILE: src/pages/dokumenty/[slug].astro
|
||
--------------------------------------------------------------------------------
|
||
---
|
||
import DefaultLayout from "../../layouts/DefaultLayout.astro";
|
||
import Markdown from "../../islands/Markdown.jsx";
|
||
import { marked } from "marked";
|
||
import { getDocumentBySlug } from "../../lib/documents";
|
||
import "../../styles/document.css";
|
||
|
||
const { slug } = Astro.params;
|
||
|
||
const doc = slug ? getDocumentBySlug(slug) : null;
|
||
if (!doc || doc.visible !== true) {
|
||
return Astro.redirect("/dokumenty", 302);
|
||
}
|
||
|
||
const html = marked.parse(doc.content);
|
||
---
|
||
|
||
<DefaultLayout title={doc.title}>
|
||
<section class="f-section">
|
||
<div class="f-section-grid-single">
|
||
<a href="/dokumenty" class="f-document-link">
|
||
← Wróć do dokumentów
|
||
</a>
|
||
|
||
<h1 class="f-section-title">
|
||
{doc.title}
|
||
</h1>
|
||
|
||
<div class="fuz-markdown max-w-none">
|
||
<Markdown text={html} />
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</DefaultLayout>
|
||
|
||
================================================================================
|
||
|
||
FILE: src/pages/dokumenty/index.astro
|
||
--------------------------------------------------------------------------------
|
||
---
|
||
import DefaultLayout from "../../layouts/DefaultLayout.astro";
|
||
import yaml from "js-yaml";
|
||
import fs from "node:fs";
|
||
import Markdown from "../../islands/Markdown.jsx";
|
||
import "../../styles/document.css";
|
||
|
||
type DocFile = {
|
||
nazwa?: string;
|
||
file?: string;
|
||
slug?: string;
|
||
};
|
||
|
||
type DocGroup = {
|
||
tytul?: string;
|
||
pliki?: DocFile[];
|
||
};
|
||
|
||
type DocsYaml = {
|
||
tytul?: string;
|
||
opis?: string;
|
||
grupy?: Record<string, DocGroup>;
|
||
};
|
||
|
||
const doc = yaml.load(
|
||
fs.readFileSync("./src/content/document/documents.yaml", "utf8"),
|
||
) as DocsYaml;
|
||
|
||
const pageTitle = doc?.tytul;
|
||
const pageDesc = doc?.opis;
|
||
|
||
const groups = doc?.grupy ?? {};
|
||
const left = groups["otworz"] ?? {};
|
||
const right = groups["pobierz"] ?? {};
|
||
|
||
function normalizePublicHref(input?: string) {
|
||
let s = String(input ?? "").trim();
|
||
if (!s) return "";
|
||
if (s.startsWith("/public/")) s = s.replace("/public", "");
|
||
s = s.replace(/ /g, "%20");
|
||
return s;
|
||
}
|
||
---
|
||
|
||
<DefaultLayout title={pageTitle} description={pageDesc}>
|
||
<section class="f-section">
|
||
<div class="f-section-grid-top md:grid-cols-2 gap-10">
|
||
|
||
<div>
|
||
<h3 class="f-section-title mt-0">{left.tytul ?? "Przeczytaj"}</h3>
|
||
|
||
{!left.pliki?.length ? (
|
||
<p class="opacity-70 mt-4">Brak dokumentów.</p>
|
||
) : (
|
||
<div class="f-documents-grid">
|
||
{left.pliki.map((p) => (
|
||
p.slug ? (
|
||
<a
|
||
class="f-document-card"
|
||
href={`/dokumenty/${p.slug}`}
|
||
title={p.nazwa}
|
||
>
|
||
<div class="f-document-title">{p.nazwa}</div>
|
||
</a>
|
||
) : null
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div>
|
||
<h3 class="f-section-title mt-0">{right.tytul ?? "Pobierz"}</h3>
|
||
|
||
{!right.pliki?.length ? (
|
||
<p class="opacity-70 mt-4">Brak plików.</p>
|
||
) : (
|
||
<div class="f-documents-grid">
|
||
{right.pliki.map((p) => {
|
||
const href = normalizePublicHref(p.file);
|
||
if (!href) return null;
|
||
|
||
return (
|
||
<a
|
||
class="f-document-card"
|
||
href={href}
|
||
download
|
||
title={p.nazwa}
|
||
>
|
||
<div class="f-document-title">{p.nazwa}</div>
|
||
</a>
|
||
);
|
||
})}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
</div>
|
||
</section>
|
||
</DefaultLayout>
|
||
|
||
================================================================================
|
||
|
||
FILE: src/pages/index.astro
|
||
--------------------------------------------------------------------------------
|
||
---
|
||
import DefaultLayout from "../layouts/DefaultLayout.astro";
|
||
import Hero from "../components/hero/Hero.astro";
|
||
import SectionRenderer from "../components/sections/SectionRenderer.astro"
|
||
|
||
import yaml from "js-yaml";
|
||
import fs from "fs";
|
||
|
||
const seo = yaml.load(fs.readFileSync("./src/content/home/seo.yaml", "utf8"));
|
||
const hero = yaml.load(fs.readFileSync("./src/content/home/hero.yaml", "utf8"));
|
||
---
|
||
|
||
<DefaultLayout seo={seo}>
|
||
<Hero {...hero} />
|
||
<SectionRenderer src="./src/content/site/site.section.yaml" />
|
||
</DefaultLayout>
|
||
|
||
================================================================================
|
||
|
||
FILE: src/pages/internet-swiatlowodowy/index.astro
|
||
--------------------------------------------------------------------------------
|
||
---
|
||
import path from "node:path";
|
||
|
||
import DefaultLayout from "../../layouts/DefaultLayout.astro";
|
||
import SectionRenderer from "../../components/sections/SectionRenderer.astro";
|
||
import InternetCards from "../../islands/Internet/InternetCards.jsx";
|
||
|
||
import { loadYamlFile } from "../../lib/loadYaml";
|
||
|
||
type SeoYaml = any;
|
||
|
||
type InternetParam = { klucz: string; label: string; value: string | number };
|
||
type InternetCena = {
|
||
budynek: number | string;
|
||
umowa: number | string;
|
||
miesiecznie: number;
|
||
aktywacja?: number;
|
||
};
|
||
type InternetCard = {
|
||
nazwa: string;
|
||
widoczny?: boolean;
|
||
popularny?: boolean;
|
||
parametry?: InternetParam[];
|
||
ceny?: InternetCena[];
|
||
};
|
||
type InternetCardsYaml = {
|
||
tytul?: string;
|
||
opis?: string;
|
||
uwaga?: string;
|
||
waluta?: string;
|
||
cena_opis?: string;
|
||
cards?: InternetCard[];
|
||
};
|
||
|
||
type PhoneParam = { klucz: string; label: string; value: string | number };
|
||
type PhoneCard = {
|
||
nazwa: string;
|
||
widoczny?: boolean;
|
||
popularny?: boolean;
|
||
cena?: { wartosc: number; opis?: string };
|
||
parametry?: PhoneParam[];
|
||
};
|
||
type PhoneCardsYaml = { cards?: PhoneCard[] };
|
||
|
||
type Addon = {
|
||
id: string;
|
||
nazwa: string;
|
||
typ?: string;
|
||
ilosc?: boolean;
|
||
min?: number;
|
||
max?: number;
|
||
krok?: number;
|
||
opis?: string;
|
||
cena: number;
|
||
};
|
||
type AddonsYaml = { cena_opis?: string; dodatki?: Addon[] };
|
||
|
||
const seo = loadYamlFile<SeoYaml>(
|
||
path.join(
|
||
process.cwd(),
|
||
"src",
|
||
"content",
|
||
"internet-swiatlowodowy",
|
||
"seo.yaml",
|
||
),
|
||
);
|
||
|
||
const data = loadYamlFile<InternetCardsYaml>(
|
||
path.join(
|
||
process.cwd(),
|
||
"src",
|
||
"content",
|
||
"internet-swiatlowodowy",
|
||
"cards.yaml",
|
||
),
|
||
);
|
||
|
||
const phoneData = loadYamlFile<PhoneCardsYaml>(
|
||
path.join(process.cwd(), "src", "content", "telefon", "cards.yaml"),
|
||
);
|
||
|
||
const addonsData = loadYamlFile<AddonsYaml>(
|
||
path.join(
|
||
process.cwd(),
|
||
"src",
|
||
"content",
|
||
"internet-swiatlowodowy",
|
||
"addons.yaml",
|
||
),
|
||
);
|
||
|
||
const tytul = data?.tytul ?? "";
|
||
const opis = data?.opis ?? "Wybierz rodzaj budynku i czas trwania umowy";
|
||
const uwaga = data?.uwaga ?? "";
|
||
|
||
const waluta = data?.waluta ?? "PLN";
|
||
const cenaOpis = data?.cena_opis ?? "zł/mies.";
|
||
|
||
const cards = (
|
||
Array.isArray(data?.cards)
|
||
? data.cards.filter((c) => c?.widoczny === true)
|
||
: []
|
||
) as InternetCard[];
|
||
|
||
const phoneCards = (
|
||
Array.isArray(phoneData?.cards)
|
||
? phoneData.cards.filter((c) => c?.widoczny === true)
|
||
: []
|
||
) as PhoneCard[];
|
||
|
||
const addons = (
|
||
Array.isArray(addonsData?.dodatki) ? addonsData.dodatki : []
|
||
) as Addon[];
|
||
|
||
const addonsCenaOpis = addonsData?.cena_opis ?? cenaOpis;
|
||
|
||
type SwitchOption = { id: string | number; nazwa: string };
|
||
type SwitchDef = {
|
||
id: string;
|
||
etykieta?: string;
|
||
title?: string;
|
||
domyslny?: string | number;
|
||
opcje: SwitchOption[];
|
||
};
|
||
type SwitchesYaml = { switches?: SwitchDef[] };
|
||
|
||
const switchesData = loadYamlFile<SwitchesYaml>(
|
||
path.join(process.cwd(), "src", "content", "site", "switches.yaml"),
|
||
);
|
||
|
||
const switches = (
|
||
Array.isArray(switchesData?.switches) ? switchesData.switches : []
|
||
) as SwitchDef[];
|
||
---
|
||
|
||
<DefaultLayout seo={seo}>
|
||
<section class="f-section">
|
||
<div class="f-section-grid-single md:grid-cols-1">
|
||
<InternetCards
|
||
client:load
|
||
title={tytul}
|
||
description={opis}
|
||
uwaga={uwaga}
|
||
cards={cards}
|
||
waluta={waluta}
|
||
cenaOpis={cenaOpis}
|
||
phoneCards={phoneCards}
|
||
addons={addons}
|
||
addonsCenaOpis={addonsCenaOpis}
|
||
switches={switches}
|
||
/>
|
||
<p><span class="f-card-price text-sm">* </span>{uwaga}</p>
|
||
</div>
|
||
</section>
|
||
|
||
<SectionRenderer src="./src/content/internet-swiatlowodowy/section.yaml" />
|
||
</DefaultLayout>
|
||
|
||
================================================================================
|
||
|
||
FILE: src/pages/internet-telewizja/index.astro
|
||
--------------------------------------------------------------------------------
|
||
---
|
||
import path from "node:path";
|
||
|
||
import DefaultLayout from "../../layouts/DefaultLayout.astro";
|
||
import SectionRenderer from "../../components/sections/SectionRenderer.astro";
|
||
import JamboxCards from "../../islands/jambox/JamboxCards.jsx";
|
||
import SectionChannelsSearch from "../../components/sections/SectionChannelsSearch.astro";
|
||
|
||
import { loadYamlFile } from "../../lib/loadYaml";
|
||
|
||
type SeoYaml = any;
|
||
|
||
type Param = { klucz: string; label: string; value: string | number };
|
||
type Cena = {
|
||
budynek: number | string;
|
||
umowa: number | string;
|
||
miesiecznie: number;
|
||
aktywacja?: number;
|
||
};
|
||
type Card = {
|
||
id?: string;
|
||
source?: string;
|
||
tid?: number;
|
||
nazwa: string;
|
||
slug?: string;
|
||
widoczny?: boolean;
|
||
popularny?: boolean;
|
||
parametry?: Param[];
|
||
ceny?: Cena[];
|
||
};
|
||
|
||
type CardsYaml = {
|
||
tytul?: string;
|
||
opis?: string;
|
||
uwaga?: string;
|
||
waluta?: string;
|
||
cena_opis?: string;
|
||
internet_parametry_wspolne?: Param[];
|
||
cards?: Card[];
|
||
};
|
||
|
||
type PhoneParam = { klucz: string; label: string; value: string | number };
|
||
type PhoneCard = {
|
||
id?: string;
|
||
nazwa: string;
|
||
widoczny?: boolean;
|
||
popularny?: boolean;
|
||
cena?: { wartosc: number; opis?: string };
|
||
parametry?: PhoneParam[];
|
||
};
|
||
type PhoneYaml = { cards?: PhoneCard[] };
|
||
|
||
type Decoder = { id: string; nazwa: string; opis: string; cena: number };
|
||
|
||
type Addon = {
|
||
id: string;
|
||
nazwa: string;
|
||
typ?: "checkbox" | "quantity";
|
||
ilosc?: boolean;
|
||
min?: number;
|
||
max?: number;
|
||
krok?: number;
|
||
opis?: string;
|
||
cena: number;
|
||
};
|
||
type AddonsYaml = {
|
||
tytul?: string;
|
||
opis?: string;
|
||
cena_opis?: string;
|
||
dekodery?: Decoder[];
|
||
dodatki?: Addon[];
|
||
};
|
||
|
||
const seo = loadYamlFile<SeoYaml>(
|
||
path.join(process.cwd(), "src", "content", "internet-telewizja", "seo.yaml"),
|
||
);
|
||
|
||
const data = loadYamlFile<CardsYaml>(
|
||
path.join(
|
||
process.cwd(),
|
||
"src",
|
||
"content",
|
||
"internet-telewizja",
|
||
"cards.yaml",
|
||
),
|
||
);
|
||
|
||
const tytul = data?.tytul ?? "";
|
||
const opis = data?.opis ?? "Wybierz rodzaj budynku i czas trwania umowy";
|
||
const uwaga = data?.uwaga ?? "";
|
||
|
||
const waluta = data?.waluta ?? "PLN";
|
||
const cenaOpis = data?.cena_opis ?? "zł/mies.";
|
||
|
||
const internetWspolne: Param[] = Array.isArray(data?.internet_parametry_wspolne)
|
||
? data.internet_parametry_wspolne
|
||
: [];
|
||
|
||
const cards: Card[] = Array.isArray(data?.cards)
|
||
? data.cards.filter((c) => c?.widoczny === true)
|
||
: [];
|
||
|
||
const phoneYaml = loadYamlFile<PhoneYaml>(
|
||
path.join(process.cwd(), "src", "content", "telefon", "cards.yaml"),
|
||
);
|
||
|
||
const tvAddonsYaml = loadYamlFile<any>(
|
||
path.join(process.cwd(), "src", "content", "internet-telewizja", "tv-addons.yaml"),
|
||
);
|
||
|
||
const phoneCards = Array.isArray(phoneYaml?.cards) ? phoneYaml.cards : [];
|
||
const tvAddons = Array.isArray(tvAddonsYaml?.dodatki) ? tvAddonsYaml.dodatki : [];
|
||
|
||
const addonsYaml = loadYamlFile<AddonsYaml>(
|
||
path.join(
|
||
process.cwd(),
|
||
"src",
|
||
"content",
|
||
"internet-telewizja",
|
||
"addons.yaml",
|
||
),
|
||
);
|
||
const addons: Addon[] = Array.isArray(addonsYaml?.dodatki)
|
||
? addonsYaml.dodatki
|
||
: [];
|
||
|
||
const decoders: Decoder[] = Array.isArray(addonsYaml?.dekodery)
|
||
? addonsYaml.dekodery
|
||
: [];
|
||
|
||
const addonsCenaOpis = addonsYaml?.cena_opis ?? cenaOpis;
|
||
|
||
type SwitchOption = { id: string | number; nazwa: string };
|
||
type SwitchDef = {
|
||
id: string;
|
||
etykieta?: string;
|
||
title?: string;
|
||
domyslny?: string | number;
|
||
opcje: SwitchOption[];
|
||
};
|
||
type SwitchesYaml = { switches?: SwitchDef[] };
|
||
|
||
const switchesYaml = loadYamlFile<SwitchesYaml>(
|
||
path.join(process.cwd(), "src", "content", "site", "switches.yaml"),
|
||
);
|
||
|
||
const switches: SwitchDef[] = Array.isArray(switchesYaml?.switches)
|
||
? switchesYaml.switches
|
||
: [];
|
||
|
||
---
|
||
|
||
<DefaultLayout seo={seo}>
|
||
<section class="f-section">
|
||
<div class="f-section-grid-single md:grid-cols-1">
|
||
<JamboxCards
|
||
client:load
|
||
title={tytul}
|
||
description={opis}
|
||
uwaga={uwaga}
|
||
cards={cards}
|
||
internetWspolne={internetWspolne}
|
||
waluta={waluta}
|
||
cenaOpis={cenaOpis}
|
||
tvAddons={tvAddons}
|
||
phoneCards={phoneCards}
|
||
decoders={decoders}
|
||
addons={addons}
|
||
addonsCenaOpis={addonsCenaOpis}
|
||
switches={switches}
|
||
/>
|
||
<p><span class="f-card-price text-sm">* </span>{uwaga}</p>
|
||
</div>
|
||
</section>
|
||
<SectionChannelsSearch />
|
||
<SectionRenderer src="./src/content/internet-telewizja/section.yaml" />
|
||
</DefaultLayout>
|
||
|
||
================================================================================
|
||
|
||
FILE: src/pages/internet-telewizja/telewizja-mozliwosci.astro
|
||
--------------------------------------------------------------------------------
|
||
---
|
||
export const prerender = false;
|
||
|
||
import path from "node:path";
|
||
import DefaultLayout from "../../layouts/DefaultLayout.astro";
|
||
import { loadYamlFile } from "../../lib/loadYaml";
|
||
import MozliwosciSearch from "../../islands/jambox/JamboxMozliwosciSearch.jsx";
|
||
|
||
type YamlSection = {
|
||
title: string;
|
||
image?: string;
|
||
content?: string;
|
||
};
|
||
|
||
type YamlData = {
|
||
sections?: YamlSection[];
|
||
};
|
||
|
||
function slugify(s: string) {
|
||
return String(s || "")
|
||
.toLowerCase()
|
||
.replace(/[\u0105]/g, "a")
|
||
.replace(/[\u0107]/g, "c")
|
||
.replace(/[\u0119]/g, "e")
|
||
.replace(/[\u0142]/g, "l")
|
||
.replace(/[\u0144]/g, "n")
|
||
.replace(/[\u00f3]/g, "o")
|
||
.replace(/[\u015b]/g, "s")
|
||
.replace(/[\u017a\u017c]/g, "z")
|
||
.replace(/[^a-z0-9]+/g, "-")
|
||
.replace(/(^-|-$)/g, "");
|
||
}
|
||
|
||
let items: Array<{
|
||
id: string;
|
||
title: string;
|
||
image?: string;
|
||
content: string;
|
||
}> = [];
|
||
|
||
let err = "";
|
||
|
||
try {
|
||
const data = loadYamlFile<YamlData>(
|
||
path.join(
|
||
process.cwd(),
|
||
"src",
|
||
"content",
|
||
"internet-telewizja",
|
||
"telewizja-mozliwosci.yaml"
|
||
)
|
||
);
|
||
|
||
const sections = Array.isArray(data?.sections) ? data.sections : [];
|
||
|
||
items = sections
|
||
.filter((s) => s?.title)
|
||
.map((s) => ({
|
||
id: slugify(s.title),
|
||
title: s.title,
|
||
image: s.image,
|
||
content: (s.content || "").trim(),
|
||
}))
|
||
.filter((x) => x.content);
|
||
} catch (e) {
|
||
err = e instanceof Error ? e.message : String(e);
|
||
}
|
||
---
|
||
|
||
<DefaultLayout title="Możliwości JAMBOX">
|
||
<!-- NAGŁÓWEK STRONY – zgodny z FUZ -->
|
||
<section class="f-section" id="top">
|
||
<div class="f-section-grid-single">
|
||
<h1 class="f-section-title">Możliwości JAMBOX</h1>
|
||
|
||
<div class="fuz-markdown max-w-none">
|
||
<p>Funkcje i udogodnienia dostępne w JAMBOX.</p>
|
||
</div>
|
||
|
||
{!err && <MozliwosciSearch client:load items={items} />}
|
||
</div>
|
||
|
||
{
|
||
err && (
|
||
<div class="mt-6 max-w-7xl mx-auto text-left rounded-2xl border border-red-300 bg-red-50 p-4">
|
||
<p class="font-bold">Nie udało się wczytać danych</p>
|
||
<p class="opacity-80">{err}</p>
|
||
</div>
|
||
)
|
||
}
|
||
</section>
|
||
|
||
{/* UWAGA: render sekcji przeniesiony do wyspy, żeby filtr działał */}
|
||
</DefaultLayout>
|
||
|
||
================================================================================
|
||
|
||
FILE: src/pages/kontakt/index.astro
|
||
--------------------------------------------------------------------------------
|
||
---
|
||
import path from "node:path";
|
||
import DefaultLayout from "../../layouts/DefaultLayout.astro";
|
||
import MapGoogle from "../../components/maps/MapGoogle.astro";
|
||
import Markdown from "../../islands/Markdown.jsx";
|
||
import { loadYamlFile } from "../../lib/loadYaml";
|
||
import "../../styles/contact.css";
|
||
|
||
type SeoYaml = any;
|
||
const seo = loadYamlFile<SeoYaml>(
|
||
path.join(process.cwd(), "src", "content", "contact", "seo.yaml"),
|
||
);
|
||
|
||
type ContactData = any;
|
||
const data = loadYamlFile<ContactData>(
|
||
path.join(process.cwd(), "src", "content", "contact", "contact.yaml"),
|
||
);
|
||
|
||
const apiKey = import.meta.env.PUBLIC_GOOGLE_MAPS_KEY;
|
||
const form = data.form;
|
||
---
|
||
|
||
<DefaultLayout seo={seo}>
|
||
<section class="f-section">
|
||
<div class="f-section-grid md:grid-cols-2 gap-10 items-start">
|
||
<div class="f-contact-item">
|
||
<h1 class="f-section-title">{data.title}</h1>
|
||
<Markdown text={data.description} />
|
||
</div>
|
||
|
||
<div id="form">
|
||
<h1 class="f-section-title">{data.contactFormTitle}</h1>
|
||
|
||
<form id="contactForm" class="f-contact-form">
|
||
<div class="f-contact-form-inner">
|
||
<input
|
||
type="text"
|
||
name="firstName"
|
||
placeholder={form.firstName.placeholder}
|
||
class="f-input"
|
||
required
|
||
/>
|
||
<input
|
||
type="text"
|
||
name="lastName"
|
||
placeholder={form.lastName.placeholder}
|
||
class="f-input"
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||
<input
|
||
type="email"
|
||
name="email"
|
||
placeholder={form.email.placeholder}
|
||
class="f-input"
|
||
required
|
||
autocomplete="email"
|
||
/>
|
||
<input
|
||
type="tel"
|
||
name="phone"
|
||
placeholder={form.phone.placeholder}
|
||
class="f-input"
|
||
required
|
||
autocomplete="tel"
|
||
/>
|
||
</div>
|
||
|
||
<input
|
||
type="text"
|
||
name="subject"
|
||
placeholder={form.subject.placeholder}
|
||
class="f-input"
|
||
required
|
||
/>
|
||
|
||
<textarea
|
||
name="message"
|
||
rows={form.message.rows}
|
||
placeholder={form.message.placeholder}
|
||
class="f-input"
|
||
required></textarea>
|
||
|
||
<!-- widoczne tylko gdy jest oferta -->
|
||
<div id="offerSummaryWrap" class="hidden">
|
||
<textarea
|
||
id="offerSummary"
|
||
name="offerSummary"
|
||
rows="6"
|
||
class="f-input"
|
||
readonly
|
||
placeholder="Wybrana oferta pojawi się tutaj."></textarea>
|
||
</div>
|
||
|
||
<label class="f-rodo">
|
||
<input type="checkbox" name="rodo" required />
|
||
<span>
|
||
{form.rodo.label}
|
||
<a href={form.rodo.policyLink} title={form.rodo.policyTitle}>
|
||
{form.rodo.policyText}
|
||
</a>.
|
||
</span>
|
||
</label>
|
||
|
||
<button
|
||
type="submit"
|
||
class="btn btn-primary w-full py-3"
|
||
title={form.submit.title}
|
||
>
|
||
{form.submit.label}
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-10">
|
||
<div class="f-contact-map">
|
||
<MapGoogle
|
||
apiKey={apiKey}
|
||
lat={data.lat}
|
||
lon={data.lng}
|
||
zoom={16}
|
||
title={data.markerTitle}
|
||
description={data.markerAddress}
|
||
showMarker={true}
|
||
mode="contact"
|
||
mapStyleId={data.maps.mapId}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="toast" class="f-toast"></div>
|
||
</section>
|
||
|
||
<!-- ReCaptcha v3 -->
|
||
<script
|
||
is:inline
|
||
define:vars={{ siteKey: import.meta.env.PUBLIC_RECAPTCHA_SITE_KEY }}
|
||
>
|
||
window.FUZ_RECAPTCHA_KEY = siteKey;
|
||
|
||
const s = document.createElement("script");
|
||
s.src = "https://www.google.com/recaptcha/api.js?render=" + siteKey;
|
||
s.async = true;
|
||
document.head.appendChild(s);
|
||
</script>
|
||
|
||
<script
|
||
is:inline
|
||
define:vars={{
|
||
successMsg: form.successMessage,
|
||
errorMsg: form.errorMessage,
|
||
}}
|
||
>
|
||
document.addEventListener("DOMContentLoaded", () => {
|
||
const formEl = document.getElementById("contactForm");
|
||
const toast = document.getElementById("toast");
|
||
if (!formEl) return;
|
||
|
||
const LS_KEY = "fuz_offer_config_v1";
|
||
const SS_INJECTED_KEY = "fuz_offer_injected_v1";
|
||
|
||
function showToast(msg, type) {
|
||
if (!toast) return;
|
||
toast.innerHTML = `<div class="f-toast-msg ${type}">${msg}</div>`;
|
||
toast.classList.remove("visible");
|
||
void toast.offsetWidth;
|
||
toast.classList.add("visible");
|
||
setTimeout(() => toast.classList.remove("visible"), 3000);
|
||
}
|
||
|
||
function clearOfferDraft() {
|
||
try {
|
||
localStorage.removeItem(LS_KEY);
|
||
sessionStorage.removeItem(SS_INJECTED_KEY);
|
||
|
||
const wrap = document.getElementById("offerSummaryWrap");
|
||
if (wrap) wrap.classList.add("hidden");
|
||
|
||
const offerSummary = document.getElementById("offerSummary");
|
||
if (offerSummary) offerSummary.value = "";
|
||
} catch {}
|
||
}
|
||
|
||
function hydrateOfferIntoForm() {
|
||
try {
|
||
if (sessionStorage.getItem(SS_INJECTED_KEY) === "1") return;
|
||
|
||
const raw = localStorage.getItem(LS_KEY);
|
||
if (!raw) return;
|
||
|
||
const payload = JSON.parse(raw);
|
||
|
||
const subject = formEl.querySelector('input[name="subject"]');
|
||
const wrap = document.getElementById("offerSummaryWrap");
|
||
const offerSummary = document.getElementById("offerSummary");
|
||
|
||
const offerText =
|
||
typeof payload?.message === "string" && payload.message.trim()
|
||
? payload.message
|
||
: null;
|
||
|
||
if (!offerText) return;
|
||
|
||
if (wrap) wrap.classList.remove("hidden");
|
||
|
||
if (subject && !subject.value) {
|
||
subject.value = `Zapytanie: ${payload?.pkg?.name || "Oferta"}`;
|
||
}
|
||
|
||
if (offerSummary) offerSummary.value = offerText;
|
||
|
||
sessionStorage.setItem(SS_INJECTED_KEY, "1");
|
||
} catch {}
|
||
}
|
||
|
||
hydrateOfferIntoForm();
|
||
|
||
window.addEventListener("pagehide", clearOfferDraft);
|
||
window.addEventListener("beforeunload", clearOfferDraft);
|
||
|
||
formEl.addEventListener("submit", async (e) => {
|
||
if (!formEl.reportValidity()) return;
|
||
e.preventDefault();
|
||
|
||
try {
|
||
const data = Object.fromEntries(new FormData(formEl).entries());
|
||
data.rodo = formEl.rodo?.checked === true;
|
||
|
||
const token = await grecaptcha.execute(window.FUZ_RECAPTCHA_KEY, {
|
||
action: "submit",
|
||
});
|
||
data.recaptcha = token;
|
||
|
||
const resp = await fetch("/api/contact/contact", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(data),
|
||
});
|
||
|
||
const json = await resp.json().catch(() => ({}));
|
||
|
||
showToast(
|
||
json?.ok ? successMsg : errorMsg,
|
||
json?.ok ? "success" : "error",
|
||
);
|
||
|
||
if (json?.ok) {
|
||
formEl.reset();
|
||
clearOfferDraft();
|
||
}
|
||
} catch {
|
||
showToast(errorMsg, "error");
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
</DefaultLayout>
|
||
|
||
================================================================================
|
||
|
||
FILE: src/pages/mapa-zasiegu/index.astro
|
||
--------------------------------------------------------------------------------
|
||
---
|
||
import DefaultLayout from "../../layouts/DefaultLayout.astro";
|
||
import MapGoogle from "../../components/maps/MapGoogle.astro";
|
||
import RangeForm from "../../islands/RangeForm.jsx";
|
||
|
||
import "../../styles/map-google.css";
|
||
|
||
const apiKey = import.meta.env.PUBLIC_GOOGLE_MAPS_KEY;
|
||
const lat = 52.597388;
|
||
const lon = 21.456797;
|
||
const mapStyleId = "8e0a97af9476f2d3";
|
||
---
|
||
|
||
<script>
|
||
declare global {
|
||
interface Window {
|
||
showAddressOnMap?: (result: any) => void;
|
||
fuzMaps?: any;
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<DefaultLayout title="FUZ Mapa zasięgu sieci światłowodowej">
|
||
<section class="flex flex-col md:flex-row h-full min-h-[80vh]">
|
||
<!-- PANEL LEWY -->
|
||
<aside
|
||
class="w-full md:w-[340px] bg-[var(--f-background)] text-[var(--f-text)]
|
||
pt-6 px-6 flex flex-col gap-6 overflow-y-auto z-40"
|
||
>
|
||
<h3 class="text-3xl">Sprawdź dostępność usług</h3>
|
||
<p class="text-sm">
|
||
Wybierz swoją miejscowość i ulicę oraz numer budynku, aby sprawdzić
|
||
dostępność usług światłowodowych FUZ.
|
||
</p>
|
||
|
||
<RangeForm client:load />
|
||
</aside>
|
||
|
||
<!-- MAPA -->
|
||
<div class="flex-1 relative min-h-[50vh] md:min-h-0">
|
||
<MapGoogle
|
||
apiKey={apiKey}
|
||
lat={lat}
|
||
lon={lon}
|
||
zoom={17}
|
||
showMarker={true}
|
||
mode="full"
|
||
mapStyleId={mapStyleId}
|
||
/>
|
||
</div>
|
||
</section>
|
||
|
||
<script is:inline>
|
||
let fiberLayer = null;
|
||
|
||
window.getActiveMap = function () {
|
||
if (!window.fuzMaps) return null;
|
||
return Object.values(window.fuzMaps)[0] || null;
|
||
};
|
||
|
||
function enableFiberLayer() {
|
||
const map = window.getActiveMap();
|
||
if (!map) return;
|
||
|
||
if (fiberLayer) {
|
||
fiberLayer.setMap(null);
|
||
}
|
||
|
||
fiberLayer = new google.maps.KmlLayer(
|
||
"https://www.google.com/maps/d/kml?mid=1Or8SF_9qx6QMdidS-99V_jqQuhF9de0&forcekml=1",
|
||
{ suppressInfoWindows: true, preserveViewport: false },
|
||
);
|
||
fiberLayer.setMap(map);
|
||
}
|
||
|
||
// Czekamy aż mapa się załaduje
|
||
const int = setInterval(() => {
|
||
const map = window.getActiveMap();
|
||
if (map && window.google?.maps) {
|
||
clearInterval(int);
|
||
enableFiberLayer();
|
||
}
|
||
}, 100);
|
||
</script>
|
||
|
||
<script is:inline>
|
||
window.showAddressOnMap = async function (result) {
|
||
const map = window.getActiveMap();
|
||
if (!map) return;
|
||
|
||
// Czekamy aż API będzie gotowe
|
||
if (!window.google?.maps?.importLibrary) {
|
||
await new Promise((resolve) => {
|
||
const int = setInterval(() => {
|
||
if (window.google?.maps?.importLibrary) {
|
||
clearInterval(int);
|
||
resolve(true);
|
||
}
|
||
}, 50);
|
||
});
|
||
}
|
||
|
||
const { AdvancedMarkerElement } =
|
||
await google.maps.importLibrary("marker");
|
||
const { InfoWindow } = await google.maps.importLibrary("maps");
|
||
|
||
if (window._activeMarker) window._activeMarker.map = null;
|
||
if (window._activeInfo) window._activeInfo.close();
|
||
|
||
const pos = { lat: result.lat, lng: result.lon };
|
||
|
||
const overlay = document.createElement("div");
|
||
overlay.className = "pulse-marker";
|
||
|
||
const projection = map.getProjection();
|
||
if (projection) {
|
||
const point = projection.fromLatLngToPoint(new google.maps.LatLng(pos));
|
||
overlay.style.left = point.x + "px";
|
||
overlay.style.top = point.y + "px";
|
||
}
|
||
|
||
setTimeout(() => {
|
||
overlay.remove();
|
||
}, 1500);
|
||
|
||
map.getDiv().appendChild(overlay);
|
||
map.panTo(pos);
|
||
let targetZoom = 16;
|
||
|
||
setTimeout(() => {
|
||
map.setZoom(targetZoom);
|
||
}, 250);
|
||
|
||
const marker = new AdvancedMarkerElement({
|
||
map,
|
||
position: pos,
|
||
title: `${result.city} ${result.street ?? ""} ${result.number}`,
|
||
className: "marker-bounce",
|
||
});
|
||
|
||
window._activeMarker = marker;
|
||
|
||
// InfoWindow
|
||
const html = `
|
||
<div class="f-info-window">
|
||
<div class="f-info-header">
|
||
<div class="f-info-heading">
|
||
${
|
||
result.available
|
||
? `<span class="ok">✔</span> Internet światłowodowy dostępny`
|
||
: `<span class="no">✖</span> Światłowód niedostępny`
|
||
}
|
||
</div>
|
||
<div class="f-info-city">${result.city}</div>
|
||
<div class="f-info-street">${result.street ?? ""} ${result.number}</div>
|
||
</div>
|
||
</div>
|
||
<div class="w-full flex justify-center mb-4">
|
||
<a href="/kontakt" class="btn btn-primary">Przejdź do kontaktu →</a>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
const info = new InfoWindow({ content: html });
|
||
window._activeInfo = info;
|
||
|
||
info.open({ map, anchor: marker });
|
||
};
|
||
</script>
|
||
</DefaultLayout>
|
||
|
||
================================================================================
|
||
|
||
FILE: src/pages/premium/[tid].astro
|
||
--------------------------------------------------------------------------------
|
||
---
|
||
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";
|
||
|
||
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 GroupCta = {
|
||
label?: string;
|
||
href?: string;
|
||
title?: string;
|
||
opis?: string;
|
||
};
|
||
|
||
type GroupMeta = {
|
||
tytul?: string;
|
||
rejestracja?: GroupCta;
|
||
};
|
||
|
||
type TvAddonsDoc = {
|
||
tytul?: string;
|
||
opis?: string;
|
||
dodatki?: TvAddon[];
|
||
grupy?: Record<string, GroupMeta>;
|
||
};
|
||
|
||
const doc = yaml.load(
|
||
fs.readFileSync("./src/content/internet-telewizja/tv-addons.yaml", "utf8"),
|
||
) as TvAddonsDoc;
|
||
|
||
const addons = Array.isArray(doc?.dodatki) ? doc.dodatki : [];
|
||
const groupMeta = doc?.grupy ?? {};
|
||
|
||
const tid = Number(Astro.params.tid);
|
||
const picked = addons.find((a) => Number(a?.tid) === tid);
|
||
|
||
if (!picked) {
|
||
return new Response("Nie znaleziono pakietu.", { status: 404 });
|
||
}
|
||
|
||
const pickedGroup = String(picked?.group ?? "").trim();
|
||
|
||
const viewAddons = pickedGroup
|
||
? addons.filter((a) => String(a?.group ?? "").trim() === pickedGroup)
|
||
: [picked];
|
||
|
||
const footerCta =
|
||
pickedGroup ? groupMeta[pickedGroup]?.rejestracja : undefined;
|
||
---
|
||
|
||
<DefaultLayout
|
||
title={picked?.nazwa ?? doc?.tytul}
|
||
description={doc?.opis}
|
||
>
|
||
{
|
||
viewAddons.map((addon, index) => {
|
||
const pkgName = String(addon?.nazwa ?? "").trim();
|
||
const hasYamlImage = !!String(addon?.image ?? "").trim();
|
||
const assumeHasMedia = pkgName || hasYamlImage;
|
||
const isAboveFold = index === 0;
|
||
|
||
return (
|
||
<section class="f-section" id={`tid-${addon.tid}`}>
|
||
<div
|
||
class={`f-section-grid f-addon-section ${
|
||
assumeHasMedia ? "md:grid-cols-2" : "md:grid-cols-1"
|
||
}`}
|
||
data-addon-section
|
||
data-has-media={assumeHasMedia ? "1" : "0"}
|
||
>
|
||
{/* MEDIA — odpowiednik <Image /> */}
|
||
<div class="f-addon-media md:order-2">
|
||
{pkgName ? (
|
||
<AddonChannelsGrid
|
||
client:idle
|
||
packageName={pkgName}
|
||
fallbackImage={String(addon?.image ?? "")}
|
||
aboveFold={isAboveFold}
|
||
title={pkgName}
|
||
/>
|
||
) : null}
|
||
</div>
|
||
|
||
{/* TEKST */}
|
||
<div class="md:order-1">
|
||
{pkgName && <h2 class="f-section-title">{pkgName}</h2>}
|
||
{addon?.opis && <Markdown text={addon.opis} />}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
);
|
||
})
|
||
}
|
||
|
||
{footerCta?.href && footerCta?.label ? (
|
||
<section class="f-section">
|
||
<div class="f-section-grid md:grid-cols-1">
|
||
<div class="fuz-markdown max-w-none">
|
||
{footerCta.opis && <p>{footerCta.opis}</p>}
|
||
<div class="f-section-nav">
|
||
<a
|
||
class="btn btn-primary"
|
||
href={footerCta.href}
|
||
title={footerCta.title ?? footerCta.label}
|
||
>
|
||
{footerCta.label}
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
) : null}
|
||
</DefaultLayout>
|
||
|
||
================================================================================
|
||
|
||
FILE: src/pages/premium/index.astro
|
||
--------------------------------------------------------------------------------
|
||
---
|
||
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";
|
||
|
||
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;
|
||
};
|
||
|
||
// ✅ OPCJA A: metadane grupy + CTA
|
||
type GroupCta = {
|
||
label?: string;
|
||
href?: string;
|
||
title?: string;
|
||
opis?: string;
|
||
};
|
||
|
||
type GroupMeta = {
|
||
tytul?: string;
|
||
rejestracja?: GroupCta;
|
||
};
|
||
|
||
type TvAddonsDoc = {
|
||
tytul?: string;
|
||
opis?: string;
|
||
cena_opis?: string;
|
||
dodatki?: TvAddon[];
|
||
grupy?: Record<string, GroupMeta>; // ✅
|
||
};
|
||
|
||
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 : [];
|
||
|
||
const detailsBase = "/pakiety-premium";
|
||
|
||
// ✅ mapa meta grup (np. hbo_max -> { rejestracja: {...} })
|
||
const groupMeta: Record<string, GroupMeta> = doc?.grupy ?? {};
|
||
|
||
type Group = {
|
||
key: string;
|
||
title?: string;
|
||
items: TvAddon[];
|
||
groupId?: string;
|
||
};
|
||
const groupsMap = new Map<string, Group>();
|
||
|
||
for (const a of addons) {
|
||
const g = String(a?.group ?? "").trim();
|
||
const key = g ? `g:${g}` : `s:${a?.tid ?? a?.id ?? a?.nazwa ?? ""}`;
|
||
|
||
if (!groupsMap.has(key)) {
|
||
groupsMap.set(key, {
|
||
key,
|
||
groupId: g || undefined,
|
||
title: g || undefined,
|
||
items: [],
|
||
});
|
||
}
|
||
groupsMap.get(key)!.items.push(a);
|
||
}
|
||
|
||
const groups: Group[] = Array.from(groupsMap.values());
|
||
---
|
||
|
||
<DefaultLayout title={pageTitle} description={pageDesc}>
|
||
<section class="f-section">
|
||
<div class="f-section-grid-single">
|
||
<h1 class="f-section-title">{pageTitle}</h1>
|
||
{pageDesc && <Markdown text={pageDesc} />}
|
||
</div>
|
||
</section>
|
||
|
||
{
|
||
groups.map((group, groupIndex) => {
|
||
const isSingle = group.key.startsWith("s:");
|
||
const gId = String(group.groupId ?? "").trim(); // np. "hbo_max"
|
||
const meta = gId ? groupMeta[gId] : undefined;
|
||
|
||
const footerCta = meta?.rejestracja;
|
||
const footerTitle =
|
||
meta?.tytul || (gId ? gId.replace(/_/g, " ") : undefined);
|
||
|
||
return (
|
||
<div class={`f-addon-group ${isSingle ? "f-addon-group--single" : ""}`}>
|
||
|
||
{group.items.map((addon: TvAddon, index: number) => {
|
||
const isAboveFold = groupIndex === 0 && index === 0;
|
||
|
||
const pkgName = String(addon?.nazwa ?? "").trim();
|
||
const hasYamlImage = !!String(addon?.image ?? "").trim();
|
||
|
||
// ✅ zachowanie jak wcześniej (1 kolumna + ukrycie media gdy brak)
|
||
const assumeHasMedia = pkgName ? true : hasYamlImage;
|
||
|
||
const href =
|
||
addon?.tid != null ? `${detailsBase}/${addon.tid}` : null;
|
||
|
||
return (
|
||
<section class="f-section f-addon-group-item">
|
||
<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 && <h3 class="f-section-title">{pkgName}</h3>}
|
||
|
||
{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>
|
||
);
|
||
})}
|
||
|
||
{/* ✅ STOPKA GRUPY: przycisk rejestracji dla całej grupy */}
|
||
{!isSingle && footerCta?.href && footerCta?.label ? (
|
||
<div class="f-addon-group-footer fuz-markdown max-w-none">
|
||
{footerCta.opis ? <p>{footerCta.opis}</p> : null}
|
||
<a
|
||
class="btn btn-primary"
|
||
href={footerCta.href}
|
||
title={footerCta.title ?? footerCta.label}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
>
|
||
{footerCta.label}
|
||
</a>
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
);
|
||
})
|
||
}
|
||
</DefaultLayout>
|
||
|
||
================================================================================
|
||
|
||
FILE: src/pages/telefon/index.astro
|
||
--------------------------------------------------------------------------------
|
||
---
|
||
import path from "node:path";
|
||
|
||
import DefaultLayout from "../../layouts/DefaultLayout.astro";
|
||
import SectionRenderer from "../../components/sections/SectionRenderer.astro";
|
||
import OffersPhoneCards from "../../islands/phone/PhoneCards.jsx";
|
||
|
||
import { loadYamlFile } from "../../lib/loadYaml";
|
||
|
||
type SeoYaml = any;
|
||
|
||
type PhoneParam = {
|
||
klucz: string;
|
||
label: string;
|
||
value: string | number;
|
||
};
|
||
|
||
type PhoneCard = {
|
||
nazwa: string;
|
||
widoczny?: boolean;
|
||
popularny?: boolean;
|
||
cena?: { wartosc: number; opis?: string };
|
||
parametry?: PhoneParam[];
|
||
};
|
||
|
||
type PhoneCardsYaml = {
|
||
tytul?: string;
|
||
opis?: string;
|
||
cards?: PhoneCard[];
|
||
};
|
||
|
||
const seo = loadYamlFile<SeoYaml>(
|
||
path.join(process.cwd(), "src", "content", "telefon", "seo.yaml"),
|
||
);
|
||
|
||
const phoneCards = loadYamlFile<PhoneCardsYaml>(
|
||
path.join(process.cwd(), "src", "content", "telefon", "cards.yaml"),
|
||
);
|
||
|
||
const tytul = phoneCards?.tytul ?? "";
|
||
const opis = phoneCards?.opis ?? "";
|
||
|
||
const cards: PhoneCard[] = Array.isArray(phoneCards?.cards)
|
||
? phoneCards.cards.filter((c) => c?.widoczny === true)
|
||
: [];
|
||
---
|
||
|
||
<DefaultLayout seo={seo}>
|
||
<section class="f-section">
|
||
<div class="f-section-grid-single md:grid-cols-1">
|
||
<h1 class="f-section-title">Usługa telefonu</h1>
|
||
|
||
<OffersPhoneCards client:load title={tytul} description={opis} cards={cards} />
|
||
</div>
|
||
</section>
|
||
|
||
<SectionRenderer src="./src/content/telefon/section.yaml" />
|
||
</DefaultLayout>
|
||
|
||
================================================================================
|