audio + video
This commit is contained in:
@@ -2,6 +2,7 @@ import { FC, Fragment, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Rate from 'rc-rate'
|
||||
import ServiceImages from './components/ServiceImages'
|
||||
import ServiceAudios from './components/ServiceAudios'
|
||||
import AvatarImage from '../../assets/images/avatar_image.png'
|
||||
import ServiceHeader from './components/ServiceHeader'
|
||||
import { useGetServiceBySlug } from './hooks/useServiceData'
|
||||
@@ -82,12 +83,15 @@ const DetailService: FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ServiceImages images={getDetailService?.data?.data?.danakService?.images} />
|
||||
<ServiceImages item={getDetailService?.data?.data?.danakService} />
|
||||
|
||||
<div className='mt-8 bg-white p-6 rounded-3xl'>
|
||||
<div>
|
||||
{t('service.compelete_description')}
|
||||
</div>
|
||||
|
||||
<ServiceAudios item={getDetailService?.data?.data?.danakService} />
|
||||
|
||||
<p
|
||||
dangerouslySetInnerHTML={{ __html: getDetailService.data?.data?.danakService?.metaDescription }}
|
||||
className={`text-[13px] mt-4 leading-6 ${!isExpanded ? 'line-clamp-4' : ''}`}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { FC } from 'react'
|
||||
import { ServiceDetailDataType } from '../types/ServiecTypes'
|
||||
|
||||
type Props = {
|
||||
item: ServiceDetailDataType
|
||||
}
|
||||
|
||||
const ServiceAudios: FC<Props> = ({ item }) => {
|
||||
if (!item?.audios || item.audios.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mb-6'>
|
||||
|
||||
<div className='space-y-3'>
|
||||
{item.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,5 +1,7 @@
|
||||
import { FC } from 'react'
|
||||
import { FC, useState, useRef } from 'react'
|
||||
import { Swiper, SwiperSlide } from 'swiper/react'
|
||||
import { ServiceDetailDataType } from '../types/ServiecTypes'
|
||||
import { Play, Pause } from 'iconsax-react'
|
||||
|
||||
const SWIPER_SLIDE_STYLE = {
|
||||
overflow: 'visible !important',
|
||||
@@ -7,10 +9,46 @@ const SWIPER_SLIDE_STYLE = {
|
||||
}
|
||||
|
||||
type Props = {
|
||||
images: { id: string, imageUrl: string }[]
|
||||
item: ServiceDetailDataType
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className='w-full mt-8'>
|
||||
<Swiper
|
||||
@@ -18,18 +56,44 @@ const ServiceImages: FC<Props> = ({ images }) => {
|
||||
spaceBetween={20} // فاصله پیشفرض بین اسلایدها
|
||||
slidesPerView='auto'
|
||||
>
|
||||
|
||||
{
|
||||
images.map((item) => {
|
||||
return (
|
||||
<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'>
|
||||
<img src={item.imageUrl} className='w-full rounded-2xl' />
|
||||
{/* نمایش ویدیوها */}
|
||||
{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>
|
||||
)
|
||||
})
|
||||
}
|
||||
))}
|
||||
|
||||
{/* نمایش تصاویر */}
|
||||
{item?.images?.map((image) => (
|
||||
<SwiperSlide key={image.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'>
|
||||
<img src={image.imageUrl} className='w-full rounded-2xl' />
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -36,6 +36,8 @@ export type ServiceDetailDataType = {
|
||||
icon: string;
|
||||
id: string;
|
||||
images: { id: string; createdAt: string; imageUrl: string }[];
|
||||
videos: { id: string; createdAt: string; videoUrl: string }[];
|
||||
audios: { id: string; createdAt: string; audioUrl: string }[];
|
||||
isActive: boolean;
|
||||
isDanakSuggest: boolean;
|
||||
link: string;
|
||||
|
||||
Reference in New Issue
Block a user