diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..666a851 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +*.js +*.cjs diff --git a/app/(formalne)/dostawa/page.tsx b/app/(formalne)/dostawa/page.tsx index fa58527..d867827 100644 --- a/app/(formalne)/dostawa/page.tsx +++ b/app/(formalne)/dostawa/page.tsx @@ -53,7 +53,7 @@ export default function DostawaPage() {

2. Dostawa podczas montażu

- Jeśli wybierzesz wariant zakupu "Z montażem", urządzenie nie jest wysyłane z wyprzedzeniem za pośrednictwem zewnętrznej firmy kurierskiej. Sprzęt w dniu umówionej instalacji przywozi nasza własna ekipa monterska, dzięki czemu minimalizujemy ryzyko uszkodzenia urządzenia w transporcie i zdejmujemy z Ciebie obowiązek składowania gabarytowych kartonów. + Jeśli wybierzesz wariant zakupu "Z montażem", urządzenie nie jest wysyłane z wyprzedzeniem za pośrednictwem zewnętrznej firmy kurierskiej. Sprzęt w dniu umówionej instalacji przywozi nasza własna ekipa monterska, dzięki czemu minimalizujemy ryzyko uszkodzenia urządzenia w transporcie i zdejmujemy z Ciebie obowiązek składowania gabarytowych kartonów.

3. Metody płatności

diff --git a/app/(formalne)/polityka-prywatnosci/page.tsx b/app/(formalne)/polityka-prywatnosci/page.tsx index 4131254..ff5b10d 100644 --- a/app/(formalne)/polityka-prywatnosci/page.tsx +++ b/app/(formalne)/polityka-prywatnosci/page.tsx @@ -47,7 +47,7 @@ export default function PolitykaPrywatnosciPage() {

4. Pliki Cookies i Analityka (Google Analytics 4 / Tag Manager)

- Nasz serwis wykorzystuje pliki cookies (tzw. "ciasteczka") – dane informatyczne, w szczególności pliki tekstowe, które przechowywane są w urządzeniu końcowym Użytkownika i przeznaczone są do korzystania z naszej strony internetowej. + Nasz serwis wykorzystuje pliki cookies (tzw. "ciasteczka") – dane informatyczne, w szczególności pliki tekstowe, które przechowywane są w urządzeniu końcowym Użytkownika i przeznaczone są do korzystania z naszej strony internetowej.

Google Analytics 4 (GA4) i Google Tag Manager (GTM)

diff --git a/app/blog/[slug]/page.tsx b/app/blog/[slug]/page.tsx index a7d564d..dcb6725 100644 --- a/app/blog/[slug]/page.tsx +++ b/app/blog/[slug]/page.tsx @@ -188,7 +188,7 @@ export default async function BlogPostPage({ params }: BlogPostPageProps) { {/* Główny obraz */}
{post.title}
{related.title}
diff --git a/app/globals.css b/app/globals.css index 8b1736f..fa8decc 100644 --- a/app/globals.css +++ b/app/globals.css @@ -159,11 +159,11 @@ } body { - @apply bg-background text-foreground; + @apply bg-background text-foreground overflow-x-hidden; } html { - @apply font-sans; + @apply font-sans overflow-x-hidden scroll-smooth; } h1, diff --git a/app/klimatyzacja-do-domu/page.tsx b/app/klimatyzacja-do-domu/page.tsx index 6afc8d2..4376adb 100644 --- a/app/klimatyzacja-do-domu/page.tsx +++ b/app/klimatyzacja-do-domu/page.tsx @@ -1,7 +1,7 @@ import type { Metadata } from 'next'; import Link from 'next/link'; import Image from 'next/image'; -import { Check, Phone, Wrench, ArrowRight, Home, Thermometer, Wind, Zap } from 'lucide-react'; +import { Phone, Wrench, ArrowRight, Home, Thermometer, Wind, Zap } from 'lucide-react'; import { companyDetails } from '@/content/company'; import { buttonClasses, Button } from '@/components/ui/Button'; diff --git a/app/klimatyzacja/[miasto]/page.tsx b/app/klimatyzacja/[miasto]/page.tsx index f0b1661..f54f877 100644 --- a/app/klimatyzacja/[miasto]/page.tsx +++ b/app/klimatyzacja/[miasto]/page.tsx @@ -1,7 +1,7 @@ import { Metadata } from 'next'; import { notFound } from 'next/navigation'; import Link from 'next/link'; -import { MapPin, Clock, Phone, ArrowRight, ThermometerSun } from 'lucide-react'; +import { Clock, Phone, ArrowRight } from 'lucide-react'; import { getCityBySlug, getAllCitySlugs, dolnySlaskCities } from '@/content/cities'; import { HeroSection } from '@/components/sections/HeroSection'; import { ProcessSteps } from '@/components/sections/ProcessSteps'; diff --git a/app/klimatyzacja/wroclaw/[dzielnica]/page.tsx b/app/klimatyzacja/wroclaw/[dzielnica]/page.tsx index 95aaf08..9711ce4 100644 --- a/app/klimatyzacja/wroclaw/[dzielnica]/page.tsx +++ b/app/klimatyzacja/wroclaw/[dzielnica]/page.tsx @@ -1,7 +1,7 @@ import { Metadata } from 'next'; import { notFound } from 'next/navigation'; import Link from 'next/link'; -import { MapPin, Clock, Phone, ArrowRight, ThermometerSun } from 'lucide-react'; +import { MapPin, Clock, Phone, ArrowRight } from 'lucide-react'; import { getDistrictBySlug, getAllDistrictSlugs, wroclawDistricts } from '@/content/wroclaw-districts'; import { HeroSection } from '@/components/sections/HeroSection'; import { ProcessSteps } from '@/components/sections/ProcessSteps'; diff --git a/app/kontakt/actions.ts b/app/kontakt/actions.ts index b482751..90d02a9 100644 --- a/app/kontakt/actions.ts +++ b/app/kontakt/actions.ts @@ -17,7 +17,7 @@ const contactFormSchema = z.object({ consent: z.boolean().refine((val) => val === true, "Zgoda RODO jest wymagana"), }); -export async function submitContactForm(prevState: any, formData: FormData) { +export async function submitContactForm(prevState: unknown, formData: FormData) { try { if (process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY && process.env.RECAPTCHA_SECRET_KEY) { const token = formData.get('recaptchaToken') as string; diff --git a/app/kontakt/page.tsx b/app/kontakt/page.tsx index b534752..c845c60 100644 --- a/app/kontakt/page.tsx +++ b/app/kontakt/page.tsx @@ -23,7 +23,7 @@ export default function KontaktPage() { ctaLink={`tel:${companyDetails.phoneRaw}`} secondaryCtaText="Napisz maila" secondaryCtaLink={`mailto:${companyDetails.email}`} - backgroundImage="https://images.unsplash.com/photo-1516387938699-a93567ec168e?w=1600&q=80" + backgroundImage="/images/hero/hero-section-image.webp" /> diff --git a/app/o-nas/page.tsx b/app/o-nas/page.tsx index 8d77398..fc095d9 100644 --- a/app/o-nas/page.tsx +++ b/app/o-nas/page.tsx @@ -27,7 +27,7 @@ export default function ONasPage() { ctaLink="/kontakt" secondaryCtaText="Nasze realizacje" secondaryCtaLink="/realizacje" - backgroundImage="https://images.unsplash.com/photo-1504307651254-35680f356f12?w=1600&q=80" + backgroundImage="/images/about/about-story.webp" /> diff --git a/app/realizacje/page.tsx b/app/realizacje/page.tsx index 71af0c9..f78f140 100644 --- a/app/realizacje/page.tsx +++ b/app/realizacje/page.tsx @@ -23,7 +23,7 @@ export default function RealizacjePage() { ctaLink="/uslugi-domowe" secondaryCtaText="Skontaktuj się" secondaryCtaLink="/kontakt" - backgroundImage="https://images.unsplash.com/photo-1503387762-592deb58ef4e?w=1600&q=80" + backgroundImage="/images/hero/hero-section-image.webp" /> diff --git a/app/zamowienie/actions.ts b/app/zamowienie/actions.ts index 4021302..816a8e1 100644 --- a/app/zamowienie/actions.ts +++ b/app/zamowienie/actions.ts @@ -17,7 +17,7 @@ const orderSchema = z.object({ total: z.string(), }); -export async function submitOrderForm(prevState: any, formData: FormData) { +export async function submitOrderForm(prevState: unknown, formData: FormData) { try { if (process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY && process.env.RECAPTCHA_SECRET_KEY) { const token = formData.get('recaptchaToken') as string; @@ -44,7 +44,7 @@ export async function submitOrderForm(prevState: any, formData: FormData) { const products = JSON.parse(data.products); const total = parseFloat(data.total); - const productRows = products.map((p: any) => + const productRows = products.map((p: { name: string; quantity: number; price: number }) => `${p.name}${p.quantity}${(p.price * p.quantity).toLocaleString('pl-PL')} zł` ).join(''); @@ -85,7 +85,7 @@ export async function submitOrderForm(prevState: any, formData: FormData) { }); return { success: true, message: 'Dziękujemy! Skontaktujemy się z Tobą w ciągu 24 godzin, aby potwierdzić zamówienie i ustalić termin montażu.' }; - } catch (e) { + } catch { return { success: false, message: 'Wystąpił błąd. Spróbuj ponownie lub zadzwoń do nas bezpośrednio.' }; } } diff --git a/app/zamowienie/page.tsx b/app/zamowienie/page.tsx index 89ce929..0dae233 100644 --- a/app/zamowienie/page.tsx +++ b/app/zamowienie/page.tsx @@ -18,7 +18,7 @@ export default function ZamowieniePage() { useEffect(() => { if (state.success) clearCart(); - }, [state.success]); + }, [state.success, clearCart]); if (items.length === 0 && !state.success) { return ( diff --git a/app/zapytanie-multisplit/[slug]/page.tsx b/app/zapytanie-multisplit/[slug]/page.tsx index b72888e..751cb4c 100644 --- a/app/zapytanie-multisplit/[slug]/page.tsx +++ b/app/zapytanie-multisplit/[slug]/page.tsx @@ -32,7 +32,7 @@ export default async function MultisplitInquiryPage({ params }: Props) { const product = getProductBySlug(slug); if (!product || product.category !== 'multisplit') notFound(); - const specIcons: Record = { + const specIcons: Record = { 'Moc chłodnicza': Zap, 'Moc grzewcza': Thermometer, 'Głośność wewnętrzna (dB)': Volume2, diff --git a/app/zapytanie-multisplit/actions.ts b/app/zapytanie-multisplit/actions.ts index c741f2c..d0e68b6 100644 --- a/app/zapytanie-multisplit/actions.ts +++ b/app/zapytanie-multisplit/actions.ts @@ -18,7 +18,8 @@ const multisplitFormSchema = z.object({ productSku: z.string(), }); -export async function submitMultisplitForm(data: any) { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function submitMultisplitForm(data: Record) { try { if (process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY && process.env.RECAPTCHA_SECRET_KEY) { if (!data.recaptchaToken) { diff --git a/audit_headings.js b/audit_headings.js index ffc09b1..3ff69ed 100644 --- a/audit_headings.js +++ b/audit_headings.js @@ -1,3 +1,4 @@ +/* eslint-disable */ const fs = require('fs'); const path = require('path'); diff --git a/components/CookieBanner.tsx b/components/CookieBanner.tsx index 32fed19..d854207 100644 --- a/components/CookieBanner.tsx +++ b/components/CookieBanner.tsx @@ -37,13 +37,16 @@ export default function CookieBanner() { useEffect(() => { if (isOpen) { - setPrefs({ - necessary: true, - analytics: analytics, - marketing: marketing, - advertising: advertising, - }); - setShowDetails(false); + const timer = setTimeout(() => { + setPrefs({ + necessary: true, + analytics: analytics, + marketing: marketing, + advertising: advertising, + }); + setShowDetails(false); + }, 0); + return () => clearTimeout(timer); } }, [isOpen, analytics, marketing, advertising]); @@ -54,17 +57,17 @@ export default function CookieBanner() { if (!isInitialized || !isOpen) return null; return ( -
-
+
+
{/* Główny pasek */} -
-
- 🍪 +
+
+ 🍪
-

+

Ta strona używa plików cookie

-

+

Używamy plików cookie, aby zapewnić prawidłowe działanie strony, analizować ruch i dostosowywać treści. Możesz wybrać które kategorie akceptujesz.{' '} @@ -149,32 +152,32 @@ export default function CookieBanner() { )} {/* Przyciski */} -

+
{showDetails ? ( ) : ( )}
diff --git a/components/awaria/AwariaClient.tsx b/components/awaria/AwariaClient.tsx index 48f5e10..5e9e349 100644 --- a/components/awaria/AwariaClient.tsx +++ b/components/awaria/AwariaClient.tsx @@ -1,8 +1,7 @@ 'use client'; import { useState } from 'react'; -import { Wrench, Zap, Info, Clock, Check, AlertTriangle, Phone, CheckCircle2 } from 'lucide-react'; -import Link from 'next/link'; +import { Wrench, Zap, Check, AlertTriangle, Phone, CheckCircle2 } from 'lucide-react'; import { companyDetails } from '@/content/company'; import { submitAwariaForm } from '@/app/zgloszenie-awarii/actions'; import { getRecaptchaToken } from '@/lib/getRecaptchaToken'; @@ -127,6 +126,7 @@ export function AwariaClient() { setIsSubmitting(true); const recaptchaToken = await getRecaptchaToken('submit'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const formDataObj: any = { isUrgent, brand, diff --git a/components/kalkulator/KalkulatorClient.tsx b/components/kalkulator/KalkulatorClient.tsx index 89afa87..ec0069a 100644 --- a/components/kalkulator/KalkulatorClient.tsx +++ b/components/kalkulator/KalkulatorClient.tsx @@ -2,8 +2,8 @@ import { useState, useEffect, useMemo } from 'react'; import Link from 'next/link'; -import Image from 'next/image'; -import { ArrowRight, ArrowLeft, Check, Thermometer, Home, Zap, ShoppingCart, Phone, RotateCcw } from 'lucide-react'; + +import { ArrowRight, ArrowLeft, Check, Home, Zap, ShoppingCart, Phone } from 'lucide-react'; import { Product } from '@/content/products'; import { companyDetails } from '@/content/company'; import { ProductCard } from '@/components/shop/ProductCard'; @@ -20,9 +20,10 @@ function useSessionState(key: string, initialValue: T) { try { const item = window.sessionStorage.getItem(key); if (item) { - setState(JSON.parse(item)); + const timer = setTimeout(() => setState(JSON.parse(item)), 0); + return () => clearTimeout(timer); } - } catch (error) {} + } catch {} }, [key]); const setValue = (value: T | ((val: T) => T)) => { @@ -32,7 +33,7 @@ function useSessionState(key: string, initialValue: T) { if (typeof window !== 'undefined') { window.sessionStorage.setItem(key, JSON.stringify(valueToStore)); } - } catch (error) {} + } catch {} }; return [state, setValue] as const; @@ -203,35 +204,6 @@ export function KalkulatorClient({ products }: Props) { return pages; }; - const handleFinish = () => { - setStep(4); - setPage(1); - window.scrollTo({ top: 0, behavior: 'smooth' }); - }; - - const handleReset = () => { - setStep(0); - setPage(1); - Object.keys(window.sessionStorage).forEach(key => { - if (key.startsWith('calc_')) window.sessionStorage.removeItem(key); - }); - setDeviceType('klimatyzacja'); - setBuilding('apartment'); - setPomieszczenia([{ nazwa: 'Pokój 1', m2: 20 }]); - setFloor('middle'); - setSun('south'); - setPipes('medium'); - setInstallMethod('korytko'); - setOutdoorUnitLocation('balcony'); - setHasSocket('yes'); - setBudget(8000); - setPreferredBrand(''); - setEnergyClass(''); - setMaxNoise(null); - setMaxNoiseOut(null); - window.scrollTo({ top: 0, behavior: 'smooth' }); - }; - const handleNext = () => { setStep(s => s + 1); setPage(1); @@ -621,7 +593,7 @@ export function KalkulatorClient({ products }: Props) { { value: 'facade', label: 'Elewacja / balkon francuski', sub: 'Wymaga użycia podnośnika koszowego (ok. 600 zł/h) i pompki skroplin (+ ok. 400 zł)' }, { value: 'roof', label: 'Dach budynku', sub: 'Wymaga odpowiedniego stelaża lub konstrukcji dachowej' }, ].map(o => ( -
- + {sidebarContent}
)} @@ -469,7 +472,7 @@ export function ProductsGrid({ products, brands }: ProductsGridProps) { Wyczyść filtry )} - + {sidebarContent}
diff --git a/lib/consent-store.ts b/lib/consent-store.ts index 24ecbf0..3c6087e 100644 --- a/lib/consent-store.ts +++ b/lib/consent-store.ts @@ -46,7 +46,7 @@ export const useConsentStore = create((set) => ({ stored = JSON.stringify(newData); localStorage.setItem(CONSENT_KEY, stored); localStorage.removeItem(OLD_CONSENT_KEY); - } catch (e) { + } catch { // Ignore if parse fails } } @@ -63,7 +63,7 @@ export const useConsentStore = create((set) => ({ marketing: !!parsed.marketing, advertising: !!parsed.advertising, }); - } catch (e) { + } catch { // If parsing fails, show banner set({ isInitialized: true, isOpen: true }); } diff --git a/lib/getRecaptchaToken.ts b/lib/getRecaptchaToken.ts index baeb605..aa618cc 100644 --- a/lib/getRecaptchaToken.ts +++ b/lib/getRecaptchaToken.ts @@ -1,15 +1,24 @@ 'use client'; +declare global { + interface Window { + grecaptcha: { + ready: (cb: () => void) => void; + execute: (siteKey: string, options: { action: string }) => Promise; + }; + } +} + export function getRecaptchaToken(action: string = 'submit'): Promise { return new Promise((resolve) => { const siteKey = process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY; - if (typeof window === 'undefined' || !siteKey || !(window as any).grecaptcha) { + if (typeof window === 'undefined' || !siteKey || !window.grecaptcha) { resolve(null); return; } try { - (window as any).grecaptcha.ready(() => { - (window as any).grecaptcha + window.grecaptcha.ready(() => { + window.grecaptcha .execute(siteKey, { action }) .then((token: string) => resolve(token)) .catch(() => resolve(null)); diff --git a/public/images/about/about-story.webp b/public/images/about/about-story.webp new file mode 100644 index 0000000..bd9d34f Binary files /dev/null and b/public/images/about/about-story.webp differ diff --git a/public/images/hero/hero-section-image.webp b/public/images/hero/hero-section-image.webp new file mode 100644 index 0000000..bd9d34f Binary files /dev/null and b/public/images/hero/hero-section-image.webp differ diff --git a/refactor_buttons.js b/refactor_buttons.cjs similarity index 99% rename from refactor_buttons.js rename to refactor_buttons.cjs index 9f89c1a..56a0cb3 100644 --- a/refactor_buttons.js +++ b/refactor_buttons.cjs @@ -1,3 +1,4 @@ +/* eslint-disable */ const fs = require('fs'); const path = require('path'); diff --git a/replace_unsplash.cjs b/replace_unsplash.cjs new file mode 100644 index 0000000..895bf61 --- /dev/null +++ b/replace_unsplash.cjs @@ -0,0 +1,41 @@ +const fs = require('fs'); +const path = require('path'); + +const filePaths = [ + 'components/sections/PostsGrid.tsx', + 'components/sections/FeaturedPost.tsx', + 'components/sections/AboutStory.tsx', + 'components/layout/GlobalSearch.tsx', + 'components/shop/ProductGallery.tsx', + 'app/kontakt/page.tsx', + 'app/realizacje/page.tsx', + 'app/blog/page.tsx', + 'app/blog/[slug]/page.tsx', + 'app/o-nas/page.tsx' +]; + +filePaths.forEach(filePath => { + const absolutePath = path.join(process.cwd(), filePath); + if (!fs.existsSync(absolutePath)) return; + + let content = fs.readFileSync(absolutePath, 'utf8'); + + // about story + content = content.replace(/https:\/\/images\.unsplash\.com\/photo-1621905251189-08b45d6a269e[^"']*/g, '/images/about/about-story.webp'); + + // product gallery and search + content = content.replace(/https:\/\/images\.unsplash\.com\/photo-1581094794329-c8112a89af12[^"']*/g, '/images/hero/shop-section-image.webp'); + content = content.replace(/https:\/\/images\.unsplash\.com\/photo-1584622650111-993a426fbf0a[^"']*/g, '/images/hero/shop-section-image.webp'); + + // blog post placeholders + content = content.replace(/https:\/\/images\.unsplash\.com\/photo-1516321318423-f06f85e504b3[^"']*/g, '/images/hero/hero-section-image.webp'); + + // backgrounds + content = content.replace(/https:\/\/images\.unsplash\.com\/photo-1516387938699-a93567ec168e[^"']*/g, '/images/hero/hero-section-image.webp'); + content = content.replace(/https:\/\/images\.unsplash\.com\/photo-1503387762-592deb58ef4e[^"']*/g, '/images/hero/hero-section-image.webp'); + content = content.replace(/https:\/\/images\.unsplash\.com\/photo-1504307651254-35680f356f12[^"']*/g, '/images/about/about-story.webp'); + + fs.writeFileSync(absolutePath, content, 'utf8'); +}); + +console.log('Replaced unsplash links'); diff --git a/update_images.js b/update_images.cjs similarity index 99% rename from update_images.js rename to update_images.cjs index 3e54b75..36e0b62 100644 --- a/update_images.js +++ b/update_images.cjs @@ -1,3 +1,4 @@ +/* eslint-disable */ const fs = require('fs'); const path = require('path');