feat: add ViewCounter enhancements and file upload progress tracking
- Improved `ViewCounter` with visibility-based view increment using `IntersectionObserver` and 50% video progress tracking. - Added real-time file upload progress updates via Socket.io, including status and percentage feedback. - Integrated `ViewCounter` dynamically into `ContentCard` and removed redundant instances from static pages. - Updated backend upload logic to emit progress updates at different stages via the `EventsGateway`.
This commit is contained in:
@@ -36,6 +36,7 @@ import { ContentService } from "@/services/content.service";
|
||||
import { FavoriteService } from "@/services/favorite.service";
|
||||
import type { Content } from "@/types/content";
|
||||
import { UserContentEditDialog } from "./user-content-edit-dialog";
|
||||
import { ViewCounter } from "./view-counter";
|
||||
|
||||
interface ContentCardProps {
|
||||
content: Content;
|
||||
@@ -98,6 +99,8 @@ export function ContentCard({ content, onUpdate }: ContentCardProps) {
|
||||
await FavoriteService.add(content.id);
|
||||
setIsLiked(true);
|
||||
setLikesCount((prev) => prev + 1);
|
||||
// Considérer un like comme une vue
|
||||
ContentService.incrementViews(content.id).catch(() => {});
|
||||
}
|
||||
} catch (_error) {
|
||||
toast.error("Une erreur est survenue");
|
||||
@@ -146,6 +149,7 @@ export function ContentCard({ content, onUpdate }: ContentCardProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewCounter contentId={content.id} videoRef={videoRef} />
|
||||
<Card className="overflow-hidden border-none gap-0 shadow-none bg-transparent">
|
||||
<CardHeader className="p-3 flex flex-row items-center space-y-0 gap-3">
|
||||
<Avatar className="h-8 w-8 border">
|
||||
|
||||
@@ -1,23 +1,74 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { type RefObject, useEffect, useRef } from "react";
|
||||
import { ContentService } from "@/services/content.service";
|
||||
|
||||
interface ViewCounterProps {
|
||||
contentId: string;
|
||||
videoRef?: RefObject<HTMLVideoElement | null>;
|
||||
}
|
||||
|
||||
export function ViewCounter({ contentId }: ViewCounterProps) {
|
||||
export function ViewCounter({ contentId, videoRef }: ViewCounterProps) {
|
||||
const hasIncremented = useRef(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasIncremented.current) {
|
||||
ContentService.incrementViews(contentId).catch((err) => {
|
||||
console.error("Failed to increment views:", err);
|
||||
});
|
||||
hasIncremented.current = true;
|
||||
}
|
||||
}, [contentId]);
|
||||
const increment = () => {
|
||||
if (!hasIncremented.current) {
|
||||
ContentService.incrementViews(contentId).catch((err) => {
|
||||
console.error("Failed to increment views:", err);
|
||||
});
|
||||
hasIncremented.current = true;
|
||||
}
|
||||
};
|
||||
|
||||
return null;
|
||||
// 1. Observer pour la visibilité (IntersectionObserver)
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const entry = entries[0];
|
||||
if (entry.isIntersecting) {
|
||||
// Si c'est une image (pas de videoRef), on attend 3 secondes
|
||||
if (!videoRef) {
|
||||
const timer = setTimeout(() => {
|
||||
increment();
|
||||
}, 3000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ threshold: 0.5 },
|
||||
);
|
||||
|
||||
if (containerRef.current) {
|
||||
observer.observe(containerRef.current);
|
||||
}
|
||||
|
||||
// 2. Logique pour la vidéo (> 50%)
|
||||
let videoElement: HTMLVideoElement | null = null;
|
||||
const handleTimeUpdate = () => {
|
||||
if (videoElement && videoElement.duration > 0) {
|
||||
const progress = videoElement.currentTime / videoElement.duration;
|
||||
if (progress >= 0.5) {
|
||||
increment();
|
||||
videoElement.removeEventListener("timeupdate", handleTimeUpdate);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (videoRef?.current) {
|
||||
videoElement = videoRef.current;
|
||||
videoElement.addEventListener("timeupdate", handleTimeUpdate);
|
||||
}
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
if (videoElement) {
|
||||
videoElement.removeEventListener("timeupdate", handleTimeUpdate);
|
||||
}
|
||||
};
|
||||
}, [contentId, videoRef]);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="absolute inset-0 pointer-events-none" />
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user