Kolejne zmiany,

This commit is contained in:
dm
2025-12-15 11:28:53 +01:00
parent c0b9d5a584
commit 6b5a913666
48 changed files with 1630 additions and 868 deletions

View File

@@ -1,50 +1,163 @@
import nodemailer from "nodemailer";
function esc(str = "") {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
function buildHtmlMail(form) {
const when = new Date().toLocaleString("pl-PL");
return `<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Nowa wiadomość FUZ</title>
</head>
<body style="margin:0;padding:0;background:#f5f7fa;font-family:Arial,Helvetica,sans-serif;">
<table width="100%" cellpadding="0" cellspacing="0" role="presentation" style="background:#f5f7fa;">
<tr>
<td align="center" style="padding:24px;">
<table width="600" cellpadding="0" cellspacing="0" role="presentation"
style="background:#ffffff;border-radius:14px;overflow:hidden;box-shadow:0 8px 24px rgba(0,0,0,0.08);">
<!-- Header -->
<tr>
<td style="background:#0066ff;color:#ffffff;padding:20px 24px;">
<div style="font-size:12px;opacity:0.9;margin-bottom:6px;">FUZ • Formularz kontaktowy</div>
<div style="font-size:20px;font-weight:700;line-height:1.2;">📩 Nowa wiadomość</div>
</td>
</tr>
<!-- Content -->
<tr>
<td style="padding:24px;">
<div style="font-size:14px;color:#111827;margin-bottom:14px;">
Poniżej szczegóły zgłoszenia:
</div>
<table width="100%" cellpadding="0" cellspacing="0" role="presentation"
style="font-size:14px;color:#111827;border-collapse:collapse;">
<tr>
<td style="padding:8px 0;width:160px;color:#374151;"><strong>Imię</strong></td>
<td style="padding:8px 0;">${esc(form.firstName)}</td>
</tr>
<tr>
<td style="padding:8px 0;color:#374151;"><strong>Nazwisko</strong></td>
<td style="padding:8px 0;">${esc(form.lastName)}</td>
</tr>
<tr>
<td style="padding:8px 0;color:#374151;"><strong>Email</strong></td>
<td style="padding:8px 0;">
<a href="mailto:${esc(form.email)}" style="color:#0066ff;text-decoration:none;">
${esc(form.email)}
</a>
</td>
</tr>
<tr>
<td style="padding:8px 0;color:#374151;"><strong>Telefon</strong></td>
<td style="padding:8px 0;">${esc(form.phone)}</td>
</tr>
<tr>
<td style="padding:8px 0;color:#374151;"><strong>Temat</strong></td>
<td style="padding:8px 0;">${esc(form.subject)}</td>
</tr>
</table>
<div style="height:1px;background:#e5e7eb;margin:18px 0;"></div>
<div style="font-size:14px;color:#111827;margin:0 0 8px;font-weight:700;">
Wiadomość:
</div>
<div style="font-size:14px;color:#111827;white-space:pre-line;line-height:1.6;
background:#f8fafc;border:1px solid #e5e7eb;border-radius:12px;padding:14px;">
${esc(form.message)}
</div>
<div style="margin-top:16px;font-size:12px;color:#6b7280;">
Wysłano: ${when}
</div>
</td>
</tr>
<!-- Footer -->
<tr>
<td style="background:#f1f5f9;padding:14px 24px;font-size:12px;color:#6b7280;">
To jest automatyczna wiadomość wygenerowana przez formularz na stronie FUZ.
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>`;
}
export async function POST({ request }) {
try {
const form = await request.json();
// (opcjonalnie) prosta walidacja minimum:
if (!form?.email || !form?.message) {
return new Response(JSON.stringify({ ok: false, error: "Brak danych" }), {
status: 400,
headers: { "Content-Type": "application/json" },
});
}
const transporter = nodemailer.createTransport({
host: import.meta.env.SMTP_HOST,
port: Number(import.meta.env.SMTP_PORT),
secure: true, // true = 465, false = 587
secure: true,
auth: {
user: import.meta.env.SMTP_USER,
pass: import.meta.env.SMTP_PASS,
},
// ⚠️ tylko jeśli masz self-signed / dziwny cert
tls: {
rejectUnauthorized: false,
},
// Uwaga: lepiej NIE wyłączać TLS w prod, ale zostawiam zgodnie z Twoją wersją
tls: { rejectUnauthorized: false },
});
const subject = `FUZ: wiadomość od ${form.firstName || ""} ${form.lastName || ""}`.trim();
const text = `
Imię: ${form.firstName || ""}
Nazwisko: ${form.lastName || ""}
Email: ${form.email || ""}
Telefon: ${form.phone || ""}
Temat: ${form.subject || ""}
Wiadomość:
${form.message || ""}
`.trim();
const html = buildHtmlMail(form);
await transporter.sendMail({
from: `"${import.meta.env.SMTP_FROM_NAME}" <${import.meta.env.SMTP_USER}>`,
to: import.meta.env.SMTP_TO,
subject: `FUZ: wiadomość od ${form.firstName} ${form.lastName}`,
text: `
Imię: ${form.firstName}
Nazwisko: ${form.lastName}
Email: ${form.email}
Telefon: ${form.phone}
Temat: ${form.subject}
Wiadomość:
${form.message}
`.trim(),
subject,
text,
html,
replyTo: form.email ? String(form.email) : undefined, // wygodne do "Odpowiedz"
});
return new Response(
JSON.stringify({ ok: true }),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
return new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
} catch (error) {
console.error("MAIL ERROR:", error);
return new Response(
JSON.stringify({ ok: false }),
{ status: 500, headers: { "Content-Type": "application/json" } }
);
return new Response(JSON.stringify({ ok: false }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
}

View File

@@ -1,83 +0,0 @@
import Database from "better-sqlite3";
const DB_PATH =
process.env.FUZ_DB_PATH || "./src/data/ServicesRange.db";
export async function GET() {
const db = new Database(DB_PATH, { readonly: true });
try {
const addonsRows = db
.prepare(
`
SELECT id, name, type, description
FROM internet_addons
ORDER BY id
`
)
.all();
const optionsRows = db
.prepare(
`
SELECT id, addon_id, code, name, price
FROM internet_addon_options
ORDER BY addon_id, id
`
)
.all();
const byAddon = new Map();
for (const addon of addonsRows) {
byAddon.set(addon.id, {
id: addon.id,
name: addon.name,
type: addon.type, // 'checkbox' / 'select'
description: addon.description || "",
options: [],
});
}
for (const opt of optionsRows) {
const parent = byAddon.get(opt.addon_id);
if (!parent) continue;
parent.options.push({
id: opt.id,
code: opt.code,
name: opt.name,
price: opt.price,
});
}
const data = Array.from(byAddon.values());
return new Response(
JSON.stringify({
ok: true,
count: data.length,
data,
}),
{
status: 200,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
}
);
} catch (err) {
console.error("❌ Błąd w /api/internet/addons:", err);
return new Response(
JSON.stringify({
ok: false,
error: err.message || "DB_ERROR",
}),
{
status: 500,
headers: { "Content-Type": "application/json" },
}
);
} finally {
db.close();
}
}

View File

@@ -1,111 +0,0 @@
// src/pages/api/internet/plans.js
import Database from "better-sqlite3";
const DB_PATH =
process.env.FUZ_DB_PATH || "./src/data/ServicesRange.db";
function getDb() {
return new Database(DB_PATH, { readonly: true });
}
/**
* GET /api/internet/plans?building=1|2&contract=1|2
*/
export function GET({ url }) {
const buildingParam = url.searchParams.get("building");
const contractParam = url.searchParams.get("contract");
const building = buildingParam ? Number(buildingParam) : 1;
const contract = contractParam ? Number(contractParam) : 1;
const db = getDb();
try {
const stmt = db.prepare(
`
SELECT
p.id AS plan_id,
p.name AS plan_name,
p.popular AS plan_popular,
pr.price_monthly AS price_monthly,
pr.price_installation AS price_installation,
f.id AS feature_id,
f.label AS feature_label,
fv.value AS feature_value
FROM internet_plans p
LEFT JOIN internet_plan_prices pr
ON pr.plan_id = p.id
AND pr.building_type = ?
AND pr.contract_type = ?
LEFT JOIN internet_plan_feature_values fv
ON fv.plan_id = p.id
LEFT JOIN internet_features f
ON f.id = fv.feature_id
ORDER BY p.id ASC, f.id ASC;
`.trim()
);
const rows = stmt.all(building, contract);
// grupowanie do struktury: jeden plan = jedna karta
const byPlan = new Map();
for (const row of rows) {
if (!byPlan.has(row.plan_id)) {
byPlan.set(row.plan_id, {
id: row.plan_id,
code: row.plan_code,
name: row.plan_name,
popular: !!row.plan_popular,
price_monthly: row.price_monthly,
price_installation: row.price_installation,
features: [], // później wypełniamy
});
}
if (row.feature_id) {
byPlan.get(row.plan_id).features.push({
id: row.feature_id,
label: row.feature_label,
value: row.feature_value,
});
}
}
const data = Array.from(byPlan.values());
return new Response(
JSON.stringify({
ok: true,
building,
contract,
count: data.length,
data,
}),
{
status: 200,
headers: {
"Content-Type": "application/json; charset=utf-8",
"Cache-Control": "public, max-age=30",
},
}
);
} catch (err) {
console.error("❌ Błąd w /api/internet/plans:", err);
return new Response(
JSON.stringify({ ok: false, error: "DB_ERROR" }),
{
status: 500,
headers: { "Content-Type": "application/json; charset=utf-8" },
}
);
} finally {
db.close();
}
}

View File

@@ -2,10 +2,6 @@ import path from "node:path";
import { XMLParser } from "fast-xml-parser";
import Database from "better-sqlite3";
/* =====================
KONFIG
===================== */
const FEEDS = [
{ url: "https://www.jambox.pl/xml/listakanalow-smart.xml", name: "Smart" },
{ url: "https://www.jambox.pl/xml/listakanalow-optimum.xml", name: "Optimum" },
@@ -15,25 +11,16 @@ const FEEDS = [
{ url: "https://www.jambox.pl/xml/listakanalow-plusbogaty.xml", name: "Bogaty" },
];
// 👉 ustaw jeśli chcesz inną bazę
const DB_PATH =
process.env.FUZ_DB_PATH ||
path.join(process.cwd(), "src", "data", "ServicesRange.db");
/* =====================
DB
===================== */
function getDb() {
const db = new Database(DB_PATH);
db.pragma("journal_mode = WAL");
return db;
}
/* =====================
XML / HTML HELPERS
===================== */
async function fetchXml(url) {
const res = await fetch(url, {
headers: { accept: "application/xml,text/xml,*/*" },
@@ -155,17 +142,9 @@ async function downloadLogoAsBase64(url) {
}
}
/* =====================
API ROUTE
===================== */
export async function POST() {
const db = getDb();
// ⚠️ WYMAGANE:
// CREATE UNIQUE INDEX ux_jambox_channels_nazwa_pckg
// ON jambox_channels(nazwa, pckg_name);
const upsert = db.prepare(`
INSERT INTO jambox_channels (nazwa, pckg_name, image, opis)
VALUES (@nazwa, @pckg_name, @image, @opis)
@@ -174,7 +153,7 @@ export async function POST() {
opis = COALESCE(excluded.opis, jambox_channels.opis)
`);
const logoCache = new Map(); // nazwa(lower) -> base64 | null
const logoCache = new Map();
const rows = [];
try {
@@ -220,7 +199,7 @@ export async function POST() {
{ headers: { "content-type": "application/json; charset=utf-8" } }
);
} catch (e) {
console.error("import jambox_channels:", e);
console.error("import jambox_channels:", e);
return new Response(
JSON.stringify({ ok: false, error: String(e.message || e) }),
{ status: 500 }

View File

@@ -1,30 +1,15 @@
import type { APIRoute } from "astro";
import { XMLParser } from "fast-xml-parser";
import path from "node:path";
import fs from "node:fs/promises";
const URL = "https://www.jambox.pl/xml/mozliwosci.xml";
type Section = {
title: string;
image?: string;
content: string;
};
type ContentBlock =
| { type: "text"; value: string }
| { type: "list"; items: string[] };
function toArray<T>(v: T | T[] | undefined | null): T[] {
function toArray(v) {
if (!v) return [];
return Array.isArray(v) ? v : [v];
}
/* =======================
HTML / XML HELPERS
======================= */
function decodeEntities(input: string): string {
function decodeEntities(input) {
if (!input) return "";
let s = String(input)
@@ -35,9 +20,7 @@ function decodeEntities(input: string): string {
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">");
s = s.replace(/&#(\d+);/g, (_, d) =>
String.fromCodePoint(Number(d))
);
s = s.replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number(d)));
s = s.replace(/&#x([0-9a-fA-F]+);/g, (_, h) =>
String.fromCodePoint(parseInt(h, 16))
);
@@ -45,30 +28,22 @@ function decodeEntities(input: string): string {
return s;
}
/**
* Parsuje HTML:
* - <p>, <div>, <br> zwykłe nowe linie
* - <ul>/<ol><li> markdown lista
*/
function parseHtmlContent(input?: string): ContentBlock[] {
function parseHtmlContent(input) {
if (!input) return [];
let s = decodeEntities(String(input));
// znaczniki list
s = s
.replace(/<\s*(ul|ol)[^>]*>/gi, "\n__LIST_START__\n")
.replace(/<\/\s*(ul|ol)\s*>/gi, "\n__LIST_END__\n")
.replace(/<\s*li[^>]*>/gi, "__LI__")
.replace(/<\/\s*li\s*>/gi, "\n");
// normalne bloki
s = s
.replace(/<\s*br\s*\/?\s*>/gi, "\n")
.replace(/<\/\s*(p|div)\s*>/gi, "\n")
.replace(/<\s*(p|div)[^>]*>/gi, "");
// usuń resztę tagów
s = s.replace(/<[^>]+>/g, "");
s = s
@@ -77,11 +52,11 @@ function parseHtmlContent(input?: string): ContentBlock[] {
.replace(/\n{3,}/g, "\n\n")
.trim();
const blocks: ContentBlock[] = [];
const blocks = [];
const lines = s.split("\n");
let textBuf: string[] = [];
let listBuf: string[] | null = null;
let textBuf = [];
let listBuf = null;
const flushText = () => {
const txt = textBuf.join("\n").trim();
@@ -132,12 +107,11 @@ function parseHtmlContent(input?: string): ContentBlock[] {
return blocks;
}
function blocksToMarkdown(blocks: ContentBlock[]): string {
const out: string[] = [];
function blocksToMarkdown(blocks) {
const out = [];
for (const b of blocks) {
if (b.type === "text") {
// 👉 każde zdanie zakończone kropką = nowa linia
const lines = b.value
.replace(/\.\s+/g, ".\n")
.split("\n")
@@ -157,20 +131,15 @@ function blocksToMarkdown(blocks: ContentBlock[]): string {
return out.join("\n\n").trim();
}
/* =======================
SCREEN / YAML
======================= */
function extractUrlsFromString(s: string): string[] {
return s.match(/https?:\/\/[^\s<"]+/g) ?? [];
function extractUrlsFromString(s) {
return String(s).match(/https?:\/\/[^\s<"]+/g) ?? [];
}
function extractScreens(screen: any): string[] {
function extractScreens(screen) {
if (!screen) return [];
if (typeof screen === "string") return extractUrlsFromString(screen);
const divs = (screen as any)?.div;
const divs = screen?.div;
if (divs) {
return toArray(divs)
.map((d) => (typeof d === "string" ? d : d?.["#text"] ?? ""))
@@ -180,19 +149,19 @@ function extractScreens(screen: any): string[] {
return extractUrlsFromString(JSON.stringify(screen));
}
function yamlQuote(v: string): string {
function yamlQuote(v) {
return `"${String(v).replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
}
function toYaml(sections: Section[]): string {
const out: string[] = ["sections:"];
function toYaml(sections) {
const out = ["sections:"];
for (const s of sections) {
out.push(` - title: ${yamlQuote(s.title)}`);
if (s.image) out.push(` image: ${yamlQuote(s.image)}`);
out.push(" content: |");
for (const line of s.content.split("\n")) {
for (const line of String(s.content).split("\n")) {
out.push(` ${line}`);
}
@@ -202,11 +171,7 @@ function toYaml(sections: Section[]): string {
return out.join("\n").trimEnd() + "\n";
}
/* =======================
API
======================= */
export const POST: APIRoute = async () => {
export async function POST() {
const res = await fetch(URL, {
headers: { accept: "application/xml,text/xml,*/*" },
});
@@ -219,10 +184,10 @@ export const POST: APIRoute = async () => {
const parser = new XMLParser({ trimValues: true });
const parsed = parser.parse(xml);
const nodes = toArray((parsed as any)?.xml?.node ?? (parsed as any)?.node);
const nodes = toArray(parsed?.xml?.node ?? parsed?.node);
const sections: Section[] = nodes
.map((n: any) => {
const sections = nodes
.map((n) => {
const title = parseHtmlContent(n?.title)
.map((b) => (b.type === "text" ? b.value : ""))
.join(" ")
@@ -243,21 +208,15 @@ export const POST: APIRoute = async () => {
return { title, image, content };
})
.filter(Boolean) as Section[];
.filter(Boolean);
const outDir = path.join(
process.cwd(),
"src",
"content",
"internet-telewizja"
);
const outDir = path.join(process.cwd(), "src", "content", "internet-telewizja");
const outFile = path.join(outDir, "telewizja-mozliwosci.yaml");
await fs.mkdir(outDir, { recursive: true });
await fs.writeFile(outFile, toYaml(sections), "utf8");
return new Response(
JSON.stringify({ ok: true, count: sections.length }),
{ headers: { "content-type": "application/json" } }
);
};
return new Response(JSON.stringify({ ok: true, count: sections.length }), {
headers: { "content-type": "application/json" },
});
}

View File

@@ -8,7 +8,6 @@ function getDb() {
function cleanPkgName(v) {
const s = String(v || "").trim();
// prosta sanity: niepuste, nieprzesadnie długie
if (!s) return null;
if (s.length > 64) return null;
return s;
@@ -51,7 +50,7 @@ export function GET({ url }) {
headers: { "Content-Type": "application/json; charset=utf-8" },
});
} catch (err) {
console.error("Błąd w /api/jambox/channels:", err);
console.error("Błąd w /api/jambox/jambox-channels-package:", err);
return new Response(
JSON.stringify({ ok: false, error: "DB_ERROR" }),
{

View File

@@ -14,7 +14,6 @@ function uniq(arr) {
return Array.from(new Set(arr));
}
// jeśli chcesz id do scrollowania (pkg-smart), to możesz dać slug
function slugifyPkg(name) {
return String(name || "")
.toLowerCase()
@@ -28,7 +27,6 @@ export function GET({ url }) {
const q = (url.searchParams.get("q") || "").trim();
const limit = clamp(Number(url.searchParams.get("limit") || 50), 1, 200);
if (q.length < 1) {
return new Response(JSON.stringify({ ok: true, data: [] }), {
status: 200,
@@ -36,7 +34,6 @@ export function GET({ url }) {
});
}
// escape LIKE wildcardów
const safe = q.replace(/[%_]/g, (m) => `\\${m}`);
const like = `%${safe}%`;
@@ -68,19 +65,15 @@ export function GET({ url }) {
const packages = uniq(pkgsRaw)
.map((p) => ({
// jeśli UI wymaga ID do scrolla, to to jest najbezpieczniejsze:
id: slugifyPkg(p), // np. "smart" -> użyjesz jako pkg-smart
id: slugifyPkg(p),
name: p,
number: "—", // brak w nowej tabeli
guaranteed: false, // brak w nowej tabeli
}))
.sort((a, b) => a.name.localeCompare(b.name, "pl"));
return {
name: r.name,
logo_url: r.logo_url || "", // base64 data-url albo ""
logo_url: r.logo_url || "",
description: r.description || "",
min_number: 0, // brak numerów
packages,
};
});
@@ -90,7 +83,7 @@ export function GET({ url }) {
headers: { "Content-Type": "application/json; charset=utf-8" },
});
} catch (err) {
console.error("Błąd w /api/jambox/channels-search:", err);
console.error("Błąd w /api/jambox/jambox-channels-search:", err);
return new Response(JSON.stringify({ ok: false, error: "DB_ERROR" }), {
status: 500,
headers: { "Content-Type": "application/json; charset=utf-8" },

View File

@@ -1,70 +0,0 @@
// src/pages/api/switches.js
import Database from "better-sqlite3";
const DB_PATH =
process.env.FUZ_DB_PATH || "./src/data/ServicesRange.db";
function getDb() {
return new Database(DB_PATH, { readonly: true });
}
export function GET() {
const db = getDb();
try {
const buildingTypes = db
.prepare("SELECT code, label FROM jambox_building_types ORDER BY is_default DESC, code")
.all();
const contractTypes = db
.prepare("SELECT code, label FROM jambox_contract_types ORDER BY is_default DESC, code")
.all();
const switches = [
{
id: "budynek",
etykieta: "Rodzaj budynku",
domyslny: buildingTypes[0]?.code ?? 1,
title: "Zmień rodzaj budynku by zobaczyć odpowiednie ceny",
opcje: buildingTypes.map((b) => ({
id: b.code,
nazwa: b.label,
})),
},
{
id: "umowa",
etykieta: "Okres umowy",
domyslny: contractTypes[0]?.code ?? 1,
title: "Wybierz okres umowy by zobaczyć odpowiednie ceny",
opcje: contractTypes.map((c) => ({
id: c.code,
nazwa: c.label,
})),
},
];
return new Response(
JSON.stringify({ ok: true, data: switches }),
{
status: 200,
headers: {
"Content-Type": "application/json; charset=utf-8",
"Cache-Control": "public, max-age=60",
},
}
);
} catch (err) {
console.error("Błąd w /api/switches:", err);
return new Response(
JSON.stringify({ ok: false, error: err.message || "DB_ERROR" }),
{
status: 500,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
}
);
} finally {
db.close();
}
}

View File

@@ -15,18 +15,19 @@ const html = marked.parse(doc.content);
---
<DefaultLayout title={doc.title}>
<section class="max-w-4xl mx-auto px-4 py-10">
<a href="/dokumenty" class="text-sm opacity-70 hover:opacity-100">
← Wróć do dokumentów
</a>
<section class="f-section">
<div class="f-section-grid-single">
<a href="/dokumenty" class="text-sm opacity-70 hover:opacity-100">
← Wróć do dokumentów
</a>
<h1 class="mt-4 text-4xl md:text-5xl font-bold text-[--f-header]">
{doc.title}
</h1>
<h1 class="f-section-title">
{doc.title}
</h1>
<article class="mt-8 prose max-w-none">
<div class="fuz-markdown max-w-none">
<Markdown text={html} />
<!-- <div set:html={html} /> -->
</article>
</div>
</div>
</section>
</DefaultLayout>

View File

@@ -65,6 +65,24 @@ const addons: Addon[] = Array.isArray(addonsData?.dodatki)
// jeśli chcesz, możesz nadpisać cenaOpis w modalu z addons.yaml:
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: SwitchDef[] = Array.isArray(switchesData?.switches)
? switchesData.switches
: [];
---
<DefaultLayout seo={seo}>
@@ -80,6 +98,7 @@ const addonsCenaOpis = addonsData?.cena_opis ?? cenaOpis;
phoneCards={phoneCards}
addons={addons}
addonsCenaOpis={addonsCenaOpis}
switches={switches}
/>
</div>
</section>

View File

@@ -50,7 +50,7 @@ type PhoneCard = {
};
type PhoneYaml = { cards?: PhoneCard[] };
type Decoder = { id: string; nazwa: string; cena: number };
type Decoder = { id: string; nazwa: string; opis: string; cena: number };
// ✅ dodatki z YAML (do modala)
type Addon = {
@@ -72,16 +72,16 @@ type AddonsYaml = {
dodatki?: Addon[];
};
type ChannelsYaml = {
title?: string;
updated_at?: string;
channels?: Array<{
nazwa: string;
opis?: string;
image?: string;
pakiety?: string[];
}>;
};
// type ChannelsYaml = {
// title?: string;
// updated_at?: string;
// channels?: Array<{
// nazwa: string;
// opis?: string;
// image?: string;
// pakiety?: string[];
// }>;
// };
const seo = loadYamlFile<SeoYaml>(
path.join(process.cwd(), "src", "content", "internet-telewizja", "seo.yaml"),
@@ -141,6 +141,25 @@ const decoders: Decoder[] = Array.isArray(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}>
@@ -159,6 +178,7 @@ const addonsCenaOpis = addonsYaml?.cena_opis ?? cenaOpis;
decoders={decoders}
addons={addons}
addonsCenaOpis={addonsCenaOpis}
switches={switches}
/>
</div>
</section>

View File

@@ -1,21 +0,0 @@
---
import yaml from "js-yaml";
import fs from "fs";
import DefaultLayout from "../../layouts/DefaultLayout.astro";
import Markdown from "../../islands/Markdown.jsx";
const privacy = yaml.load(
fs.readFileSync("./src/content/polityka-prywatnosci/privacy.yaml", "utf8"),
);
---
<DefaultLayout title={privacy.title}>
<section class="f-section">
<div class="f-section-grid-single">
<h1 class="f-section-title">
{privacy.title}
</h1>
<Markdown text={privacy.content} />
</div>
</section>
</DefaultLayout>