fix: split collaboration request controllers
هذا الالتزام موجود في:
162
oudelaa_dashboard/components/dashboard/media-inspector.tsx
Normal file
162
oudelaa_dashboard/components/dashboard/media-inspector.tsx
Normal file
@@ -0,0 +1,162 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { FileAudio, FileImage, FileVideo, LinkIcon } from "lucide-react";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { resolveMediaUrl } from "@/lib/media-url";
|
||||
import type { ApiPost } from "@/types/api";
|
||||
|
||||
function pickImageUrl(post: ApiPost) {
|
||||
const variant = Array.isArray(post.imageVariants)
|
||||
? post.imageVariants[0]
|
||||
: post.imageVariants;
|
||||
|
||||
return (
|
||||
post.imageItems?.find((item) => item.variants?.medium || item.url)?.variants?.medium ??
|
||||
post.imageItems?.find((item) => item.url)?.url ??
|
||||
variant?.medium ??
|
||||
variant?.thumbnail ??
|
||||
post.imageUrls?.[0] ??
|
||||
""
|
||||
);
|
||||
}
|
||||
|
||||
function getPostMedia(post: ApiPost) {
|
||||
const imageUrl = pickImageUrl(post);
|
||||
const thumbnailUrl = post.thumbnailVariants?.medium ?? post.thumbnailVariants?.thumbnail ?? post.thumbnailUrl ?? "";
|
||||
const playbackUrl = post.hlsUrl || post.videoUrl || "";
|
||||
const audioUrl = post.audioUrl ?? "";
|
||||
|
||||
return {
|
||||
imageUrl: resolveMediaUrl(imageUrl),
|
||||
thumbnailUrl: resolveMediaUrl(thumbnailUrl),
|
||||
playbackUrl: resolveMediaUrl(playbackUrl),
|
||||
audioUrl: resolveMediaUrl(audioUrl),
|
||||
};
|
||||
}
|
||||
|
||||
export function getMediaHealth(post: ApiPost) {
|
||||
const checks = {
|
||||
hasImage: Boolean(pickImageUrl(post)),
|
||||
hasVideo: Boolean(post.videoUrl || post.hlsUrl),
|
||||
hasAudio: Boolean(post.audioUrl),
|
||||
hasThumbnail: Boolean(post.thumbnailUrl || post.thumbnailVariants?.thumbnail || post.thumbnailVariants?.medium),
|
||||
hasDuration: typeof post.durationSeconds === "number" && post.durationSeconds > 0,
|
||||
hasOptimizedImage:
|
||||
Boolean(post.imageItems?.some((item) => item.variants && Object.keys(item.variants).length > 0)) ||
|
||||
Boolean(post.imageVariants && Object.keys(post.imageVariants).length > 0),
|
||||
hasHls: Boolean(post.hlsUrl),
|
||||
};
|
||||
|
||||
const problems: string[] = [];
|
||||
if (post.postType === "image" && !checks.hasImage) problems.push("missing image");
|
||||
if (post.postType === "image" && !checks.hasOptimizedImage) problems.push("no image variants");
|
||||
if (post.postType === "video" && !checks.hasVideo) problems.push("missing video");
|
||||
if (post.postType === "video" && !checks.hasThumbnail) problems.push("missing thumbnail");
|
||||
if (post.postType === "video" && !checks.hasHls) problems.push("no HLS");
|
||||
if ((post.postType === "video" || post.postType === "audio") && !checks.hasDuration) {
|
||||
problems.push("missing duration");
|
||||
}
|
||||
if (post.postType === "audio" && !checks.hasAudio) problems.push("missing audio");
|
||||
|
||||
return {
|
||||
checks,
|
||||
problems,
|
||||
score: Math.max(0, 100 - problems.length * 20),
|
||||
};
|
||||
}
|
||||
|
||||
export function MediaInspector({ post, compact = false }: { post: ApiPost; compact?: boolean }) {
|
||||
const media = getPostMedia(post);
|
||||
const health = getMediaHealth(post);
|
||||
const badges = [
|
||||
post.hlsUrl ? "HLS" : null,
|
||||
post.thumbnailUrl ? "thumbnail" : null,
|
||||
post.durationSeconds ? `${Math.round(post.durationSeconds)}s` : null,
|
||||
post.processingStatus ? post.processingStatus : null,
|
||||
].filter(Boolean);
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="overflow-hidden rounded-lg border border-border/70 bg-background/30">
|
||||
{post.postType === "video" && media.playbackUrl ? (
|
||||
<video
|
||||
className="aspect-video w-full bg-black object-contain"
|
||||
controls
|
||||
preload="metadata"
|
||||
poster={media.thumbnailUrl || undefined}
|
||||
>
|
||||
<source src={media.playbackUrl} />
|
||||
</video>
|
||||
) : post.postType === "audio" && media.audioUrl ? (
|
||||
<div className="space-y-4 p-4">
|
||||
{media.thumbnailUrl ? (
|
||||
<Image
|
||||
src={media.thumbnailUrl}
|
||||
alt={post.content || "audio thumbnail"}
|
||||
width={640}
|
||||
height={360}
|
||||
unoptimized
|
||||
className="aspect-video w-full rounded-md object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex aspect-video w-full items-center justify-center rounded-md bg-secondary/50">
|
||||
<FileAudio className="h-8 w-8 text-primary" />
|
||||
</div>
|
||||
)}
|
||||
<audio className="w-full" controls preload="metadata" src={media.audioUrl} />
|
||||
</div>
|
||||
) : media.imageUrl ? (
|
||||
<Image
|
||||
src={media.imageUrl}
|
||||
alt={post.imageItems?.[0]?.altText || post.content || "post image"}
|
||||
width={720}
|
||||
height={720}
|
||||
unoptimized
|
||||
className={compact ? "aspect-video w-full object-cover" : "max-h-[520px] w-full object-contain"}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex aspect-video w-full items-center justify-center gap-2 text-sm text-muted-foreground">
|
||||
{post.postType === "video" ? <FileVideo className="h-5 w-5" /> : <FileImage className="h-5 w-5" />}
|
||||
No preview media
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge variant={health.problems.length ? "warning" : "success"}>
|
||||
media health {health.score}%
|
||||
</Badge>
|
||||
{badges.map((badge) => (
|
||||
<Badge key={badge} variant="muted">
|
||||
{badge}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{health.problems.length ? (
|
||||
<div className="flex flex-wrap gap-2 text-xs text-muted-foreground">
|
||||
{health.problems.map((problem) => (
|
||||
<span key={problem} className="rounded-md border border-border/70 px-2 py-1">
|
||||
{problem}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{!compact ? (
|
||||
<div className="grid gap-2 text-xs text-muted-foreground">
|
||||
{[media.imageUrl, media.playbackUrl, media.audioUrl, media.thumbnailUrl].filter(Boolean).map((url) => (
|
||||
<div key={url} className="flex min-w-0 items-center gap-2 rounded-md border border-border/60 px-2 py-1">
|
||||
<LinkIcon className="h-3.5 w-3.5 shrink-0" />
|
||||
<span dir="ltr" className="truncate font-mono">
|
||||
{url}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
المرجع في مشكلة جديدة
حظر مستخدم