audio and video
This commit is contained in:
@@ -9,6 +9,7 @@ import Image from 'next/image'
|
|||||||
import Loading from '../loading'
|
import Loading from '../loading'
|
||||||
import Rate from 'rc-rate'
|
import Rate from 'rc-rate'
|
||||||
import { ReviewItem } from './types/ProductTypes'
|
import { ReviewItem } from './types/ProductTypes'
|
||||||
|
import ServiceAudios from './components/ServiceAudios'
|
||||||
|
|
||||||
// Default avatar image
|
// Default avatar image
|
||||||
const defaultAvatar = '/images/logo.svg' // Using an existing image
|
const defaultAvatar = '/images/logo.svg' // Using an existing image
|
||||||
@@ -86,14 +87,17 @@ const DetailService: FC<{ id: string }> = ({ id }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{getDetailService?.data?.data?.danakService?.images && (
|
{getDetailService?.data?.data?.danakService && (
|
||||||
<ServiceImages images={getDetailService.data.data.danakService.images} />
|
<ServiceImages item={getDetailService.data.data.danakService} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className='mt-8 bg-white p-6 rounded-3xl'>
|
<div className='mt-8 bg-white p-6 rounded-3xl'>
|
||||||
<div className='text-sm'>
|
<div className='text-sm'>
|
||||||
توضیحات کامل
|
توضیحات کامل
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ServiceAudios item={getDetailService.data} />
|
||||||
|
|
||||||
{getDetailService.data?.data?.danakService?.metaDescription && (
|
{getDetailService.data?.data?.danakService?.metaDescription && (
|
||||||
<ExpandableContent html={getDetailService.data.data.danakService.metaDescription} />
|
<ExpandableContent html={getDetailService.data.data.danakService.metaDescription} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { FC } from 'react'
|
||||||
|
import { ServiceDetailResponse } from '../types/ProductTypes'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
item: ServiceDetailResponse | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const ServiceAudios: FC<Props> = ({ item }) => {
|
||||||
|
if (!item?.data?.danakService?.audios || item.data.danakService.audios.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='mb-6'>
|
||||||
|
|
||||||
|
<div className='space-y-3'>
|
||||||
|
{item.data.danakService.audios.map((audio) => (
|
||||||
|
<div key={audio.id} className='bg-white rounded-2xl p-4'>
|
||||||
|
<div className='flex items-center justify-between mb-3'>
|
||||||
|
<span className='text-sm text-gray-700'>فایل صوتی</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<audio
|
||||||
|
src={audio.audioUrl}
|
||||||
|
controls
|
||||||
|
className='w-full'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ServiceAudios
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import { FC } from 'react'
|
import { FC, useState, useRef } from 'react'
|
||||||
import { Swiper, SwiperSlide } from 'swiper/react'
|
import { Swiper, SwiperSlide } from 'swiper/react'
|
||||||
|
import { Play, Pause } from 'iconsax-react'
|
||||||
|
import { DanakService } from '../types/ProductTypes'
|
||||||
|
|
||||||
const SWIPER_SLIDE_STYLE = {
|
const SWIPER_SLIDE_STYLE = {
|
||||||
overflow: 'visible !important',
|
overflow: 'visible !important',
|
||||||
@@ -8,10 +10,46 @@ const SWIPER_SLIDE_STYLE = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
images: { id: string, imageUrl: string }[]
|
item: DanakService
|
||||||
}
|
}
|
||||||
|
|
||||||
const ServiceImages: FC<Props> = ({ images }) => {
|
const ServiceImages: FC<Props> = ({ item }) => {
|
||||||
|
const [playingVideos, setPlayingVideos] = useState<{ [key: string]: boolean }>({})
|
||||||
|
const videoRefs = useRef<{ [key: string]: HTMLVideoElement | null }>({})
|
||||||
|
|
||||||
|
const toggleVideo = (videoId: string) => {
|
||||||
|
const video = videoRefs.current[videoId]
|
||||||
|
if (!video) return
|
||||||
|
|
||||||
|
if (playingVideos[videoId]) {
|
||||||
|
video.pause()
|
||||||
|
setPlayingVideos(prev => ({
|
||||||
|
...prev,
|
||||||
|
[videoId]: false
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
video.play()
|
||||||
|
setPlayingVideos(prev => ({
|
||||||
|
...prev,
|
||||||
|
[videoId]: true
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleVideoPlay = (videoId: string) => {
|
||||||
|
setPlayingVideos(prev => ({
|
||||||
|
...prev,
|
||||||
|
[videoId]: true
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleVideoPause = (videoId: string) => {
|
||||||
|
setPlayingVideos(prev => ({
|
||||||
|
...prev,
|
||||||
|
[videoId]: false
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full mt-8'>
|
<div className='w-full mt-8'>
|
||||||
<Swiper
|
<Swiper
|
||||||
@@ -19,18 +57,44 @@ const ServiceImages: FC<Props> = ({ images }) => {
|
|||||||
spaceBetween={20} // فاصله پیشفرض بین اسلایدها
|
spaceBetween={20} // فاصله پیشفرض بین اسلایدها
|
||||||
slidesPerView='auto'
|
slidesPerView='auto'
|
||||||
>
|
>
|
||||||
|
{/* نمایش ویدیوها */}
|
||||||
|
{item?.videos?.map((video) => (
|
||||||
|
<SwiperSlide key={video.id} className='xl:max-w-[38%] max-w-[80%]' style={SWIPER_SLIDE_STYLE}>
|
||||||
|
<div className='bg-white bg-opacity-50 w-full p-2 rounded-2xl relative'>
|
||||||
|
<video
|
||||||
|
ref={(el) => {
|
||||||
|
videoRefs.current[video.id] = el
|
||||||
|
}}
|
||||||
|
src={video.videoUrl}
|
||||||
|
className='w-full rounded-2xl'
|
||||||
|
controls={false}
|
||||||
|
loop
|
||||||
|
playsInline
|
||||||
|
onPlay={() => handleVideoPlay(video.id)}
|
||||||
|
onPause={() => handleVideoPause(video.id)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={() => toggleVideo(video.id)}
|
||||||
|
className='absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-black bg-opacity-50 hover:bg-opacity-70 text-white rounded-full p-3 transition-all duration-200'
|
||||||
|
>
|
||||||
|
{playingVideos[video.id] ? (
|
||||||
|
<Pause size={24} color='white' />
|
||||||
|
) : (
|
||||||
|
<Play size={24} color='white' />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</SwiperSlide>
|
||||||
|
))}
|
||||||
|
|
||||||
{
|
{/* نمایش تصاویر */}
|
||||||
images.map((item) => {
|
{item?.images?.map((image) => (
|
||||||
return (
|
<SwiperSlide key={image.id} className='xl:max-w-[38%] max-w-[80%]' style={SWIPER_SLIDE_STYLE}>
|
||||||
<SwiperSlide key={item.id} className='xl:max-w-[38%] max-w-[80%]' style={SWIPER_SLIDE_STYLE}>
|
<div className='bg-white bg-opacity-50 w-full p-2 rounded-2xl'>
|
||||||
<div className='bg-white bg-opacity-50 w-full p-2 rounded-2xl'>
|
<Image alt={image.imageUrl} width={2000} height={1000} src={image.imageUrl} className='w-full h-auto rounded-2xl' />
|
||||||
<Image alt={item.imageUrl} width={2000} height={1000} src={item.imageUrl} className='w-full h-auto rounded-2xl' />
|
</div>
|
||||||
</div>
|
</SwiperSlide>
|
||||||
</SwiperSlide>
|
))}
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</Swiper>
|
</Swiper>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -84,6 +84,8 @@ export interface DanakService {
|
|||||||
reviews: ReviewItem[];
|
reviews: ReviewItem[];
|
||||||
// reviews: any[]; // replace with Review[] if review structure is defined
|
// reviews: any[]; // replace with Review[] if review structure is defined
|
||||||
deletedAt: string | null;
|
deletedAt: string | null;
|
||||||
|
videos: { id: string; createdAt: string; videoUrl: string }[];
|
||||||
|
audios: { id: string; createdAt: string; audioUrl: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ServiceDetailResponse = ApiResponse<{
|
export type ServiceDetailResponse = ApiResponse<{
|
||||||
|
|||||||
Reference in New Issue
Block a user