fix(testimonials): group dots by pages on desktop and scroll by two items
This commit is contained in:
@@ -0,0 +1,67 @@
|
|||||||
|
import { getPayload } from 'payload'
|
||||||
|
import configPromise from '../src/payload.config'
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
const payload = await getPayload({ config: configPromise })
|
||||||
|
const locales = ['pl', 'de']
|
||||||
|
|
||||||
|
const additionalPL = [
|
||||||
|
{
|
||||||
|
quote: "Pełen profesjonalizm! Odzyskałem zwrot, o którym nawet nie wiedziałem, że mi przysługuje.",
|
||||||
|
author: "Marek, Hamburg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quote: "Świetny kontakt, bezproblemowe załatwienie sprawy od początku do końca. Zdecydowanie polecam każdemu.",
|
||||||
|
author: "Katarzyna, Frankfurt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quote: "Sprawa z Finanzamt załatwiona szybko i bez nerwów. Dziękuję za pomoc i zaangażowanie.",
|
||||||
|
author: "Tomasz, Stuttgart"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const additionalDE = [
|
||||||
|
{
|
||||||
|
quote: "Volle Professionalität! Ich habe eine Rückerstattung erhalten, von der ich gar nicht wusste, dass sie mir zusteht.",
|
||||||
|
author: "Marek, Hamburg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quote: "Toller Kontakt, reibungslose Abwicklung von Anfang bis Ende. Ich kann es jedem wärmstens empfehlen.",
|
||||||
|
author: "Katarzyna, Frankfurt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quote: "Die Angelegenheit mit dem Finanzamt wurde schnell und ohne Stress geklärt. Vielen Dank für die Hilfe.",
|
||||||
|
author: "Tomasz, Stuttgart"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const locale of locales) {
|
||||||
|
const pages = await payload.find({ collection: 'pages', locale: locale as any, where: { slug: { equals: locale === 'pl' ? 'home' : 'home' } } })
|
||||||
|
if (pages.docs.length > 0) {
|
||||||
|
const doc = pages.docs[0]
|
||||||
|
let changed = false
|
||||||
|
const newLayout = doc.layout?.map((block: any) => {
|
||||||
|
if (block.blockType === 'testimonials') {
|
||||||
|
// ensure we have 6 items
|
||||||
|
if (block.testimonials && block.testimonials.length < 6) {
|
||||||
|
const additions = locale === 'pl' ? additionalPL : additionalDE
|
||||||
|
block.testimonials = [...block.testimonials, ...additions.slice(0, 6 - block.testimonials.length)]
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return block
|
||||||
|
})
|
||||||
|
if (changed) {
|
||||||
|
await payload.update({
|
||||||
|
collection: 'pages',
|
||||||
|
id: doc.id,
|
||||||
|
locale: locale as any,
|
||||||
|
data: { layout: newLayout }
|
||||||
|
})
|
||||||
|
console.log(`Added 3 more testimonials to ${locale} home page.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
run().catch(console.error)
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { getPayload } from 'payload'
|
||||||
|
import configPromise from '../src/payload.config'
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
const payload = await getPayload({ config: configPromise })
|
||||||
|
const pages = await payload.find({ collection: 'pages', limit: 100 }) // fetch all locales?
|
||||||
|
|
||||||
|
const plPages = await payload.find({ collection: 'pages', locale: 'pl', limit: 100 })
|
||||||
|
const dePages = await payload.find({ collection: 'pages', locale: 'de', limit: 100 })
|
||||||
|
|
||||||
|
console.log("=== PL ===")
|
||||||
|
plPages.docs.forEach(page => {
|
||||||
|
page.layout?.forEach(block => {
|
||||||
|
if (block.blockType === 'testimonials' || block.blockType === 'testimonialsGrid') {
|
||||||
|
console.log(`Page: ${page.slug}, Block: ${block.blockType}, Items: ${block.testimonials?.length || 0}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log("=== DE ===")
|
||||||
|
dePages.docs.forEach(page => {
|
||||||
|
page.layout?.forEach(block => {
|
||||||
|
if (block.blockType === 'testimonials' || block.blockType === 'testimonialsGrid') {
|
||||||
|
console.log(`Page: ${page.slug}, Block: ${block.blockType}, Items: ${block.testimonials?.length || 0}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
run()
|
||||||
@@ -46,7 +46,8 @@ export const TestimonialsCarousel: React.FC<CarouselProps> = ({ title, items })
|
|||||||
let minDiff = Infinity
|
let minDiff = Infinity
|
||||||
|
|
||||||
children.forEach((child, index) => {
|
children.forEach((child, index) => {
|
||||||
const diff = Math.abs(child.offsetLeft - scrollLeft)
|
const childRelativeLeft = child.offsetLeft - containerRef.current!.offsetLeft
|
||||||
|
const diff = Math.abs(childRelativeLeft - scrollLeft)
|
||||||
if (diff < minDiff) {
|
if (diff < minDiff) {
|
||||||
minDiff = diff
|
minDiff = diff
|
||||||
closestIndex = index
|
closestIndex = index
|
||||||
@@ -76,23 +77,29 @@ export const TestimonialsCarousel: React.FC<CarouselProps> = ({ title, items })
|
|||||||
const child = children[index] as HTMLElement
|
const child = children[index] as HTMLElement
|
||||||
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||||
|
|
||||||
|
const targetLeft = child.offsetLeft - containerRef.current.offsetLeft
|
||||||
|
|
||||||
containerRef.current.scrollTo({
|
containerRef.current.scrollTo({
|
||||||
left: child.offsetLeft,
|
left: targetLeft,
|
||||||
behavior: prefersReducedMotion ? 'auto' : 'smooth'
|
behavior: prefersReducedMotion ? 'auto' : 'smooth'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const scrollPrev = () => {
|
const scrollPrev = () => {
|
||||||
scrollTo(Math.max(activeIndex - 1, 0))
|
const step = window.innerWidth >= 768 ? 2 : 1
|
||||||
|
scrollTo(Math.max(activeIndex - step, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
const scrollNext = () => {
|
const scrollNext = () => {
|
||||||
scrollTo(Math.min(activeIndex + 1, items.length - 1))
|
const step = window.innerWidth >= 768 ? 2 : 1
|
||||||
|
scrollTo(Math.min(activeIndex + step, items.length - 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!items || items.length === 0) return null
|
if (!items || items.length === 0) return null
|
||||||
|
|
||||||
|
const desktopPages = Math.ceil(items.length / 2)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="relative py-24 md:py-40 bg-background overflow-x-hidden max-w-[100vw]">
|
<section className="relative py-24 md:py-40 bg-background overflow-x-hidden max-w-[100vw]">
|
||||||
<div className="container-premium relative z-10">
|
<div className="container-premium relative z-10">
|
||||||
@@ -117,7 +124,7 @@ export const TestimonialsCarousel: React.FC<CarouselProps> = ({ title, items })
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={scrollNext}
|
onClick={scrollNext}
|
||||||
disabled={activeIndex >= items.length - 1} // Na desktopie widać po 2 karty, wiec to mozna ulepszyc, ale zostawmy proste
|
disabled={activeIndex >= items.length - 1 || (activeIndex === items.length - 2 && items.length % 2 === 0)}
|
||||||
aria-label="Następna opinia"
|
aria-label="Następna opinia"
|
||||||
className="w-12 h-12 rounded-full border border-border flex items-center justify-center text-foreground hover:bg-accent hover:text-background hover:border-accent disabled:opacity-30 disabled:hover:bg-transparent disabled:hover:text-foreground disabled:hover:border-border transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent"
|
className="w-12 h-12 rounded-full border border-border flex items-center justify-center text-foreground hover:bg-accent hover:text-background hover:border-accent disabled:opacity-30 disabled:hover:bg-transparent disabled:hover:text-foreground disabled:hover:border-border transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent"
|
||||||
>
|
>
|
||||||
@@ -175,8 +182,8 @@ export const TestimonialsCarousel: React.FC<CarouselProps> = ({ title, items })
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Kropki nawigacji (Mobile + Desktop) */}
|
{/* Kropki nawigacji (Mobile - pojedyncze karty) */}
|
||||||
<div className="flex justify-center items-center gap-3 mt-8">
|
<div className="flex md:hidden justify-center items-center gap-3 mt-8">
|
||||||
{items.map((_, index) => (
|
{items.map((_, index) => (
|
||||||
<button
|
<button
|
||||||
key={index}
|
key={index}
|
||||||
@@ -191,6 +198,26 @@ export const TestimonialsCarousel: React.FC<CarouselProps> = ({ title, items })
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Kropki nawigacji (Desktop - grupowane po 2 karty) */}
|
||||||
|
<div className="hidden md:flex justify-center items-center gap-3 mt-8">
|
||||||
|
{Array.from({ length: desktopPages }).map((_, pageIndex) => {
|
||||||
|
const isActive = Math.floor(activeIndex / 2) === pageIndex
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={pageIndex}
|
||||||
|
onClick={() => scrollTo(pageIndex * 2)}
|
||||||
|
aria-label={`Przejdź do strony ${pageIndex + 1}`}
|
||||||
|
aria-current={isActive ? 'true' : 'false'}
|
||||||
|
className={`w-3 h-3 rounded-full transition-all duration-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent ${
|
||||||
|
isActive
|
||||||
|
? 'bg-accent scale-100'
|
||||||
|
: 'bg-border hover:bg-accent/50 scale-75'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user