rework components
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
import {Mail, Phone} from 'lucide-react';
|
||||
import {contact} from '@/config/contact';
|
||||
|
||||
const schedulerUrl = 'https://42min.us/kubapyla';
|
||||
|
||||
function phoneHref(phone: string) {
|
||||
return `tel:${phone.replaceAll(' ', '')}`;
|
||||
}
|
||||
|
||||
export function ContactSection() {
|
||||
return (
|
||||
<section id="kontakt" className="contactSection sectionBand">
|
||||
<div className="container contactSection__grid">
|
||||
<div className="contactSection__intro">
|
||||
<div>
|
||||
<p className="eyebrow">Kontakt</p>
|
||||
<h2>Porozmawiajmy o konfiguratorze 3D</h2>
|
||||
<p>Opisz katalog produktów, zakres wariantów albo pierwszy pomysł na demo. Wrócimy z konkretną ścieżką wdrożenia.</p>
|
||||
</div>
|
||||
<address className="contactSection__details">
|
||||
<div>
|
||||
<Mail size={22} />
|
||||
<a href={`mailto:${contact.email}`}>{contact.email}</a>
|
||||
</div>
|
||||
<div>
|
||||
<Phone size={22} />
|
||||
<span>
|
||||
{contact.phones.map(phone => (
|
||||
<a href={phoneHref(phone)} key={phone}>
|
||||
{phone}
|
||||
</a>
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
</address>
|
||||
</div>
|
||||
<div className="contactSection__schedulerWrap">
|
||||
<iframe
|
||||
className="contactSection__scheduler"
|
||||
src={schedulerUrl}
|
||||
title="Umów rozmowę z Ultifide"
|
||||
loading="lazy"
|
||||
referrerPolicy="strict-origin-when-cross-origin"
|
||||
/>
|
||||
<a className="contactSection__fallback" href={schedulerUrl} target="_blank" rel="noreferrer">
|
||||
Umów rozmowę online w kalendarzu
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import {ArrowRight, DoorOpen} from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import {useRouter, useSearchParams} from 'next/navigation';
|
||||
import {useMemo, useState} from 'react';
|
||||
import {useMemo} from 'react';
|
||||
import {DemoFrame} from './DemoFrame';
|
||||
import type {DemoContent} from '@/types/content';
|
||||
|
||||
@@ -12,7 +12,7 @@ type DemoBrowserProps = {
|
||||
demos: DemoContent[];
|
||||
};
|
||||
|
||||
function isKnownDemo(slug: string | null, demos: DemoContent[]) {
|
||||
function isKnownDemo(slug: string | null, demos: DemoContent[]): slug is DemoContent['slug'] {
|
||||
return demos.some(demo => demo.slug === slug);
|
||||
}
|
||||
|
||||
@@ -20,8 +20,7 @@ export function DemoBrowser({demos}: DemoBrowserProps) {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const requestedDemo = searchParams.get('demo');
|
||||
const initialSlug = isKnownDemo(requestedDemo, demos) ? requestedDemo : demos[0]?.slug;
|
||||
const [activeSlug, setActiveSlug] = useState(initialSlug);
|
||||
const activeSlug = isKnownDemo(requestedDemo, demos) ? requestedDemo : demos[0].slug;
|
||||
|
||||
const activeDemo = useMemo(
|
||||
() => demos.find(demo => demo.slug === activeSlug) ?? demos[0],
|
||||
@@ -29,7 +28,6 @@ export function DemoBrowser({demos}: DemoBrowserProps) {
|
||||
);
|
||||
|
||||
function selectDemo(slug: DemoContent['slug']) {
|
||||
setActiveSlug(slug);
|
||||
router.replace(`/pl/dema?demo=${slug}`, {scroll: false});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,11 @@ import {CheckCircle2, DoorOpen} from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
import {ButtonLink} from './ButtonLink';
|
||||
import {DemoFrame} from './DemoFrame';
|
||||
import {DemoSubnav} from './DemoSubnav';
|
||||
import {Faq} from './Faq';
|
||||
import {SectionHeader} from './SectionHeader';
|
||||
import {Stats} from './Stats';
|
||||
import {contact} from '@/config/contact';
|
||||
import {orderedDemos} from '@/config/content';
|
||||
import type {DemoContent} from '@/types/content';
|
||||
|
||||
type DemoPageProps = {
|
||||
@@ -14,7 +15,14 @@ type DemoPageProps = {
|
||||
|
||||
export function DemoPage({demo}: DemoPageProps) {
|
||||
return (
|
||||
<main>
|
||||
<>
|
||||
<div className="demoSubnavBar">
|
||||
<div className="container">
|
||||
<DemoSubnav demos={orderedDemos} activeSlug={demo.slug} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main>
|
||||
<section className="demoHero sectionBand sectionBand--light">
|
||||
<div className="container demoHero__grid">
|
||||
<div>
|
||||
@@ -24,7 +32,7 @@ export function DemoPage({demo}: DemoPageProps) {
|
||||
<p>{demo.description}</p>
|
||||
<div className="buttonRow">
|
||||
<ButtonLink href="#podglad">Zobacz demo</ButtonLink>
|
||||
<ButtonLink href={contact.url} variant="secondary" isExternal>
|
||||
<ButtonLink href="#kontakt" variant="secondary">
|
||||
Porozmawiajmy
|
||||
</ButtonLink>
|
||||
</div>
|
||||
@@ -70,7 +78,7 @@ export function DemoPage({demo}: DemoPageProps) {
|
||||
<div className="container finalCta finalCta--dark">
|
||||
<h2>{demo.cta.title}</h2>
|
||||
<p>{demo.cta.description}</p>
|
||||
<ButtonLink href={contact.url} isExternal>
|
||||
<ButtonLink href="#kontakt">
|
||||
{demo.cta.button}
|
||||
</ButtonLink>
|
||||
</div>
|
||||
@@ -82,6 +90,7 @@ export function DemoPage({demo}: DemoPageProps) {
|
||||
<Faq items={demo.faq} />
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import {usePathname, useSearchParams} from 'next/navigation';
|
||||
import type {DemoContent} from '@/types/content';
|
||||
|
||||
type DemoSubnavProps = {
|
||||
demos: DemoContent[];
|
||||
activeSlug?: DemoContent['slug'];
|
||||
hrefType?: 'page' | 'browser';
|
||||
};
|
||||
|
||||
export function DemoSubnav({demos, activeSlug, hrefType = 'page'}: DemoSubnavProps) {
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const selectedSlug = activeSlug ?? searchParams.get('demo') ?? demos[0]?.slug;
|
||||
|
||||
return (
|
||||
<nav className="demoSubnav" aria-label="Dema konfiguratorów">
|
||||
{demos.map(demo => {
|
||||
const isActive = selectedSlug === demo.slug || pathname === `/pl/${demo.slug}`;
|
||||
const href = hrefType === 'browser' ? `/pl/dema?demo=${demo.slug}` : `/pl/${demo.slug}`;
|
||||
|
||||
return (
|
||||
<Link className={isActive ? 'isActive' : undefined} href={href} key={demo.slug}>
|
||||
<span>{demo.eyebrow}</span>
|
||||
{demo.title}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
@@ -12,29 +12,11 @@ export function Footer() {
|
||||
</Link>
|
||||
<p>Copyright © 2026, Ultifide</p>
|
||||
</div>
|
||||
<nav className="footer__nav" aria-label="Nawigacja stopki">
|
||||
<Link href="/pl">Platforma</Link>
|
||||
<Link href="/pl/demo">Demo biurka</Link>
|
||||
<Link href="/pl/demo-room">Demo wnętrz</Link>
|
||||
<Link href="/pl/demo-door">Demo drzwi</Link>
|
||||
<a href={contact.url}>Kontakt</a>
|
||||
</nav>
|
||||
<address className="footer__contact">
|
||||
<strong>Contact Us</strong>
|
||||
{contact.address.map(line => (
|
||||
<span key={line}>{line}</span>
|
||||
))}
|
||||
<a href={`mailto:${contact.email}`}>{contact.email}</a>
|
||||
<a href={`tel:${contact.phone.replaceAll(' ', '')}`}>{contact.phone}</a>
|
||||
</address>
|
||||
<div className="footer__social">
|
||||
<a href={contact.linkedin} aria-label="LinkedIn Ultifide">
|
||||
in
|
||||
</a>
|
||||
<a href={contact.facebook} aria-label="Facebook Ultifide">
|
||||
f
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
|
||||
+24
-23
@@ -4,15 +4,13 @@ import {Menu, X} from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import {useEffect, useState} from 'react';
|
||||
import {usePathname} from 'next/navigation';
|
||||
import {contact} from '@/config/contact';
|
||||
import {Logo} from './Logo';
|
||||
|
||||
const navItems = [
|
||||
{id: 'dema', label: 'Dema', href: '/pl#dema'},
|
||||
{id: 'platforma', label: 'Platforma', href: '/pl#platforma'},
|
||||
{id: 'dema', label: 'Dema', href: '/pl/dema'},
|
||||
{id: 'funkcje', label: 'Funkcje', href: '/pl#funkcje'},
|
||||
{id: 'faq', label: 'FAQ', href: '/pl#faq'},
|
||||
{id: 'kontakt', label: 'Kontakt', href: contact.url}
|
||||
{id: 'faq', label: 'FAQ', href: '/pl#faq'}
|
||||
];
|
||||
|
||||
export function Header() {
|
||||
@@ -25,33 +23,36 @@ export function Header() {
|
||||
return;
|
||||
}
|
||||
|
||||
const sections = ['platforma', 'dema', 'funkcje', 'faq']
|
||||
const sections = ['dema', 'platforma', 'funkcje', 'faq', 'kontakt']
|
||||
.map(id => document.getElementById(id))
|
||||
.filter((section): section is HTMLElement => Boolean(section));
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
entries => {
|
||||
const visibleEntry = entries
|
||||
.filter(entry => entry.isIntersecting)
|
||||
.sort((first, second) => second.intersectionRatio - first.intersectionRatio)[0];
|
||||
function updateActiveSection() {
|
||||
const activationLine = 120;
|
||||
const currentSection = sections.reduce<HTMLElement | null>((active, section) => {
|
||||
const sectionTop = section.getBoundingClientRect().top;
|
||||
|
||||
if (visibleEntry?.target.id) {
|
||||
setActiveSection(visibleEntry.target.id);
|
||||
if (sectionTop <= activationLine) {
|
||||
return section;
|
||||
}
|
||||
},
|
||||
{rootMargin: '-30% 0px -55% 0px', threshold: [0.1, 0.35, 0.6]}
|
||||
);
|
||||
|
||||
sections.forEach(section => observer.observe(section));
|
||||
return active;
|
||||
}, null);
|
||||
|
||||
return () => observer.disconnect();
|
||||
setActiveSection(currentSection?.id ?? 'platforma');
|
||||
}
|
||||
|
||||
updateActiveSection();
|
||||
window.addEventListener('scroll', updateActiveSection, {passive: true});
|
||||
window.addEventListener('resize', updateActiveSection);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', updateActiveSection);
|
||||
window.removeEventListener('resize', updateActiveSection);
|
||||
};
|
||||
}, [pathname]);
|
||||
|
||||
function isActive(itemId: string) {
|
||||
if (itemId === 'kontakt') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pathname === '/pl') {
|
||||
return itemId === activeSection;
|
||||
}
|
||||
@@ -76,8 +77,8 @@ export function Header() {
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
<a className="siteHeader__contact" href={contact.url}>
|
||||
Porozmawiajmy
|
||||
<a className={activeSection === 'kontakt' ? 'siteHeader__contact isActive' : 'siteHeader__contact'} href="#kontakt">
|
||||
Kontakt
|
||||
</a>
|
||||
<button
|
||||
className="siteHeader__menu"
|
||||
|
||||
@@ -44,10 +44,6 @@ export function HeroVisual() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="heroVisual__footer">
|
||||
<span>3D preview</span>
|
||||
<strong>API-ready</strong>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user