Porządkowanie kodu, dodanie sekcji wyszukiwania kanałów
This commit is contained in:
@@ -25,16 +25,13 @@ const {
|
||||
ctas = []
|
||||
} = Astro.props;
|
||||
|
||||
// Wyciągnij nazwę bazową bez rozszerzenia
|
||||
const imageBase = imageUrl.replace(/\.(webp|png|jpg|jpeg)$/i, '');
|
||||
|
||||
// Importuj wszystkie obrazki
|
||||
const images = import.meta.glob<{ default: ImageMetadata }>(
|
||||
'/src/assets/hero/**/*.webp',
|
||||
{ eager: true }
|
||||
);
|
||||
|
||||
// Funkcja do znajdowania obrazka dla danego rozmiaru
|
||||
function findImage(folder: string): ImageMetadata | null {
|
||||
const key = `/src/assets/hero/${folder}/${imageBase}-${folder}.webp`;
|
||||
return images[key]?.default || null;
|
||||
|
||||
@@ -8,6 +8,7 @@ const links = [
|
||||
{ name: "TELEFON", href: "/telefon" },
|
||||
{ name: "ZASIĘG SIECI", href: "/mapa-zasiegu" },
|
||||
{ name: "KONTAKT", href: "/kontakt" },
|
||||
{ name: "DOKUMENTY", href: "/dokumenty" },
|
||||
{
|
||||
name: "BOK",
|
||||
href: "https://panel.fuz.pl/userpanel/auth",
|
||||
|
||||
58
src/components/sections/SectionChannelsSearch.astro
Normal file
58
src/components/sections/SectionChannelsSearch.astro
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
import { Image } from "astro:assets";
|
||||
import type { ImageMetadata } from "astro";
|
||||
import Markdown from "../../islands/Markdown.jsx";
|
||||
import TvChannelsSearch from "../../islands/jambox/JamboxChannelsSearch.jsx";
|
||||
|
||||
const props = Astro.props ?? {};
|
||||
const section = props.section ?? {};
|
||||
const index = Number(props.index ?? 0);
|
||||
|
||||
const hasImage = !!section.image;
|
||||
const reverse = index % 2 === 1;
|
||||
|
||||
const sectionImages = import.meta.glob<{ default: ImageMetadata }>(
|
||||
"/src/assets/sections/**/*.{png,jpg,jpeg,webp,avif}",
|
||||
{ eager: true }
|
||||
);
|
||||
|
||||
let sectionImage: ImageMetadata | null = null;
|
||||
|
||||
if (section.image) {
|
||||
const path = `/src/assets/sections/${section.image}`;
|
||||
const mod = sectionImages[path];
|
||||
if (mod) sectionImage = mod.default;
|
||||
}
|
||||
---
|
||||
|
||||
<section class="f-section">
|
||||
<div class={`f-section-grid ${hasImage ? "md:grid-cols-2" : "md:grid-cols-1"}`}>
|
||||
{sectionImage && (
|
||||
<Image
|
||||
src={sectionImage}
|
||||
alt={section.title ?? "Kanały TV"}
|
||||
class={`f-section-image ${reverse ? "md:order-1" : "md:order-2"} ${section.dimmed ? "f-image-dimmed" : ""}`}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
format="webp"
|
||||
widths={[480, 768, 1024, 1440]}
|
||||
sizes="100vw"
|
||||
/>
|
||||
)}
|
||||
|
||||
<div class={`${hasImage ? (reverse ? "md:order-2" : "md:order-1") : ""}`}>
|
||||
{section.title && <h2 class="f-section-title">{section.title}</h2>}
|
||||
{section.content && <Markdown text={section.content} />}
|
||||
|
||||
<TvChannelsSearch client:load />
|
||||
|
||||
{section.button && (
|
||||
<div class="f-section-nav">
|
||||
<a href={section.button.url} class="btn btn-primary" title={section.button.title}>
|
||||
{section.button.text}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,125 +0,0 @@
|
||||
---
|
||||
import yaml from "js-yaml";
|
||||
import fs from "fs";
|
||||
import MapGoogle from "../../components/maps/MapGoogle.astro";
|
||||
|
||||
const data = yaml.load(
|
||||
fs.readFileSync("./src/content/contact/contact.yaml", "utf8")
|
||||
);
|
||||
|
||||
const apiKey = import.meta.env.PUBLIC_GOOGLE_MAPS_KEY;
|
||||
const form = data.form;
|
||||
---
|
||||
|
||||
<section id="kontakt" class="f-section">
|
||||
<div class="f-contact-grid">
|
||||
<!-- Kolumna lewa -->
|
||||
<div class="f-contact-col-1">
|
||||
<h2>{data.title}</h2>
|
||||
<div class="f-contact-item" set:html={data.description}></div>
|
||||
</div>
|
||||
|
||||
<div class="f-contact-col-2">
|
||||
<h2>{data.contactFormTitle}</h2>
|
||||
<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-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>
|
||||
|
||||
<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 title={form.submit.title}>{form.submit.label}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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 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 form = document.getElementById("contactForm");
|
||||
const toast = document.getElementById("toast");
|
||||
|
||||
if (!form) return;
|
||||
|
||||
form.addEventListener("submit", async (e) => {
|
||||
if (!form.reportValidity()) return;
|
||||
e.preventDefault();
|
||||
|
||||
const data = Object.fromEntries(new FormData(form).entries());
|
||||
data.rodo = form.rodo.checked;
|
||||
|
||||
const token = await grecaptcha.execute(window.FUZ_RECAPTCHA_KEY, { action: "submit" });
|
||||
data.recaptcha = token;
|
||||
|
||||
const resp = await fetch("/api/contact", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
const json = await resp.json();
|
||||
|
||||
showToast(json.ok ? successMsg : errorMsg, json.ok ? "success" : "error");
|
||||
|
||||
if (json.ok) form.reset();
|
||||
});
|
||||
|
||||
function showToast(msg, type) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -43,11 +43,9 @@ if (section.image) {
|
||||
<div
|
||||
class={`f-section-grid ${hasImage ? (reverse ? "md:order-2" : "md:order-1") : ""}`}
|
||||
>
|
||||
<!-- TODO: Styl nagłowka powinien byc trochę niżej -->
|
||||
<h2 class="f-section-title">{section.title}</h2>
|
||||
|
||||
<Markdown text={section.content} />
|
||||
|
||||
{
|
||||
section.button && (
|
||||
<div class="f-section-nav">
|
||||
@@ -63,4 +61,4 @@ if (section.image) {
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
import ChannelSwitcher from "../../islands/ChannelSwitcher.jsx";
|
||||
const { section } = Astro.props;
|
||||
---
|
||||
|
||||
<section class="f-section">
|
||||
<div class="max-w-7xl mx-auto text-center">
|
||||
|
||||
{section.title && (
|
||||
<h2 class="f-section-title">{section.title}</h2>
|
||||
)}
|
||||
|
||||
{section.content && (
|
||||
<div class="f-markdown mb-10" set:html={section.html} />
|
||||
)}
|
||||
|
||||
{section.iframe_sets && section.iframe_sets.length > 0 && (
|
||||
<ChannelSwitcher
|
||||
client:load
|
||||
sets={section.iframe_sets}
|
||||
title={section.title}
|
||||
/>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,67 +0,0 @@
|
||||
---
|
||||
import Markdown from "../../islands/Markdown.jsx";
|
||||
|
||||
// Pobranie XML Jambox
|
||||
const url = "https://www.jambox.pl/xml/mozliwosci.xml";
|
||||
const xmlText: string = await fetch(url).then(r => r.text());
|
||||
|
||||
// Parser wszystkich <node>
|
||||
function parseNodes(xml: string) {
|
||||
return [...xml.matchAll(/<node>([\s\S]*?)<\/node>/g)].map((match) => {
|
||||
const block = match[1];
|
||||
|
||||
const get = (tag: string) => {
|
||||
const m = block.match(new RegExp(`<${tag}>([\\s\\S]*?)<\\/${tag}>`));
|
||||
return m ? m[1].trim() : "";
|
||||
};
|
||||
|
||||
return {
|
||||
title: get("title"),
|
||||
teaser: get("teaser"),
|
||||
description: get("description"),
|
||||
icon: get("icon"),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const nodes = parseNodes(xmlText);
|
||||
---
|
||||
|
||||
<section class="f-section">
|
||||
<h2 class="f-section-title mb-10">Dodatkowe możliwości telewizji JAMBOX</h2>
|
||||
|
||||
{nodes.map((item, i) => {
|
||||
const reverse = i % 2 === 1;
|
||||
|
||||
return (
|
||||
<div class={`f-section-item py-14`}>
|
||||
<div class={`f-section-grid md:grid-cols-2`}>
|
||||
|
||||
<!-- OBRAZ -->
|
||||
<div class={`${reverse ? "md:order-2" : "md:order-1"}`}>
|
||||
<img
|
||||
src={item.icon}
|
||||
alt={item.title}
|
||||
loading="lazy"
|
||||
class="f-section-image rounded-xl shadow-lg"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- TEKST -->
|
||||
<div class={`f-section-grid ${reverse ? "md:order-1" : "md:order-2"}`}>
|
||||
<h3 class="f-section-title text-2xl mb-4">{item.title}</h3>
|
||||
|
||||
{item.teaser && (
|
||||
<p class="text-lg font-medium opacity-80 mb-4">
|
||||
{item.teaser}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<Markdown text={item.description} />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
@@ -4,7 +4,7 @@ import fs from "fs";
|
||||
import { marked } from "marked";
|
||||
|
||||
import SectionDefault from "./SectionDefault.astro";
|
||||
import SectionIframeChannels from "./SectionIframeChannels.astro";
|
||||
|
||||
|
||||
const { src } = Astro.props;
|
||||
|
||||
@@ -19,9 +19,5 @@ const sections = (data.sections as any[]).map((s: any) => ({
|
||||
{sections.map((section: any, index: number) => {
|
||||
const type = section.type || "default";
|
||||
|
||||
if (type === "iframe-channels") {
|
||||
return <SectionIframeChannels section={section} index={index} />;
|
||||
}
|
||||
|
||||
return <SectionDefault section={section} index={index} />;
|
||||
})}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
const { href, variant = "primary" } = Astro.props;
|
||||
|
||||
const base =
|
||||
"inline-flex items-center justify-center rounded-full px-4 py-2 text-sm font-medium transition";
|
||||
const variants = {
|
||||
primary: "bg-sky-600 text-white hover:bg-sky-700 dark:bg-sky-500 dark:hover:bg-sky-400",
|
||||
secondary: "bg-slate-100 text-slate-900 hover:bg-slate-200 dark:bg-slate-800 dark:text-slate-100"
|
||||
};
|
||||
const classes = `${base} ${variants[variant] ?? variants.primary}`;
|
||||
---
|
||||
|
||||
{href ? (
|
||||
<a href={href} class={classes}>
|
||||
<slot />
|
||||
</a>
|
||||
) : (
|
||||
<button type="button" class={classes}>
|
||||
<slot />
|
||||
</button>
|
||||
)}
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
const { message, type = "success" } = Astro.props;
|
||||
|
||||
const base = "fixed right-4 top-20 z-50 rounded-xl px-4 py-3 text-sm shadow-lg border backdrop-blur";
|
||||
const variants = {
|
||||
success: "bg-emerald-50/90 border-emerald-200 text-emerald-900 dark:bg-emerald-900/70 dark:border-emerald-700 dark:text-emerald-50",
|
||||
error: "bg-rose-50/90 border-rose-200 text-rose-900 dark:bg-rose-900/70 dark:border-rose-700 dark:text-rose-50",
|
||||
info: "bg-sky-50/90 border-sky-200 text-sky-900 dark:bg-sky-900/70 dark:border-sky-700 dark:text-sky-50"
|
||||
};
|
||||
const classes = `${base} ${variants[type] ?? variants.success}`;
|
||||
---
|
||||
|
||||
<div class={classes}>
|
||||
{message}
|
||||
</div>
|
||||
Reference in New Issue
Block a user