feat(video-assets): add full video asset system (uploads, downloads, @mentions, and cleanup/billing integration)

This commit is contained in:
Yusuf İpek
2026-02-25 18:34:03 +03:00
parent 6eea327083
commit 9ce033d306
32 changed files with 3524 additions and 231 deletions
+76 -53
View File
@@ -9,71 +9,94 @@ import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog';
interface ImagePreviewDialogProps {
previewImage: string | null;
onClose: () => void;
title?: string | null;
downloadFileName?: string | null;
canDownload?: boolean;
}
export const ImagePreviewDialog = memo(function ImagePreviewDialog({
previewImage,
onClose,
title,
downloadFileName,
canDownload = true,
}: ImagePreviewDialogProps) {
const resolvedDownloadName = downloadFileName || (previewImage ? previewImage.split('/').pop() || 'attachment.png' : 'attachment.png');
return (
<Dialog open={!!previewImage} onOpenChange={(open) => !open && onClose()}>
<DialogContent
showCloseButton={false}
className="max-w-none sm:max-w-none w-screen h-screen max-h-screen p-0 overflow-hidden bg-black/90 border-none shadow-none flex flex-col items-center justify-center rounded-none"
className="max-w-none sm:max-w-none w-screen h-screen max-h-screen p-0 overflow-hidden bg-black/90 border-none shadow-none flex items-center justify-center rounded-none"
onClick={onClose}
onKeyDown={(event) => {
event.stopPropagation();
if (event.key === 'Escape') {
event.preventDefault();
onClose();
}
}}
>
<DialogTitle className="sr-only">Image Preview</DialogTitle>
<div className="absolute top-4 right-4 flex gap-3 z-50">
<Button
variant="outline"
size="icon"
className="rounded-full bg-black/40 hover:bg-black/80 border-white/20 text-white h-10 w-10 backdrop-blur-md transition-all shrink-0"
onClick={async (e) => {
e.stopPropagation();
try {
if (!previewImage) return;
const response = await fetch(previewImage);
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = previewImage.split('/').pop() || 'attachment.png';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error('Failed to download image:', error);
toast.error('Failed to download image');
}
}}
<DialogTitle className="sr-only">{title || 'Image Preview'}</DialogTitle>
<div className="w-[min(96vw,1500px)] h-[min(94vh,1000px)] border border-border/60 bg-black/80 shadow-2xl flex flex-col overflow-hidden" onClick={(e) => e.stopPropagation()}>
<div className="shrink-0 flex items-center gap-2 border-b border-border/60 bg-background/85 px-2 py-1.5 backdrop-blur-sm">
<p className="flex-1 min-w-0 text-sm text-foreground truncate" title={title || undefined}>
{title || 'Image Preview'}
</p>
{canDownload ? (
<Button
variant="outline"
size="icon"
className="h-8 w-8 shrink-0"
onClick={async (e) => {
e.stopPropagation();
try {
if (!previewImage) return;
const response = await fetch(previewImage);
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = resolvedDownloadName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error('Failed to download image:', error);
toast.error('Failed to download image');
}
}}
>
<Download className="h-4 w-4" />
</Button>
) : null}
<Button
variant="outline"
size="icon"
className="h-8 w-8 shrink-0"
onClick={(e) => {
e.stopPropagation();
onClose();
}}
>
<X className="h-4 w-4" />
</Button>
</div>
<div
className="relative flex-1 min-h-0 w-full flex items-center justify-center p-2 sm:p-4 cursor-zoom-out"
onClick={onClose}
>
<Download className="h-5 w-5" />
</Button>
<Button
variant="outline"
size="icon"
className="rounded-full bg-black/40 hover:bg-black/80 border-white/20 text-white h-10 w-10 backdrop-blur-md transition-all shrink-0"
onClick={(e) => {
e.stopPropagation();
onClose();
}}
>
<X className="h-5 w-5" />
</Button>
</div>
<div
className="relative w-full h-full flex items-center justify-center p-4 cursor-zoom-out"
onClick={onClose}
>
{previewImage && (
// eslint-disable-next-line @next/next/no-img-element
<img
src={previewImage}
alt="Preview"
className="max-w-[95vw] max-h-[90vh] object-contain rounded-md select-none cursor-default"
onClick={(e) => e.stopPropagation()}
/>
)}
{previewImage && (
// eslint-disable-next-line @next/next/no-img-element
<img
src={previewImage}
alt={title || 'Preview'}
className="max-w-full max-h-full object-contain rounded-md select-none cursor-default"
onClick={(e) => e.stopPropagation()}
/>
)}
</div>
</div>
</DialogContent>
</Dialog>