diff --git a/Dockerfile b/Dockerfile index 8c4b6f4..56f1150 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,7 @@ FROM base AS builder COPY --from=deps /app/node_modules ./node_modules COPY . . ENV NEXT_TELEMETRY_DISABLED=1 +RUN NODE_OPTIONS="--import tsx/esm" pnpm payload generate:importmap RUN pnpm run build # ---- Runner ---- diff --git a/public/videos/dyvolis/poster-01.jpg b/public/videos/dyvolis/poster-01.jpg new file mode 100644 index 0000000..5b30d45 Binary files /dev/null and b/public/videos/dyvolis/poster-01.jpg differ diff --git a/public/videos/dyvolis/poster-02.jpg b/public/videos/dyvolis/poster-02.jpg new file mode 100644 index 0000000..d248619 Binary files /dev/null and b/public/videos/dyvolis/poster-02.jpg differ diff --git a/public/videos/dyvolis/poster-03.jpg b/public/videos/dyvolis/poster-03.jpg new file mode 100644 index 0000000..b616d8c Binary files /dev/null and b/public/videos/dyvolis/poster-03.jpg differ diff --git a/public/videos/dyvolis/poster-04.jpg b/public/videos/dyvolis/poster-04.jpg new file mode 100644 index 0000000..922c9f0 Binary files /dev/null and b/public/videos/dyvolis/poster-04.jpg differ diff --git a/public/videos/dyvolis/poster-05.jpg b/public/videos/dyvolis/poster-05.jpg new file mode 100644 index 0000000..970613e Binary files /dev/null and b/public/videos/dyvolis/poster-05.jpg differ diff --git a/public/videos/dyvolis/poster-06.jpg b/public/videos/dyvolis/poster-06.jpg new file mode 100644 index 0000000..dede59f Binary files /dev/null and b/public/videos/dyvolis/poster-06.jpg differ diff --git a/public/videos/dyvolis/poster-07.jpg b/public/videos/dyvolis/poster-07.jpg new file mode 100644 index 0000000..e6eef2a Binary files /dev/null and b/public/videos/dyvolis/poster-07.jpg differ diff --git a/public/videos/dyvolis/video-01.mp4 b/public/videos/dyvolis/video-01.mp4 new file mode 100644 index 0000000..df51a4d Binary files /dev/null and b/public/videos/dyvolis/video-01.mp4 differ diff --git a/public/videos/dyvolis/video-02.mp4 b/public/videos/dyvolis/video-02.mp4 new file mode 100644 index 0000000..de7ecde Binary files /dev/null and b/public/videos/dyvolis/video-02.mp4 differ diff --git a/public/videos/dyvolis/video-03.mp4 b/public/videos/dyvolis/video-03.mp4 new file mode 100644 index 0000000..cf8e11e Binary files /dev/null and b/public/videos/dyvolis/video-03.mp4 differ diff --git a/public/videos/dyvolis/video-04.mp4 b/public/videos/dyvolis/video-04.mp4 new file mode 100644 index 0000000..451e645 Binary files /dev/null and b/public/videos/dyvolis/video-04.mp4 differ diff --git a/public/videos/dyvolis/video-05.mp4 b/public/videos/dyvolis/video-05.mp4 new file mode 100644 index 0000000..fd18aa3 Binary files /dev/null and b/public/videos/dyvolis/video-05.mp4 differ diff --git a/public/videos/dyvolis/video-06.mp4 b/public/videos/dyvolis/video-06.mp4 new file mode 100644 index 0000000..cd1e03b Binary files /dev/null and b/public/videos/dyvolis/video-06.mp4 differ diff --git a/public/videos/dyvolis/video-07.mp4 b/public/videos/dyvolis/video-07.mp4 new file mode 100644 index 0000000..49851d1 Binary files /dev/null and b/public/videos/dyvolis/video-07.mp4 differ diff --git a/src/app/(frontend)/lokatsii/dyvolis/page.tsx b/src/app/(frontend)/lokatsii/dyvolis/page.tsx index d0621fe..c64e787 100644 --- a/src/app/(frontend)/lokatsii/dyvolis/page.tsx +++ b/src/app/(frontend)/lokatsii/dyvolis/page.tsx @@ -55,6 +55,15 @@ export default async function DyvoLisPage() { description: item.description, })) ?? undefined } + reviewVideos={ + data?.reviewVideos?.map( + (v: { src: string; poster?: string | null; label?: string | null }) => ({ + src: v.src, + poster: v.poster ?? null, + label: v.label ?? null, + }) + ) ?? undefined + } /> true, create: isAdminOrEditor, update: isAdminOrEditor, delete: isAdminOrEditor }, + access: { + read: () => true, + create: isAdminOrEditor, + update: isAdminOrEditor, + delete: isAdminOrEditor, + }, admin: { useAsTitle: 'name', defaultColumns: ['name', 'source', 'rating', 'showOnHome', 'sort'], @@ -26,6 +31,16 @@ export const Reviews: CollectionConfig = { ], defaultValue: 'google', }, + { + name: 'videoUrl', + type: 'text', + admin: { description: 'URL відео-відгуку, напр. /videos/dyvolis/video-01.mp4' }, + }, + { + name: 'videoPoster', + type: 'text', + admin: { description: 'URL постеру для відео' }, + }, { name: 'showOnHome', type: 'checkbox', defaultValue: true }, { name: 'sort', type: 'number', defaultValue: 0 }, ], diff --git a/src/components/sections/DyvoLisWhyVisit.tsx b/src/components/sections/DyvoLisWhyVisit.tsx index 5c7b9b5..9b862ea 100644 --- a/src/components/sections/DyvoLisWhyVisit.tsx +++ b/src/components/sections/DyvoLisWhyVisit.tsx @@ -1,10 +1,8 @@ 'use client' -/* eslint-disable @next/next/no-img-element */ import { useState, useRef, useEffect } from 'react' const FONT_MONT = { fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' } -const IMG_PLAY = '/images/figma/btn-video-play.svg' const DEFAULT_ITEMS = [ { @@ -24,29 +22,42 @@ const DEFAULT_ITEMS = [ }, ] +const FALLBACK_VIDEOS = [ + { src: '/videos/dyvolis/video-01.mp4', poster: '/videos/dyvolis/poster-01.jpg' }, + { src: '/videos/dyvolis/video-02.mp4', poster: '/videos/dyvolis/poster-02.jpg' }, + { src: '/videos/dyvolis/video-03.mp4', poster: '/videos/dyvolis/poster-03.jpg' }, + { src: '/videos/dyvolis/video-04.mp4', poster: '/videos/dyvolis/poster-04.jpg' }, + { src: '/videos/dyvolis/video-05.mp4', poster: '/videos/dyvolis/poster-05.jpg' }, + { src: '/videos/dyvolis/video-06.mp4', poster: '/videos/dyvolis/poster-06.jpg' }, + { src: '/videos/dyvolis/video-07.mp4', poster: '/videos/dyvolis/poster-07.jpg' }, +] + +interface ReviewVideo { + src: string + poster?: string | null + label?: string | null +} + interface DyvoLisWhyVisitProps { title?: string items?: Array<{ title: string; description: string }> + reviewVideos?: ReviewVideo[] } -const VIDEO_REVIEWS = [ - { poster: '/images/dyvolis/photo-22.jpg' }, - { poster: '/images/dyvolis/photo-14.jpg' }, - { poster: '/images/dyvolis/photo-07.jpg' }, - { poster: '/images/dyvolis/photo-18.jpg' }, - { poster: '/images/dyvolis/photo-03.jpg' }, -] - export function DyvoLisWhyVisit({ title = 'Чому варто відвідати ДивоЛіс', items = DEFAULT_ITEMS, + reviewVideos, }: DyvoLisWhyVisitProps) { + const videos = reviewVideos && reviewVideos.length > 0 ? reviewVideos : FALLBACK_VIDEOS const [openIndex, setOpenIndex] = useState(0) const [videoActive, setVideoActive] = useState(0) + const [playingIndex, setPlayingIndex] = useState(null) const videoPausedRef = useRef(false) const accordionTimer = useRef | null>(null) const videoTimer = useRef | null>(null) - const vn = VIDEO_REVIEWS.length + const videoRefs = useRef<(HTMLVideoElement | null)[]>([]) + const vn = videos.length useEffect(() => { accordionTimer.current = setInterval(() => { @@ -76,6 +87,31 @@ export function DyvoLisWhyVisit({ }, 4000) } + function handlePlayVideo(i: number) { + if (playingIndex === i) return + // Pause previously playing video + if (playingIndex !== null && videoRefs.current[playingIndex]) { + videoRefs.current[playingIndex]!.pause() + videoRefs.current[playingIndex]!.currentTime = 0 + } + videoPausedRef.current = true + setPlayingIndex(i) + setVideoActive(i) + setTimeout(() => { + videoRefs.current[i]?.play() + }, 50) + } + + function handleVideoCarouselNav(i: number) { + if (playingIndex !== null && videoRefs.current[playingIndex]) { + videoRefs.current[playingIndex]!.pause() + videoRefs.current[playingIndex]!.currentTime = 0 + } + setPlayingIndex(null) + videoPausedRef.current = false + setVideoActive(i) + } + return (
@@ -152,38 +188,62 @@ export function DyvoLisWhyVisit({ videoPausedRef.current = true }} onMouseLeave={() => { - videoPausedRef.current = false + if (playingIndex === null) videoPausedRef.current = false }} > - {VIDEO_REVIEWS.map((v, i) => ( -
- {i -
- ))} - {/* Play button overlay */} -
- -
+ {videos.map((v, i) => { + const isActive = i === videoActive + const isPlaying = i === playingIndex + return ( +
+
+ ) + })}
{/* Carousel navigation */}
- {VIDEO_REVIEWS.map((_, i) => ( + {videos.map((_, i) => (
)} - {/* Badge */}
Відео
- {/* Footer */}

setAutoPaused(true)} onMouseLeave={() => setAutoPaused(false)} > - {/* Video card */} - - {doubled.map((review, idx) => { + if (review.videoUrl) { + return ( + + ) + } const avatarUrl = getMediaUrl(review.avatarBg) ?? IMG_AVATAR_DEFAULT return (

) })} - - {/* Second video card for seamless loop */} -