changes
This commit is contained in:
+5
-10
@@ -1,5 +1,5 @@
|
|||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
import { HeroSection } from '@/components/sections/HeroSection';
|
import { PageHeader } from '@/components/layout/PageHeader';
|
||||||
import { PostsGrid } from '@/components/sections/PostsGrid';
|
import { PostsGrid } from '@/components/sections/PostsGrid';
|
||||||
import { CtaBanner } from '@/components/sections/CtaBanner';
|
import { CtaBanner } from '@/components/sections/CtaBanner';
|
||||||
import { getPosts, getFeaturedPost } from '@/content/posts';
|
import { getPosts, getFeaturedPost } from '@/content/posts';
|
||||||
@@ -23,15 +23,10 @@ export default function BlogPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<HeroSection
|
<PageHeader
|
||||||
|
title="Baza Wiedzy"
|
||||||
title="Blog i poradniki"
|
subtitle="Poradniki, nowości i ekspercka wiedza na temat klimatyzacji."
|
||||||
subtitle="Dzielimy się naszą wiedzą i doświadczeniem. Zobacz, jak dbać o domowe instalacje, na co uważać podczas remontu i jak uzyskać dofinansowanie."
|
breadcrumbs={[{ label: 'Blog', href: '/blog' }]}
|
||||||
ctaText="Skontaktuj się z nami"
|
|
||||||
ctaLink="/kontakt"
|
|
||||||
secondaryCtaText="Nasze usługi"
|
|
||||||
secondaryCtaLink="/uslugi-domowe"
|
|
||||||
backgroundImage="/images/hero/hero-section-image.webp"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<section className="py-16 md:py-24 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<section className="py-16 md:py-24 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
import { JsonLd } from '@/components/ui/JsonLd';
|
import { JsonLd } from '@/components/ui/JsonLd';
|
||||||
import { HeroSection } from '@/components/sections/HeroSection';
|
import { PageHeader } from '@/components/layout/PageHeader';
|
||||||
import { InfoHighlights } from '@/components/sections/InfoHighlights';
|
import { InfoHighlights } from '@/components/sections/InfoHighlights';
|
||||||
import { AcTypesSection } from '@/components/sections/AcTypesSection';
|
import { AcTypesSection } from '@/components/sections/AcTypesSection';
|
||||||
import { ProcessSteps } from '@/components/sections/ProcessSteps';
|
import { ProcessSteps } from '@/components/sections/ProcessSteps';
|
||||||
@@ -74,21 +74,10 @@ export default function KlimatyzacjaPage() {
|
|||||||
<>
|
<>
|
||||||
<JsonLd data={faqJsonLd} />
|
<JsonLd data={faqJsonLd} />
|
||||||
<main>
|
<main>
|
||||||
<HeroSection
|
<PageHeader
|
||||||
|
|
||||||
title="Montaż klimatyzacji — dobór, instalacja, serwis"
|
title="Montaż klimatyzacji — dobór, instalacja, serwis"
|
||||||
subtitle="Zapewnij sobie idealny klimat w domu i biurze. Oferujemy kompleksowe usługi od bezpłatnego audytu po montaż i opiekę serwisową."
|
subtitle="Zapewnij sobie idealny klimat w domu i biurze. Oferujemy kompleksowe usługi od bezpłatnego audytu po montaż i opiekę serwisową."
|
||||||
ctaText="Dobierz klimatyzator"
|
breadcrumbs={[{ label: 'App', href: '/app' }]}
|
||||||
ctaLink="/sklep"
|
|
||||||
secondaryCtaText="Sprawdź cennik"
|
|
||||||
secondaryCtaLink="#cennik"
|
|
||||||
backgroundImage="/images/services/montaz.webp"
|
|
||||||
features={[
|
|
||||||
'Bezpłatny audyt',
|
|
||||||
'Estetyczny montaż',
|
|
||||||
'Zgodność z F-gaz',
|
|
||||||
'Serwis gwarancyjny'
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<InfoHighlights
|
<InfoHighlights
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
import { HeroSection } from '@/components/sections/HeroSection';
|
import { PageHeader } from '@/components/layout/PageHeader';
|
||||||
import { ContactSection } from '@/components/sections/ContactSection';
|
import { ContactSection } from '@/components/sections/ContactSection';
|
||||||
import { companyDetails } from '@/content/company';
|
import { companyDetails } from '@/content/company';
|
||||||
|
|
||||||
@@ -15,15 +15,10 @@ export const metadata: Metadata = {
|
|||||||
export default function KontaktPage() {
|
export default function KontaktPage() {
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<HeroSection
|
<PageHeader
|
||||||
|
|
||||||
title="Jesteśmy do Twojej dyspozycji"
|
title="Jesteśmy do Twojej dyspozycji"
|
||||||
subtitle="Zastanawiasz się nad montażem klimatyzacji ? Potrzebujesz serwisu? Napisz lub zadzwoń — odpowiemy szybko i bezpłatnie."
|
subtitle="Zastanawiasz się nad montażem klimatyzacji ? Potrzebujesz serwisu? Napisz lub zadzwoń — odpowiemy szybko i bezpłatnie."
|
||||||
ctaText="Zadzwoń do nas"
|
breadcrumbs={[{ label: 'App', href: '/app' }]}
|
||||||
ctaLink={`tel:${companyDetails.phoneRaw}`}
|
|
||||||
secondaryCtaText="Napisz maila"
|
|
||||||
secondaryCtaLink={`mailto:${companyDetails.email}`}
|
|
||||||
backgroundImage="/images/hero/hero-section-image.webp"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ContactSection />
|
<ContactSection />
|
||||||
|
|||||||
@@ -204,11 +204,9 @@ export default function MontazKlimatyzacjiPage() {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-6 text-center">
|
<div className="flex flex-col sm:flex-row gap-4 justify-center mt-12 animate-fade-in-up" style={{ animationDelay: '0.4s' }}>
|
||||||
<Link href="/kontakt" className="inline-flex items-center gap-2 px-6 py-3 bg-accent text-white font-bold rounded-xl text-sm hover:bg-accent/90 transition-colors">
|
<Link href="/kontakt" className={buttonClasses({ variant: 'primary', size: 'lg' })}>
|
||||||
|
Darmowa wycena <ArrowRight className="size-4" />
|
||||||
Zapytaj o bezpłatną wycenę <ArrowRight className="size-4" />
|
|
||||||
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+4
-9
@@ -1,5 +1,5 @@
|
|||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
import { HeroSection } from '@/components/sections/HeroSection';
|
import { PageHeader } from '@/components/layout/PageHeader';
|
||||||
import { AboutStory } from '@/components/sections/AboutStory';
|
import { AboutStory } from '@/components/sections/AboutStory';
|
||||||
import { ValuesGrid } from '@/components/sections/ValuesGrid';
|
import { ValuesGrid } from '@/components/sections/ValuesGrid';
|
||||||
import { CtaBanner } from '@/components/sections/CtaBanner';
|
import { CtaBanner } from '@/components/sections/CtaBanner';
|
||||||
@@ -16,15 +16,10 @@ export const metadata: Metadata = {
|
|||||||
export default function ONasPage() {
|
export default function ONasPage() {
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<HeroSection
|
<PageHeader
|
||||||
|
title="Jesteśmy zgranym zespołem inżynierów, instalatorów i serwisantów. Od lat dostarczamy sprawdzone rozwiązania z zakresu klimatyzacji, ogrzewania i wykończeń."
|
||||||
title={`O firmie ${companyDetails.shortName}`}
|
|
||||||
subtitle="Jesteśmy zgranym zespołem inżynierów, instalatorów i serwisantów. Od lat dostarczamy sprawdzone rozwiązania z zakresu klimatyzacji, ogrzewania i wykończeń."
|
subtitle="Jesteśmy zgranym zespołem inżynierów, instalatorów i serwisantów. Od lat dostarczamy sprawdzone rozwiązania z zakresu klimatyzacji, ogrzewania i wykończeń."
|
||||||
ctaText="Skontaktuj się z nami"
|
breadcrumbs={[{ label: 'App', href: '/app' }]}
|
||||||
ctaLink="/kontakt"
|
|
||||||
secondaryCtaText="Nasze realizacje"
|
|
||||||
secondaryCtaLink="/realizacje"
|
|
||||||
backgroundImage="/images/about/about-story.webp"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AboutStory />
|
<AboutStory />
|
||||||
|
|||||||
+14
-16
@@ -1,6 +1,6 @@
|
|||||||
import { HeroSection } from "@/components/sections/HeroSection";
|
import { HeroSection } from "@/components/sections/HeroSection";
|
||||||
|
import { CalculatorTeaser } from "@/components/sections/CalculatorTeaser";
|
||||||
import { ServicesGrid } from "@/components/sections/ServicesGrid";
|
import { ServicesGrid } from "@/components/sections/ServicesGrid";
|
||||||
import { WhyUs } from "@/components/sections/WhyUs";
|
|
||||||
import { FeaturedProducts } from "@/components/sections/FeaturedProducts";
|
import { FeaturedProducts } from "@/components/sections/FeaturedProducts";
|
||||||
import { BlogPreview } from "@/components/sections/BlogPreview";
|
import { BlogPreview } from "@/components/sections/BlogPreview";
|
||||||
import { CtaBanner } from "@/components/sections/CtaBanner";
|
import { CtaBanner } from "@/components/sections/CtaBanner";
|
||||||
@@ -130,16 +130,10 @@ export default function Home() {
|
|||||||
<JsonLd data={jsonLd} />
|
<JsonLd data={jsonLd} />
|
||||||
|
|
||||||
<HeroSection />
|
<HeroSection />
|
||||||
<ServicesGrid />
|
<CalculatorTeaser />
|
||||||
|
|
||||||
{/* BrandsBar will go here in Group B */}
|
{/* BrandsBar will go here in Group B */}
|
||||||
<FeaturedProducts />
|
<FeaturedProducts />
|
||||||
<WhyUs />
|
|
||||||
{/* ProjectsSection will go here in Group B */}
|
|
||||||
{/* <Testimonials /> (Ukryte na czas zbierania autentycznych opinii) */}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<BlogPreview />
|
|
||||||
<section className="bg-white py-16 px-4">
|
<section className="bg-white py-16 px-4">
|
||||||
<div className="bg-slate-50 max-w-7xl mx-auto rounded-[2.5rem] p-6 sm:p-10 lg:p-14">
|
<div className="bg-slate-50 max-w-7xl mx-auto rounded-[2.5rem] p-6 sm:p-10 lg:p-14">
|
||||||
<div className="max-w-7xl mx-auto flex flex-col gap-8">
|
<div className="max-w-7xl mx-auto flex flex-col gap-8">
|
||||||
@@ -241,14 +235,12 @@ export default function Home() {
|
|||||||
<p className="text-sm text-text-muted leading-relaxed">Nasi certyfikowani technicy są dostępni od poniedziałku do soboty. Zarezerwuj termin już dziś, aby uniknąć kolejek w sezonie.</p>
|
<p className="text-sm text-text-muted leading-relaxed">Nasi certyfikowani technicy są dostępni od poniedziałku do soboty. Zarezerwuj termin już dziś, aby uniknąć kolejek w sezonie.</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col sm:flex-row items-center gap-4 shrink-0 w-full lg:w-auto justify-center lg:justify-end">
|
<div className="flex flex-col sm:flex-row items-center gap-4 shrink-0 w-full lg:w-auto justify-center lg:justify-end">
|
||||||
<Link href="/kontakt" className="w-fit sm:w-auto flex justify-center items-center gap-2 px-6 py-3.5 bg-accent text-white font-bold text-sm hover:bg-accent/90 transition-colors rounded-xl">
|
<Link href="/kontakt" className={buttonClasses({ variant: 'primary', size: 'lg' })}>
|
||||||
Umów serwis <Calendar className="size-4" />
|
Umów darmową wycenę <ArrowRight className="size-4" />
|
||||||
|
</Link>
|
||||||
|
<Link href="/cennik-montazu" className={buttonClasses({ variant: 'outline', size: 'lg' })}>
|
||||||
|
Zobacz pełny cennik <ArrowRight className="size-4" />
|
||||||
</Link>
|
</Link>
|
||||||
<a href={`tel:${companyDetails.phoneRaw}`} className="w-fit sm:w-auto">
|
|
||||||
<button className="w-fit sm:w-auto flex justify-center items-center gap-2 px-6 py-3.5 bg-white text-primary border border-border font-bold text-sm hover:bg-slate-50 transition-colors rounded-xl">
|
|
||||||
<Phone className="size-4" /> Zadzwoń do nas
|
|
||||||
</button>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -257,6 +249,12 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<ServicesGrid />
|
||||||
|
|
||||||
|
{/* ProjectsSection will go here in Group B */}
|
||||||
|
{/* <Testimonials /> (Ukryte na czas zbierania autentycznych opinii) */}
|
||||||
|
|
||||||
|
<BlogPreview />
|
||||||
<CtaBanner bgVariant="accent-light" />
|
<CtaBanner bgVariant="accent-light" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
import { HeroSection } from '@/components/sections/HeroSection';
|
import { PageHeader } from '@/components/layout/PageHeader';
|
||||||
import { ProjectsGrid } from '@/components/sections/ProjectsGrid';
|
import { ProjectsGrid } from '@/components/sections/ProjectsGrid';
|
||||||
import { StatsBar } from '@/components/sections/StatsBar';
|
import { StatsBar } from '@/components/sections/StatsBar';
|
||||||
import { CtaBanner } from '@/components/sections/CtaBanner';
|
import { CtaBanner } from '@/components/sections/CtaBanner';
|
||||||
@@ -19,15 +19,10 @@ export const metadata: Metadata = {
|
|||||||
export default function RealizacjePage() {
|
export default function RealizacjePage() {
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<HeroSection
|
<PageHeader
|
||||||
|
|
||||||
title="Nasze realizacje"
|
title="Nasze realizacje"
|
||||||
subtitle="Zobacz wybrane projekty, które zrealizowaliśmy dla naszych klientów. Każde zlecenie traktujemy indywidualnie, dbając o najwyższą jakość wykonania."
|
subtitle="Zobacz wybrane projekty, które zrealizowaliśmy dla naszych klientów. Każde zlecenie traktujemy indywidualnie, dbając o najwyższą jakość wykonania."
|
||||||
ctaText="Zobacz ofertę"
|
breadcrumbs={[{ label: 'App', href: '/app' }]}
|
||||||
ctaLink="/uslugi-domowe"
|
|
||||||
secondaryCtaText="Skontaktuj się"
|
|
||||||
secondaryCtaLink="/kontakt"
|
|
||||||
backgroundImage="/images/hero/hero-section-image.webp"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ProjectsGrid />
|
<ProjectsGrid />
|
||||||
|
|||||||
+4
-9
@@ -1,7 +1,7 @@
|
|||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
import { getProducts, getBrands } from '@/content/products';
|
import { getProducts, getBrands } from '@/content/products';
|
||||||
import { ProductsGrid } from '@/components/shop/ProductsGrid';
|
import { ProductsGrid } from '@/components/shop/ProductsGrid';
|
||||||
import { HeroSection } from '@/components/sections/HeroSection';
|
import { PageHeader } from '@/components/layout/PageHeader';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Sklep z klimatyzatorami',
|
title: 'Sklep z klimatyzatorami',
|
||||||
@@ -17,15 +17,10 @@ export default function ShopPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<HeroSection
|
<PageHeader
|
||||||
|
|
||||||
title="Klimatyzatory split i multisplit"
|
title="Klimatyzatory split i multisplit"
|
||||||
subtitle={`${splitCount} klimatyzatorów split i ${multiCount} jednostek multisplit. Gree, Rotenso, Haier, Daikin, LG, Samsung i inne marki. Z montażem lub bez.`}
|
subtitle=""
|
||||||
ctaText="Zapytaj o montaż"
|
breadcrumbs={[{ label: 'App', href: '/app' }]}
|
||||||
ctaLink="/kontakt"
|
|
||||||
secondaryCtaText=""
|
|
||||||
secondaryCtaLink=""
|
|
||||||
backgroundImage="/images/hero/shop-section-image.webp"
|
|
||||||
/>
|
/>
|
||||||
<ProductsGrid products={products} brands={brands} />
|
<ProductsGrid products={products} brands={brands} />
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import React, { useState } from 'react';
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { Loader2, CheckCircle2, AlertCircle, Check } from 'lucide-react';
|
import { Loader2, CheckCircle2, AlertCircle, Check, Send } from 'lucide-react';
|
||||||
import { submitContactForm } from '@/app/kontakt/actions';
|
import { submitContactForm } from '@/app/kontakt/actions';
|
||||||
import { getRecaptchaToken } from '@/lib/getRecaptchaToken';
|
import { getRecaptchaToken } from '@/lib/getRecaptchaToken';
|
||||||
|
import { Button } from "@/components/ui/Button";
|
||||||
|
|
||||||
const contactFormSchema = z.object({
|
const contactFormSchema = z.object({
|
||||||
name: z.string().min(2, "Imię musi mieć min. 2 znaki"),
|
name: z.string().min(2, "Imię musi mieć min. 2 znaki"),
|
||||||
@@ -88,12 +89,13 @@ export function ContactForm() {
|
|||||||
<CheckCircle2 className="w-16 h-16 text-green-500 mb-4" />
|
<CheckCircle2 className="w-16 h-16 text-green-500 mb-4" />
|
||||||
<h3 className="text-2xl font-bold font-heading mb-2">Wiadomość wysłana!</h3>
|
<h3 className="text-2xl font-bold font-heading mb-2">Wiadomość wysłana!</h3>
|
||||||
<p className="text-green-700">{serverState.message}</p>
|
<p className="text-green-700">{serverState.message}</p>
|
||||||
<button
|
<Button
|
||||||
|
variant="primary"
|
||||||
onClick={() => setServerState(null)}
|
onClick={() => setServerState(null)}
|
||||||
className="mt-6 px-6 py-2 bg-green-600 text-white rounded-lg font-semibold hover:bg-green-700 transition-colors"
|
className="mt-6"
|
||||||
>
|
>
|
||||||
Wyślij kolejną wiadomość
|
Wyślij kolejną wiadomość
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -282,20 +284,21 @@ export function ContactForm() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
className="btn btn-primary w-full text-white font-bold py-4 h-auto rounded-xl flex items-center justify-center gap-2 shadow-md"
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
|
className="w-full"
|
||||||
>
|
>
|
||||||
{isSubmitting ? (
|
{isSubmitting ? (
|
||||||
<>
|
"Wysyłanie..."
|
||||||
<Loader2 className="w-5 h-5 animate-spin" />
|
|
||||||
<span>Wysyłanie...</span>
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<span>Wyślij zapytanie</span>
|
<>
|
||||||
|
Wyślij zapytanie <Send className="size-4" />
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Link from 'next/link';
|
|||||||
import { ArrowRight, ArrowLeft, Check, Home, Zap, ShoppingCart, Phone } from 'lucide-react';
|
import { ArrowRight, ArrowLeft, Check, Home, Zap, ShoppingCart, Phone } from 'lucide-react';
|
||||||
import { Product } from '@/content/products';
|
import { Product } from '@/content/products';
|
||||||
import { companyDetails } from '@/content/company';
|
import { companyDetails } from '@/content/company';
|
||||||
|
import { Button, buttonClasses } from '@/components/ui/Button';
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -574,9 +575,9 @@ export function KalkulatorClient({ products }: Props) {
|
|||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col sm:flex-row gap-2">
|
<div className="flex flex-col sm:flex-row gap-2">
|
||||||
<a href={`tel:${companyDetails.phoneRaw}`}>
|
<a href={`tel:${companyDetails.phoneRaw}`}>
|
||||||
<button className="flex items-center gap-2 px-4 py-2 bg-accent text-white rounded-xl text-xs font-bold hover:bg-accent/90 transition-colors">
|
<Button variant="primary" size="sm" className="w-full sm:w-auto">
|
||||||
<Phone className="size-3.5" /> Bezpłatna wycena
|
<Phone className="size-3.5" /> Bezpłatna wycena
|
||||||
</button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
<Link href="/kontakt" className="flex items-center gap-2 px-4 py-2 bg-amber-100 text-amber-800 rounded-xl text-xs font-bold hover:bg-amber-200 transition-colors border border-amber-300">
|
<Link href="/kontakt" className="flex items-center gap-2 px-4 py-2 bg-amber-100 text-amber-800 rounded-xl text-xs font-bold hover:bg-amber-200 transition-colors border border-amber-300">
|
||||||
|
|
||||||
@@ -589,19 +590,25 @@ export function KalkulatorClient({ products }: Props) {
|
|||||||
)}
|
)}
|
||||||
<div className="flex justify-between mt-8 pt-6 border-t border-border">
|
<div className="flex justify-between mt-8 pt-6 border-t border-border">
|
||||||
{step > 0 ? (
|
{step > 0 ? (
|
||||||
<button onClick={() => setStep(s => s - 1)}
|
<Button
|
||||||
className="flex items-center gap-2 px-4 py-2.5 border border-border rounded-xl text-sm font-semibold text-primary hover:border-accent transition-colors">
|
onClick={() => setStep(s => s - 1)}
|
||||||
|
variant="outline"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
<ArrowLeft className="size-4" /> Wstecz
|
<ArrowLeft className="size-4" /> Wstecz
|
||||||
</button>
|
</Button>
|
||||||
) : <div />}
|
) : <div />}
|
||||||
|
|
||||||
{step < STEPS.length - 1 ? (
|
{step < STEPS.length - 1 ? (
|
||||||
<button onClick={handleNext}
|
<Button
|
||||||
className="flex items-center gap-2 px-6 py-2.5 bg-accent text-white rounded-xl text-sm font-bold hover:bg-accent/90 transition-colors">
|
onClick={handleNext}
|
||||||
|
variant="primary"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
Dalej <ArrowRight className="size-4" />
|
Dalej <ArrowRight className="size-4" />
|
||||||
</button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Link href={getShopUrl()} className="flex items-center gap-2 px-6 py-2.5 bg-accent text-white rounded-xl text-sm font-bold hover:bg-accent/90 transition-colors">
|
<Link href={getShopUrl()} className={buttonClasses({ variant: "primary", size: "md" })}>
|
||||||
|
|
||||||
Przejdź do sklepu <ArrowRight className="size-4" />
|
Przejdź do sklepu <ArrowRight className="size-4" />
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { ChevronRight } from "lucide-react";
|
||||||
|
import { Outfit } from "next/font/google";
|
||||||
|
|
||||||
|
const outfit = Outfit({ subsets: ["latin"], weight: ["500", "700", "800"] });
|
||||||
|
|
||||||
|
export interface PageHeaderProps {
|
||||||
|
title: React.ReactNode;
|
||||||
|
subtitle?: string;
|
||||||
|
breadcrumbs?: { label: string; href: string }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PageHeader({ title, subtitle, breadcrumbs }: PageHeaderProps) {
|
||||||
|
return (
|
||||||
|
<div className="w-full bg-slate-50 border-b border-border pt-24 pb-16 lg:pt-32 lg:pb-24">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
|
||||||
|
{/* Breadcrumbs */}
|
||||||
|
{breadcrumbs && breadcrumbs.length > 0 && (
|
||||||
|
<nav className="flex items-center gap-2 text-xs font-semibold uppercase tracking-widest text-text-muted mb-8">
|
||||||
|
<Link href="/" className="hover:text-primary transition-colors">Główna</Link>
|
||||||
|
{breadcrumbs.map((crumb, index) => (
|
||||||
|
<div key={crumb.href} className="flex items-center gap-2">
|
||||||
|
<ChevronRight className="size-3" />
|
||||||
|
{index === breadcrumbs.length - 1 ? (
|
||||||
|
<span className="text-primary">{crumb.label}</span>
|
||||||
|
) : (
|
||||||
|
<Link href={crumb.href} className="hover:text-primary transition-colors">
|
||||||
|
{crumb.label}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="max-w-3xl">
|
||||||
|
<h1 className={`${outfit.className} text-4xl sm:text-5xl lg:text-6xl font-extrabold text-primary uppercase tracking-tight leading-[1.05]`}>
|
||||||
|
{title}
|
||||||
|
</h1>
|
||||||
|
{subtitle && (
|
||||||
|
<p className="mt-6 text-lg text-text-muted max-w-2xl leading-relaxed border-l-[3px] border-accent pl-4">
|
||||||
|
{subtitle}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { ArrowRight, Home } from "lucide-react";
|
||||||
|
import { buttonClasses } from "@/components/ui/Button";
|
||||||
|
|
||||||
|
export function CalculatorTeaser() {
|
||||||
|
return (
|
||||||
|
<section className="py-16 md:py-24 bg-white border-b border-border">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="bg-slate-50 p-8 md:p-12 lg:p-16 border-y border-border flex flex-col lg:grid lg:grid-cols-[45%_55%] items-center gap-12 lg:gap-16">
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-6 w-full">
|
||||||
|
<h2 className="text-3xl md:text-4xl lg:text-5xl font-extrabold text-primary font-heading leading-tight">
|
||||||
|
Nie wiesz, jakiej mocy potrzebujesz?
|
||||||
|
</h2>
|
||||||
|
<p className="text-base md:text-lg text-text-muted leading-relaxed max-w-xl">
|
||||||
|
Skorzystaj z naszego inteligentnego kalkulatora. Wystarczy podać metraż pomieszczenia, a my wskażemy optymalną moc klimatyzatora i przybliżony koszt instalacji.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="mt-4">
|
||||||
|
<Link href="/kalkulator" className={buttonClasses({ variant: 'primary', size: 'lg', className: 'group w-fit' })}>
|
||||||
|
<span>Uruchom kalkulator</span>
|
||||||
|
<ArrowRight className="size-5 group-hover:translate-x-1 transition-transform" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full">
|
||||||
|
<div className="bg-white rounded-xl p-8 border border-border shadow-md">
|
||||||
|
<div className="flex flex-col gap-8">
|
||||||
|
<div className="flex justify-between items-end border-b border-border pb-4">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-semibold text-text-muted mb-1">Powierzchnia</p>
|
||||||
|
<p className="text-3xl font-bold text-primary font-heading">35 <span className="text-xl text-text-muted">m²</span></p>
|
||||||
|
</div>
|
||||||
|
<Home className="size-8 text-accent opacity-50" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="h-2 w-full bg-slate-100 rounded-full overflow-hidden">
|
||||||
|
<div className="h-full bg-accent rounded-full w-1/2"></div>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-xs font-semibold text-text-muted">
|
||||||
|
<span>10 m²</span>
|
||||||
|
<span>100 m²</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-accent/5 rounded-xl p-6 border border-accent/10 flex flex-col items-center text-center">
|
||||||
|
<p className="text-sm font-semibold text-primary mb-1">Sugerowana moc</p>
|
||||||
|
<p className="text-4xl font-extrabold text-accent font-heading">3.5 <span className="text-2xl text-accent/70">kW</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import Link from "next/link";
|
|||||||
import { Phone, ArrowRight } from "lucide-react";
|
import { Phone, ArrowRight } from "lucide-react";
|
||||||
import { companyDetails } from "@/content/company";
|
import { companyDetails } from "@/content/company";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { buttonClasses } from "@/components/ui/Button";
|
||||||
|
|
||||||
interface CtaBannerProps {
|
interface CtaBannerProps {
|
||||||
title?: string;
|
title?: string;
|
||||||
@@ -56,26 +57,15 @@ export function CtaBanner({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col sm:flex-row items-center gap-4 mt-8 w-full sm:w-auto shrink-0 justify-center">
|
<div className="flex flex-col sm:flex-row items-center gap-4 mt-8 w-full sm:w-auto shrink-0 justify-center">
|
||||||
<Link href={ctaLink} className="w-fit sm:w-auto btn btn-primary font-bold text-white px-6">
|
<Link href={ctaLink} className={buttonClasses({ variant: 'primary', size: 'md', className: 'w-fit sm:w-auto' })}>
|
||||||
<span>{ctaText}</span>
|
{ctaText} <ArrowRight className="size-4" />
|
||||||
<ArrowRight className="size-5" />
|
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
href={`tel:${companyDetails.phoneRaw}`}
|
href={`tel:${companyDetails.phoneRaw}`}
|
||||||
className="w-fit sm:w-auto"
|
className={buttonClasses({ variant: isPrimary ? 'outline-light' : 'outline', size: 'md', className: 'w-fit sm:w-auto' })}
|
||||||
>
|
>
|
||||||
<button
|
<Phone className="size-4" /> {companyDetails.phone}
|
||||||
className={cn(
|
|
||||||
"btn btn-outline btn-primary bg-white w-fit sm:w-auto font-bold flex items-center gap-2 px-6",
|
|
||||||
isPrimary
|
|
||||||
? "text-white hover:bg-white/20 hover:text-white border-white/30"
|
|
||||||
: "text-primary hover:bg-primary/10 border-primary/20"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Phone className="size-5" />
|
|
||||||
<span>{companyDetails.phone}</span>
|
|
||||||
</button>
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { ArrowRight, Star } from "lucide-react";
|
import { Outfit } from "next/font/google";
|
||||||
import { buttonClasses } from "@/components/ui/Button";
|
|
||||||
|
const outfit = Outfit({ subsets: ["latin"], weight: ["500", "700", "800"] });
|
||||||
|
|
||||||
export interface HeroSectionProps {
|
export interface HeroSectionProps {
|
||||||
title?: React.ReactNode;
|
title?: React.ReactNode;
|
||||||
@@ -10,73 +11,88 @@ export interface HeroSectionProps {
|
|||||||
ctaLink?: string;
|
ctaLink?: string;
|
||||||
secondaryCtaText?: string;
|
secondaryCtaText?: string;
|
||||||
secondaryCtaLink?: string;
|
secondaryCtaLink?: string;
|
||||||
backgroundImage?: string;
|
|
||||||
features?: string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HeroSection({
|
export function HeroSection({
|
||||||
title = (
|
title = (
|
||||||
<>
|
<>
|
||||||
<span className="text-white">Klimatyzacja</span><br />
|
<span className="text-primary">Klimatyzacja</span><br />
|
||||||
<span className="text-accent">we Wrocławiu</span>
|
<span className="text-accent">we Wrocławiu</span>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
subtitle = "Montaż, serwis i naprawa — przyjeżdżamy do Ciebie.",
|
subtitle = "Montaż, serwis i naprawa — przyjeżdżamy do Ciebie. Zapewniamy najwyższą jakość instalacji oraz pełne wsparcie pogwarancyjne.",
|
||||||
ctaText = "Darmowa wycena",
|
ctaText = "Darmowa wycena",
|
||||||
ctaLink = "/kontakt",
|
ctaLink = "/kontakt",
|
||||||
secondaryCtaText = "Zobacz ofertę",
|
secondaryCtaText = "Zobacz ofertę",
|
||||||
secondaryCtaLink = "/sklep",
|
secondaryCtaLink = "/sklep"
|
||||||
backgroundImage = "/images/hero/home_page_hero.webp",
|
|
||||||
features
|
|
||||||
}: HeroSectionProps = {}) {
|
}: HeroSectionProps = {}) {
|
||||||
return (
|
return (
|
||||||
<section className="relative bg-primary min-h-[480px] md:min-h-[600px] flex items-center overflow-hidden w-full">
|
<section className="relative w-full bg-slate-50 flex flex-col lg:flex-row min-h-[85vh] border-b border-border">
|
||||||
{/* TODO ZDJĘCIE HERO: realne zdjęcie firmy/montażu, format poziomy min. 1920×1080, plik public/hero.jpg */}
|
|
||||||
<Image
|
|
||||||
src={backgroundImage}
|
|
||||||
alt="Montaż klimatyzacji i pomp ciepła"
|
|
||||||
fill
|
|
||||||
priority
|
|
||||||
sizes="100vw"
|
|
||||||
className="object-cover"
|
|
||||||
/>
|
|
||||||
<div className="absolute inset-0 bg-black/50" />
|
|
||||||
|
|
||||||
<div className="relative z-10 w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 md:py-24">
|
{/* Lewa kolumna: Typografia i CTA */}
|
||||||
<div className="flex flex-col items-start text-left max-w-3xl">
|
<div className="w-full lg:w-[55%] flex flex-col justify-center px-6 sm:px-12 lg:px-16 xl:px-24 py-16 lg:py-24 z-10">
|
||||||
<h1 className="text-3xl md:text-5xl lg:text-6xl font-extrabold tracking-tight font-heading leading-[1.1] text-white">
|
|
||||||
|
<h1 className={`${outfit.className} text-5xl sm:text-6xl xl:text-7xl font-extrabold tracking-tight leading-[1.05] uppercase`}>
|
||||||
{title}
|
{title}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-6 text-base md:text-lg lg:text-xl text-white/90 max-w-2xl leading-relaxed">
|
|
||||||
|
<p className="mt-8 text-lg sm:text-xl text-text-muted max-w-xl leading-relaxed">
|
||||||
{subtitle}
|
{subtitle}
|
||||||
</p>
|
</p>
|
||||||
{features && features.length > 0 && (
|
|
||||||
<ul className="mt-6 grid grid-cols-2 gap-3 text-white/90 font-medium">
|
<div className="mt-12 flex flex-col sm:flex-row gap-4">
|
||||||
{features.map((feature, idx) => (
|
|
||||||
<li key={idx} className="flex items-center gap-2">
|
|
||||||
<Star className="size-4 text-yellow-400 fill-yellow-400" />
|
|
||||||
{feature}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
<div className="mt-10 flex flex-col sm:flex-row items-start sm:items-center gap-4 w-full sm:w-auto">
|
|
||||||
{ctaText && ctaLink && (
|
{ctaText && ctaLink && (
|
||||||
<Link href={ctaLink} className={buttonClasses({ variant: 'primary', size: 'md', className: 'w-fit sm:w-auto group' })}>
|
<Link
|
||||||
<span>{ctaText}</span>
|
href={ctaLink}
|
||||||
<ArrowRight className="size-5 group-hover:translate-x-1 transition-transform" />
|
className="inline-flex items-center justify-center bg-primary text-white font-bold uppercase tracking-wider px-10 py-5 rounded-none hover:bg-primary/90 transition-colors border-2 border-primary"
|
||||||
|
>
|
||||||
|
{ctaText}
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
{secondaryCtaText && secondaryCtaLink && (
|
{secondaryCtaText && secondaryCtaLink && (
|
||||||
<Link href={secondaryCtaLink} className={buttonClasses({ variant: 'outline', size: 'md', className: 'w-fit sm:w-auto' })}>
|
<Link
|
||||||
<span>{secondaryCtaText}</span>
|
href={secondaryCtaLink}
|
||||||
|
className="inline-flex items-center justify-center bg-transparent text-primary font-bold uppercase tracking-wider px-10 py-5 rounded-none border-2 border-primary hover:bg-primary/5 transition-colors"
|
||||||
|
>
|
||||||
|
{secondaryCtaText}
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Trust Signals: Minimalistyczne dane typograficzne */}
|
||||||
|
<div className="mt-16 sm:mt-24 pt-8 border-t border-border flex flex-wrap gap-10">
|
||||||
|
<div>
|
||||||
|
<p className={`${outfit.className} text-4xl font-extrabold text-primary`}>15+</p>
|
||||||
|
<p className="text-xs font-semibold uppercase tracking-widest text-text-muted mt-2">Lat doświadczenia</p>
|
||||||
|
</div>
|
||||||
|
<div className="hidden sm:block w-px bg-border self-stretch" />
|
||||||
|
<div>
|
||||||
|
<p className={`${outfit.className} text-4xl font-extrabold text-primary`}>5</p>
|
||||||
|
<p className="text-xs font-semibold uppercase tracking-widest text-text-muted mt-2">Lat gwarancji</p>
|
||||||
|
</div>
|
||||||
|
<div className="hidden sm:block w-px bg-border self-stretch" />
|
||||||
|
<div>
|
||||||
|
<p className={`${outfit.className} text-4xl font-extrabold text-primary`}>100%</p>
|
||||||
|
<p className="text-xs font-semibold uppercase tracking-widest text-text-muted mt-2">Pokrycia w regionie</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Prawa kolumna: Jedno wielkie, surowe zdjęcie */}
|
||||||
|
<div className="w-full lg:w-[45%] h-[500px] lg:h-auto relative bg-slate-200">
|
||||||
|
<Image
|
||||||
|
src="/images/services/montaz.webp"
|
||||||
|
alt="Profesjonalny montaż klimatyzacji"
|
||||||
|
fill
|
||||||
|
priority
|
||||||
|
sizes="(max-width: 1024px) 100vw, 45vw"
|
||||||
|
className="object-cover object-center"
|
||||||
|
/>
|
||||||
|
{/* Subtelny overlay dla kontrastu jeśli zajdzie potrzeba (opcjonalnie) */}
|
||||||
|
<div className="absolute inset-0 bg-black/5 lg:bg-transparent" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
import { SectionHeading } from "../common/SectionHeading";
|
import { SectionHeading } from "../common/SectionHeading";
|
||||||
import { IconCard } from "../common/IconCard";
|
|
||||||
import { services as defaultServices } from "@/content/services";
|
import { services as defaultServices } from "@/content/services";
|
||||||
|
import Link from "next/link";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { buttonClasses } from "@/components/ui/Button";
|
||||||
|
import { ArrowRight } from "lucide-react";
|
||||||
|
|
||||||
interface ServicesGridProps {
|
interface ServicesGridProps {
|
||||||
services?: {
|
services?: {
|
||||||
@@ -16,12 +22,14 @@ interface ServicesGridProps {
|
|||||||
|
|
||||||
export function ServicesGrid({
|
export function ServicesGrid({
|
||||||
services = defaultServices,
|
services = defaultServices,
|
||||||
title = "Kompleksowe usługi dla Twojego domu",
|
title = "Rozwiązania dla Twojego domu i biznesu",
|
||||||
subtitle = "Zapewniamy profesjonalne wsparcie w zakresie instalacji sanitarnych, grzewczych, chłodniczych oraz prac wykończeniowych.",
|
subtitle = "Wybierz kategorię usług, aby poznać szczegóły. Oferujemy kompleksowe doradztwo, montaż oraz serwis.",
|
||||||
eyebrow = "Pełna Oferta"
|
eyebrow = "Nasze Usługi"
|
||||||
}: ServicesGridProps) {
|
}: ServicesGridProps) {
|
||||||
|
const [activeTab, setActiveTab] = useState(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="py-16 md:py-24 bg-slate-50 border-t border-b border-border">
|
<section className="py-16 md:py-24 bg-white border-b border-border">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<SectionHeading
|
<SectionHeading
|
||||||
eyebrow={eyebrow}
|
eyebrow={eyebrow}
|
||||||
@@ -30,17 +38,61 @@ export function ServicesGrid({
|
|||||||
align="center"
|
align="center"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={`grid grid-cols-1 sm:grid-cols-2 gap-6 md:gap-8 mt-10 ${services.length === 2 ? 'max-w-4xl mx-auto' : 'lg:grid-cols-3'}`}>
|
<div className="mt-12 flex flex-col items-center">
|
||||||
|
{/* Zakładki */}
|
||||||
|
<div className="flex flex-wrap justify-center gap-2 md:gap-4 mb-10 bg-slate-50 p-2 rounded-2xl border border-border">
|
||||||
{services.map((service, idx) => (
|
{services.map((service, idx) => (
|
||||||
<IconCard
|
<button
|
||||||
key={idx}
|
key={idx}
|
||||||
title={service.title}
|
onClick={() => setActiveTab(idx)}
|
||||||
description={service.description}
|
className={`px-4 py-2.5 md:px-6 md:py-3 rounded-xl text-sm md:text-base font-bold transition-all ${
|
||||||
iconName={service.icon}
|
activeTab === idx
|
||||||
href={service.href}
|
? "bg-white text-primary shadow-sm border border-border"
|
||||||
/>
|
: "text-text-muted hover:text-primary hover:bg-slate-100"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{service.title}
|
||||||
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Zawartość aktywnej zakładki */}
|
||||||
|
<div className="w-full max-w-4xl bg-slate-50 rounded-3xl p-8 md:p-12 border border-border shadow-sm">
|
||||||
|
{services.map((service, idx) => {
|
||||||
|
if (idx !== activeTab) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={idx} className="flex flex-col md:grid md:grid-cols-[2fr_3fr] gap-8 lg:gap-12 items-center animate-fade-in-up">
|
||||||
|
|
||||||
|
{/* Zastąpiono sztuczną ikonę w kółku realnym zdjęciem */}
|
||||||
|
<div className="w-full aspect-[4/3] relative rounded-xl overflow-hidden border border-border shadow-sm">
|
||||||
|
<Image
|
||||||
|
src={`/images/services/montaz.webp`} // Placeholder obrazka z usług (można podpiąć z contentu jeśli jest)
|
||||||
|
alt={service.title}
|
||||||
|
fill
|
||||||
|
className="object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="text-2xl md:text-3xl font-bold text-primary font-heading mb-4">
|
||||||
|
{service.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-base md:text-lg text-text-muted leading-relaxed mb-6">
|
||||||
|
{service.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{service.href && (
|
||||||
|
<Link href={service.href} className={buttonClasses({ variant: 'outline', size: 'sm' })}>
|
||||||
|
Więcej szczegółów <ArrowRight className="size-4" />
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { ShoppingCart, GitCompare, Phone } from 'lucide-react';
|
|||||||
import { Product } from '@/content/products';
|
import { Product } from '@/content/products';
|
||||||
import { useCartStore } from '@/lib/cart-store';
|
import { useCartStore } from '@/lib/cart-store';
|
||||||
import { useCompareStore } from '@/lib/compare-store';
|
import { useCompareStore } from '@/lib/compare-store';
|
||||||
|
import { formatCurrency } from "@/lib/utils";
|
||||||
|
import { Button, buttonClasses } from "@/components/ui/Button";
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
const brandLogos: Record<string, string> = {
|
const brandLogos: Record<string, string> = {
|
||||||
@@ -144,11 +146,10 @@ export function ProductCard({ product }: ProductCardProps) {
|
|||||||
<div />
|
<div />
|
||||||
<Link
|
<Link
|
||||||
href={`/zapytanie-multisplit/${product.slug}`}
|
href={`/zapytanie-multisplit/${product.slug}`}
|
||||||
|
className={buttonClasses({ variant: 'primary', size: 'sm', className: 'shrink-0' })}
|
||||||
onClick={e => e.stopPropagation()}
|
onClick={e => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<button className="flex items-center gap-1.5 px-3 py-2 rounded-lg text-xs font-bold bg-accent text-white hover:bg-accent/90 transition-colors shrink-0">
|
|
||||||
Zapytaj o wycenę
|
Zapytaj o wycenę
|
||||||
</button>
|
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
) : product.price !== null ? (
|
) : product.price !== null ? (
|
||||||
@@ -162,18 +163,20 @@ export function ProductCard({ product }: ProductCardProps) {
|
|||||||
{added ? (
|
{added ? (
|
||||||
<Link
|
<Link
|
||||||
href="/koszyk"
|
href="/koszyk"
|
||||||
className="flex items-center gap-1.5 px-3 py-2 rounded-lg text-xs font-bold bg-green-500 text-white hover:bg-green-600 transition-colors shrink-0"
|
className={buttonClasses({ variant: 'success', size: 'sm', className: 'shrink-0' })}
|
||||||
onClick={e => e.stopPropagation()}
|
onClick={e => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<ShoppingCart className="size-3.5" /> Zobacz koszyk
|
<ShoppingCart className="size-3.5" /> Zobacz koszyk
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="sm"
|
||||||
|
className="shrink-0"
|
||||||
onClick={handleAddToCart}
|
onClick={handleAddToCart}
|
||||||
className="flex items-center gap-1.5 px-3 py-2 rounded-lg text-xs font-bold bg-accent text-white hover:bg-accent/90 transition-colors shrink-0"
|
|
||||||
>
|
>
|
||||||
<ShoppingCart className="size-3.5" /> Do koszyka
|
<ShoppingCart className="size-3.5" /> Do koszyka
|
||||||
</button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@@ -182,10 +185,12 @@ export function ProductCard({ product }: ProductCardProps) {
|
|||||||
<Phone className="size-3.5" />
|
<Phone className="size-3.5" />
|
||||||
<span className="text-sm font-semibold">Zapytaj o cenę</span>
|
<span className="text-sm font-semibold">Zapytaj o cenę</span>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/kontakt" onClick={e => e.stopPropagation()}>
|
<Link
|
||||||
<button className="flex items-center gap-1.5 px-3 py-2 rounded-lg text-xs font-bold bg-primary text-white hover:bg-primary/90 transition-colors shrink-0">
|
href="/kontakt"
|
||||||
|
className={buttonClasses({ variant: 'primary', size: 'sm', className: 'shrink-0' })}
|
||||||
|
onClick={e => e.stopPropagation()}
|
||||||
|
>
|
||||||
Zapytaj
|
Zapytaj
|
||||||
</button>
|
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -5,9 +5,12 @@ import Image from 'next/image';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import {
|
import {
|
||||||
Phone, ShoppingCart, GitCompare, Check,
|
Phone, ShoppingCart, GitCompare, Check,
|
||||||
Thermometer, Volume2, Zap, Wind, Star, Info, Eye, X, type LucideIcon
|
Thermometer, Volume2, Zap, Wind, Star, Info, Eye, X, Mail, type LucideIcon
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Product } from '@/content/products';
|
import { Product } from '@/content/products';
|
||||||
|
import { formatCurrency } from "@/lib/utils";
|
||||||
|
import { companyDetails } from "@/content/company";
|
||||||
|
import { Button, buttonClasses } from "@/components/ui/Button";
|
||||||
import { useCartStore } from '@/lib/cart-store';
|
import { useCartStore } from '@/lib/cart-store';
|
||||||
import { useCompareStore } from '@/lib/compare-store';
|
import { useCompareStore } from '@/lib/compare-store';
|
||||||
import { getProductVariants } from '@/content/product-variants';
|
import { getProductVariants } from '@/content/product-variants';
|
||||||
@@ -277,40 +280,46 @@ export function ProductPageClient({ product, seriesProducts, companyPhone, compa
|
|||||||
<>
|
<>
|
||||||
<div className="flex items-end gap-3 mb-1">
|
<div className="flex items-end gap-3 mb-1">
|
||||||
<span className="text-2xl font-extrabold text-primary font-heading">
|
<span className="text-2xl font-extrabold text-primary font-heading">
|
||||||
{currentProduct.price.toLocaleString('pl-PL')} zł
|
{formatCurrency(currentProduct.price)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-text-muted mb-1">cena urządzenia brutto (23% VAT)</p>
|
<p className="text-xs text-text-muted mb-1">cena urządzenia brutto (23% VAT)</p>
|
||||||
<p className="text-xs text-text-muted mb-4">Montaż wyceniamy indywidualnie — zadzwoń lub napisz</p>
|
<p className="text-xs text-text-muted mb-4">Montaż wyceniamy indywidualnie — zadzwoń lub napisz</p>
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
{cartState === 'idle' && (
|
{cartState === 'idle' && (
|
||||||
<button
|
<Button
|
||||||
onClick={handleAddToCart}
|
onClick={handleAddToCart}
|
||||||
className="flex-1 flex items-center justify-center gap-2 py-2.5 px-4 rounded-xl font-bold text-sm bg-accent text-white hover:bg-accent/90 transition-all"
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
|
className="w-full justify-center"
|
||||||
>
|
>
|
||||||
<ShoppingCart className="size-4" /> Do koszyka
|
<ShoppingCart className="size-4" /> Do koszyka
|
||||||
</button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{cartState === 'added' && (
|
{cartState === 'added' && (
|
||||||
<button
|
<Button
|
||||||
disabled
|
disabled
|
||||||
className="flex-1 flex items-center justify-center gap-2 py-2.5 px-4 rounded-xl font-bold text-sm bg-green-500 text-white"
|
variant="success"
|
||||||
|
size="lg"
|
||||||
|
className="w-full justify-center"
|
||||||
>
|
>
|
||||||
<Check className="size-4" /> Dodano do koszyka
|
<Check className="size-4" /> Dodano do koszyka
|
||||||
</button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{cartState === 'cart' && (
|
{cartState === 'cart' && (
|
||||||
<Link href="/koszyk" className="flex-1 w-full flex items-center justify-center gap-2 py-2.5 px-4 rounded-xl font-bold text-sm bg-green-500 text-white hover:bg-green-600 transition-colors">
|
<Link
|
||||||
|
href="/koszyk"
|
||||||
|
className={buttonClasses({ variant: 'success', size: 'lg', className: 'w-full justify-center' })}
|
||||||
|
>
|
||||||
<ShoppingCart className="size-4" /> Zobacz koszyk
|
<ShoppingCart className="size-4" /> Zobacz koszyk
|
||||||
|
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
<Link href="/kontakt" className="flex-1 w-full flex items-center justify-center gap-2 py-2.5 px-4 rounded-xl font-bold text-sm border-2 border-primary text-primary hover:bg-primary hover:text-white transition-all">
|
<Link
|
||||||
|
href="/kontakt"
|
||||||
Zapytaj o montaż
|
className={buttonClasses({ variant: 'outline', size: 'lg', className: 'w-full justify-center' })}
|
||||||
|
>
|
||||||
|
<Mail className="size-4" /> Zapytaj o montaż
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -328,7 +337,7 @@ export function ProductPageClient({ product, seriesProducts, companyPhone, compa
|
|||||||
<>
|
<>
|
||||||
<p className="text-lg font-bold text-primary font-heading mb-1">Cena na zapytanie</p>
|
<p className="text-lg font-bold text-primary font-heading mb-1">Cena na zapytanie</p>
|
||||||
<p className="text-sm text-text-muted mb-4">Multisplit — cena zależy od konfiguracji jednostek wewnętrznych.</p>
|
<p className="text-sm text-text-muted mb-4">Multisplit — cena zależy od konfiguracji jednostek wewnętrznych.</p>
|
||||||
<a href={`tel:${companyPhoneRaw}`} className="flex items-center justify-center gap-2 py-3 px-4 rounded-xl bg-accent text-white font-bold text-sm hover:bg-accent/90 transition-colors">
|
<a href={`tel:${companyPhoneRaw}`} className={buttonClasses({ variant: 'primary', size: 'md', className: 'w-full' })}>
|
||||||
<Phone className="size-4" /> Zadzwoń: {companyPhone}
|
<Phone className="size-4" /> Zadzwoń: {companyPhone}
|
||||||
</a>
|
</a>
|
||||||
</>
|
</>
|
||||||
@@ -337,7 +346,7 @@ export function ProductPageClient({ product, seriesProducts, companyPhone, compa
|
|||||||
|
|
||||||
<a
|
<a
|
||||||
href={`tel:${companyPhoneRaw}`}
|
href={`tel:${companyPhoneRaw}`}
|
||||||
className="flex items-center justify-between px-5 py-2.5 bg-primary rounded-xl text-white hover:bg-primary/90 transition-colors"
|
className={buttonClasses({ variant: 'outline', size: 'lg', className: 'w-full justify-between' })}
|
||||||
>
|
>
|
||||||
<span className="text-xs font-semibold">Profesjonalna porada</span>
|
<span className="text-xs font-semibold">Profesjonalna porada</span>
|
||||||
<span className="flex items-center gap-2 font-bold">
|
<span className="flex items-center gap-2 font-bold">
|
||||||
@@ -458,7 +467,7 @@ export function ProductPageClient({ product, seriesProducts, companyPhone, compa
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
<a href={`tel:${companyPhoneRaw}`} className="mt-4 inline-flex items-center gap-2 btn btn-primary text-white font-bold text-sm">
|
<a href={`tel:${companyPhoneRaw}`} className={buttonClasses({ variant: 'primary', size: 'md', className: 'mt-4' })}>
|
||||||
<Phone className="size-4" /> Zapytaj o montaż
|
<Phone className="size-4" /> Zapytaj o montaż
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -483,7 +492,7 @@ export function ProductPageClient({ product, seriesProducts, companyPhone, compa
|
|||||||
<label className="text-sm font-semibold text-primary">Treść opinii</label>
|
<label className="text-sm font-semibold text-primary">Treść opinii</label>
|
||||||
<textarea required rows={4} className="w-full px-4 py-2.5 rounded-xl border border-border bg-white text-sm focus:outline-none focus:ring-2 focus:ring-accent/50" placeholder="Podziel się swoimi wrażeniami..."></textarea>
|
<textarea required rows={4} className="w-full px-4 py-2.5 rounded-xl border border-border bg-white text-sm focus:outline-none focus:ring-2 focus:ring-accent/50" placeholder="Podziel się swoimi wrażeniami..."></textarea>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" className="btn btn-primary font-bold text-white w-full sm:w-auto">Wyślij opinię</button>
|
<Button type="submit" variant="primary" className="w-full sm:w-auto">Wyślij opinię</Button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
export type ButtonVariant = 'primary' | 'outline' | 'error' | 'outline-light';
|
export type ButtonVariant = 'primary' | 'outline' | 'error' | 'outline-light' | 'success';
|
||||||
export type ButtonSize = 'sm' | 'md' | 'lg';
|
export type ButtonSize = 'sm' | 'md' | 'lg';
|
||||||
|
|
||||||
export interface ButtonClassesProps {
|
export interface ButtonClassesProps {
|
||||||
@@ -11,19 +11,20 @@ export interface ButtonClassesProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function buttonClasses({ variant = 'primary', size = 'md', className }: ButtonClassesProps = {}) {
|
export function buttonClasses({ variant = 'primary', size = 'md', className }: ButtonClassesProps = {}) {
|
||||||
const baseClasses = 'inline-flex items-center justify-center gap-2 whitespace-nowrap transition-colors';
|
const baseClasses = 'inline-flex items-center justify-center gap-2 whitespace-nowrap transition-colors font-bold uppercase tracking-wider rounded-md';
|
||||||
|
|
||||||
const variantClasses = {
|
const variantClasses = {
|
||||||
primary: 'btn btn-primary text-white font-bold',
|
primary: 'bg-primary text-white hover:bg-primary/90 border-2 border-primary',
|
||||||
outline: 'btn bg-white/10 text-white border border-white/20 hover:bg-white/20 font-bold',
|
outline: 'bg-transparent text-primary border-2 border-primary hover:bg-primary/5',
|
||||||
'outline-light': 'btn bg-white text-primary border border-border hover:bg-slate-50 font-bold',
|
'outline-light': 'bg-transparent text-white border-2 border-white hover:bg-white/10',
|
||||||
error: 'btn btn-error text-white font-bold',
|
error: 'bg-red-600 text-white hover:bg-red-700 border-2 border-red-600',
|
||||||
|
success: 'bg-green-600 text-white hover:bg-green-700 border-2 border-green-600',
|
||||||
};
|
};
|
||||||
|
|
||||||
const sizeClasses = {
|
const sizeClasses = {
|
||||||
sm: 'btn-sm text-sm',
|
sm: 'px-6 py-2.5 text-xs',
|
||||||
md: '', // default DaisyUI btn size
|
md: 'px-8 py-3.5 text-sm',
|
||||||
lg: 'btn-lg text-lg',
|
lg: 'px-10 py-5 text-base',
|
||||||
};
|
};
|
||||||
|
|
||||||
return cn(
|
return cn(
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
const fs = require('fs');
|
|
||||||
let p = 'components/kalkulator/KalkulatorClient.tsx';
|
|
||||||
let c = fs.readFileSync(p, 'utf8');
|
|
||||||
|
|
||||||
c = c.replace(/const STEPS = \['Typ', 'Pomieszczenie', 'Instalacja', 'Budżet', 'Dobór'\] as const;/g, "const STEPS = ['Pomieszczenie', 'Instalacja', 'Budżet', 'Dobór'] as const;");
|
|
||||||
|
|
||||||
c = c.replace(/const \[deviceType, setDeviceType\] = useSessionState<'klimatyzacja' \| 'pompa'>\('calc_deviceType', 'klimatyzacja'\);\n/g, "");
|
|
||||||
c = c.replace(/const \[pumpFormSent, setPumpFormSent\] = useState\(false\);\n/g, "");
|
|
||||||
c = c.replace(/const \[pumpForm, setPumpForm\] = useState\(\{ name: '', phone: '', city: '', message: '' \}\);\n/g, "");
|
|
||||||
|
|
||||||
c = c.replace(/if \(step < 4 \|\| deviceType === 'pompa'\) return \[\];/g, "if (step < 3) return [];");
|
|
||||||
|
|
||||||
c = c.replace(/step === 4 && deviceType !== 'pompa'/g, "step === 3");
|
|
||||||
|
|
||||||
// Remove handlePumpSubmit
|
|
||||||
c = c.replace(/ const handlePumpSubmit = async \(\) => \{[\s\S]*? \};\n/g, "");
|
|
||||||
|
|
||||||
// Replace step === 0 (Typ urządzenia) entirely
|
|
||||||
c = c.replace(/ \{\/\* Krok 0: Typ urządzenia \*\/\}[\s\S]*? \{\/\* Krok 1: Pomieszczenie \*\/\}/g, " {/* Krok 0: Pomieszczenie */}");
|
|
||||||
|
|
||||||
// Shift step indices
|
|
||||||
c = c.replace(/step === 1 && \(/g, "step === 0 && (");
|
|
||||||
c = c.replace(/step === 2 && \(/g, "step === 1 && (");
|
|
||||||
c = c.replace(/step === 3 && \(/g, "step === 2 && (");
|
|
||||||
c = c.replace(/step === 4 && \(/g, "step === 3 && (");
|
|
||||||
c = c.replace(/step === 4 \? 'bg-accent/g, "step === 3 ? 'bg-accent");
|
|
||||||
c = c.replace(/\{step === 4 && \(/g, "{step === 3 && (");
|
|
||||||
|
|
||||||
// Remove deviceType from results useMemo dependencies
|
|
||||||
c = c.replace(/, deviceType/g, "");
|
|
||||||
|
|
||||||
fs.writeFileSync(p, c);
|
|
||||||
console.log("Done");
|
|
||||||
Reference in New Issue
Block a user