-
setFormData({ ...formData, videoUrl: e.target.value })}
- placeholder="https://... MP4 or Aparat/YouTube iframe"
- className="w-full px-3 py-2 border rounded-xl text-xs font-mono"
- dir="ltr"
- />
-
+
+
+
+ setFormData({ ...formData, podcastTitle: e.target.value })}
+ placeholder="مثلاً: بررسی بالینی اثر مکمل بر سلامت مفاصل سگها"
+ className="w-full px-3 py-2 border rounded-xl text-xs bg-white font-medium"
+ />
+
+
+
+
+
+
+
+ {/* Section 2: Video Management Card */}
+
+
+
+ 🎬 تنظیمات ویدیو و راهنمای مصرف
+
+ {formData.videoUrl && (
+
+ ویدیو متصل است
+
+ )}
+
+
+
+
+
+
+ setFormData({ ...formData, videoUrl: e.target.value })}
+ placeholder="https://... MP4 or Aparat iframe"
+ className="w-full px-3 py-2 border rounded-xl text-xs font-mono bg-white"
+ dir="ltr"
+ />
+
+
+
+
+
+
+
+ setFormData({ ...formData, videoCover: e.target.value })}
+ placeholder="در صورت خالی بودن، تصویر محصول استفاده میشود"
+ className="w-full px-3 py-2 border rounded-xl text-xs font-mono bg-white"
+ dir="ltr"
+ />
+
+
-
-
-
-
setFormData({ ...formData, pdfUrl: e.target.value })}
- placeholder="https://... PDF catalog"
- className="w-full px-3 py-2 border rounded-xl text-xs font-mono"
- dir="ltr"
- />
-
+
+
+
+ setFormData({ ...formData, videoTitle: e.target.value })}
+ placeholder="مثلاً: راهنمای نحوه خوراندن و دوز مصرف مکمل"
+ className="w-full px-3 py-2 border rounded-xl text-xs bg-white font-medium"
+ />
+
+
+
+
+
+
+
+ {/* Section 3: PDF / Document Management Card */}
+
+
+
+ 📄 تنظیمات کاتالوگ و مستندات PDF
+
+ {formData.pdfUrl && (
+
+ فایل PDF متصل است
+
+ )}
+
+
+
+
+
+
+ setFormData({ ...formData, pdfUrl: e.target.value })}
+ placeholder="https://... /uploads/catalog.pdf"
+ className="w-full px-3 py-2 border rounded-xl text-xs font-mono bg-white"
+ dir="ltr"
+ />
+
+
+
+
+
+
+ setFormData({ ...formData, pdfCover: e.target.value })}
+ placeholder="در صورت خالی بودن، تصویر محصول استفاده میشود"
+ className="w-full px-3 py-2 border rounded-xl text-xs font-mono bg-white"
+ dir="ltr"
+ />
+
+
+
+
+
+
+
+ setFormData({ ...formData, pdfTitle: e.target.value })}
+ placeholder="مثلاً: کاتالوگ جامع مشخصات فنی و مطالعات بالینی"
+ className="w-full px-3 py-2 border rounded-xl text-xs bg-white font-medium"
+ />
+
+
+
+
+
diff --git a/frontend/application/components/PodcastPlayerModal.tsx b/frontend/application/components/PodcastPlayerModal.tsx
new file mode 100644
index 0000000..c119990
--- /dev/null
+++ b/frontend/application/components/PodcastPlayerModal.tsx
@@ -0,0 +1,391 @@
+"use client";
+import React, { useState, useRef, useEffect, useCallback } from "react";
+import { motion, AnimatePresence } from "motion/react";
+import {
+ Play,
+ Pause,
+ RotateCcw,
+ RotateCw,
+ Volume2,
+ VolumeX,
+ Download,
+ Share2,
+ X,
+ Check,
+ Headphones,
+ Loader2,
+ Sparkles
+} from "lucide-react";
+
+interface PodcastPlayerModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ podcast: {
+ audioUrl?: string;
+ title?: string;
+ description?: string;
+ cover?: string;
+ productName?: string;
+ fallbackCover?: string;
+ } | null;
+}
+
+const PLAYBACK_RATES = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5];
+
+export default function PodcastPlayerModal({ isOpen, onClose, podcast }: PodcastPlayerModalProps) {
+ const audioRef = useRef
(null);
+ const [isPlaying, setIsPlaying] = useState(false);
+ const [currentTime, setCurrentTime] = useState(0);
+ const [duration, setDuration] = useState(0);
+ const [volume, setVolume] = useState(1);
+ const [isMuted, setIsMuted] = useState(false);
+ const [playbackRate, setPlaybackRate] = useState(1);
+ const [showRateMenu, setShowRateMenu] = useState(false);
+ const [isBuffering, setIsBuffering] = useState(false);
+ const [isCopied, setIsCopied] = useState(false);
+
+ // Reset states on open/close
+ useEffect(() => {
+ if (isOpen) {
+ setIsPlaying(true);
+ setShowRateMenu(false);
+ setIsBuffering(false);
+ } else {
+ setIsPlaying(false);
+ setCurrentTime(0);
+ }
+ }, [isOpen, podcast]);
+
+ const togglePlay = useCallback(() => {
+ if (!audioRef.current) return;
+ if (audioRef.current.paused) {
+ audioRef.current.play().catch(console.error);
+ setIsPlaying(true);
+ } else {
+ audioRef.current.pause();
+ setIsPlaying(false);
+ }
+ }, []);
+
+ const handleSeek = (e: React.ChangeEvent) => {
+ const time = Number(e.target.value);
+ if (audioRef.current) {
+ audioRef.current.currentTime = time;
+ setCurrentTime(time);
+ }
+ };
+
+ const skipTime = (seconds: number) => {
+ if (audioRef.current) {
+ audioRef.current.currentTime = Math.max(0, Math.min(audioRef.current.duration || 0, audioRef.current.currentTime + seconds));
+ }
+ };
+
+ const handleRateChange = (rate: number) => {
+ if (audioRef.current) {
+ audioRef.current.playbackRate = rate;
+ setPlaybackRate(rate);
+ setShowRateMenu(false);
+ }
+ };
+
+ const toggleMute = () => {
+ if (audioRef.current) {
+ audioRef.current.muted = !isMuted;
+ setIsMuted(!isMuted);
+ }
+ };
+
+ const handleVolumeChange = (e: React.ChangeEvent) => {
+ const val = Number(e.target.value);
+ setVolume(val);
+ if (audioRef.current) {
+ audioRef.current.volume = val;
+ if (val === 0) {
+ setIsMuted(true);
+ audioRef.current.muted = true;
+ } else {
+ setIsMuted(false);
+ audioRef.current.muted = false;
+ }
+ }
+ };
+
+ const handleShare = async () => {
+ const shareUrl = window.location.href;
+ if (navigator.share) {
+ try {
+ await navigator.share({
+ title: podcast?.title || 'پادکست و بررسی تخصصی کنینا',
+ text: podcast?.description || '',
+ url: shareUrl
+ });
+ } catch {
+ // Fallback or dismissed
+ }
+ } else {
+ try {
+ await navigator.clipboard.writeText(shareUrl);
+ setIsCopied(true);
+ setTimeout(() => setIsCopied(false), 2500);
+ } catch (err) {
+ console.error('Copy failed:', err);
+ }
+ }
+ };
+
+ const handleDownload = () => {
+ if (!podcast?.audioUrl) return;
+ const a = document.createElement('a');
+ a.href = podcast.audioUrl;
+ a.download = `${podcast.title || 'canina-podcast'}.mp3`;
+ a.target = '_blank';
+ a.rel = 'noopener noreferrer';
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ };
+
+ const formatTime = (secs: number) => {
+ if (isNaN(secs) || secs < 0) return "00:00";
+ const mins = Math.floor(secs / 60);
+ const remainingSecs = Math.floor(secs % 60);
+ return `${mins.toString().padStart(2, "0")}:${remainingSecs.toString().padStart(2, "0")}`;
+ };
+
+ if (!isOpen || !podcast) return null;
+
+ const displayCover = podcast.cover || podcast.fallbackCover || '/images/default-podcast.png';
+
+ return (
+
+
+ {/* Backdrop */}
+
+
+ {/* Modal Content */}
+
+ {/* Top Bar (Header + Close) */}
+
+
+
+ پادکست و بررسی صوتی کنینا
+
+
+
+
+
+
+
+
+
+
+
+ {/* Hidden Audio Tag */}
+
+
+
+ );
+}
diff --git a/frontend/application/components/ProductPage.tsx b/frontend/application/components/ProductPage.tsx
index 08813bb..2e1bcc0 100644
--- a/frontend/application/components/ProductPage.tsx
+++ b/frontend/application/components/ProductPage.tsx
@@ -28,7 +28,8 @@ import {
AlertTriangle,
Share2,
Phone,
- Download
+ Download,
+ Play
} from "lucide-react";
const ICON_MAP: Record> = {
@@ -68,6 +69,8 @@ const useCalculatorStore = create((set) => ({
import Tooltip from "./Tooltip";
import SafeImage from "./SafeImage";
import ProductReviews from "./ProductReviews";
+import VideoModalPlayer from "./VideoModalPlayer";
+import PodcastPlayerModal from "./PodcastPlayerModal";
import { useRouter } from 'next/navigation';
@@ -83,6 +86,8 @@ export default function ProductPage({ productSlug }: { productSlug: string }) {
const [activeImage, setActiveImage] = useState(null);
const [isImageZoomOpen, setIsImageZoomOpen] = useState(false);
+ const [isVideoModalOpen, setIsVideoModalOpen] = useState(false);
+ const [isPodcastModalOpen, setIsPodcastModalOpen] = useState(false);
useEffect(() => {
Promise.resolve().then(() => setIsMounted(true));
@@ -687,84 +692,140 @@ export default function ProductPage({ productSlug }: { productSlug: string }) {