|
|
@@ -1,8 +1,8 @@
|
|
|
import React, { useState, useEffect } from "react";
|
|
|
import { API_BASE } from "../config";
|
|
|
|
|
|
-function MediaGalleryModal({ isOpen, onClose, onSelect, slug }) {
|
|
|
- const [images, setImages] = useState([]);
|
|
|
+function MediaGalleryModal({ isOpen, onClose, onSelect, slug, typeFilter = "image" }) {
|
|
|
+ const [media, setMedia] = useState([]);
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
const [uploading, setUploading] = useState(false);
|
|
|
const [error, setError] = useState(null);
|
|
|
@@ -11,22 +11,22 @@ function MediaGalleryModal({ isOpen, onClose, onSelect, slug }) {
|
|
|
if (isOpen) {
|
|
|
fetchMedia();
|
|
|
}
|
|
|
- }, [isOpen, slug]);
|
|
|
+ }, [isOpen, slug, typeFilter]);
|
|
|
|
|
|
const fetchMedia = async () => {
|
|
|
try {
|
|
|
setLoading(true);
|
|
|
const url = slug
|
|
|
- ? `${API_BASE}/media?slug=${encodeURIComponent(slug)}`
|
|
|
- : `${API_BASE}/media`;
|
|
|
+ ? `${API_BASE}/media?slug=${encodeURIComponent(slug)}&type=${typeFilter}`
|
|
|
+ : `${API_BASE}/media?type=${typeFilter}`;
|
|
|
|
|
|
const response = await fetch(url, { credentials: "include" });
|
|
|
if (!response.ok) throw new Error("Failed to load media");
|
|
|
const data = await response.json();
|
|
|
- setImages(data);
|
|
|
+ setMedia(data);
|
|
|
} catch (err) {
|
|
|
console.error("Fetch media error:", err);
|
|
|
- setError("Could not load images.");
|
|
|
+ setError(`Could not load ${typeFilter === 'image' ? 'images' : 'files'}.`);
|
|
|
} finally {
|
|
|
setLoading(false);
|
|
|
}
|
|
|
@@ -63,7 +63,7 @@ function MediaGalleryModal({ isOpen, onClose, onSelect, slug }) {
|
|
|
|
|
|
const handleDelete = async (path, e) => {
|
|
|
e.stopPropagation();
|
|
|
- if (!window.confirm("Delete this image?")) return;
|
|
|
+ if (!window.confirm("Delete this file?")) return;
|
|
|
|
|
|
try {
|
|
|
const response = await fetch(`${API_BASE}/media`, {
|
|
|
@@ -82,19 +82,23 @@ function MediaGalleryModal({ isOpen, onClose, onSelect, slug }) {
|
|
|
|
|
|
if (!isOpen) return null;
|
|
|
|
|
|
+ const modalTitle = typeFilter === "csv" ? "CSV Media Gallery" : "Image Media Gallery";
|
|
|
+ const uploadLabel = typeFilter === "csv" ? "Upload CSV" : "Upload Image";
|
|
|
+ const acceptAttr = typeFilter === "csv" ? ".csv" : "image/*";
|
|
|
+
|
|
|
return (
|
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm">
|
|
|
<div className="theme-surface rounded-xl shadow-2xl w-full max-w-4xl max-h-[80vh] flex flex-col overflow-hidden theme-border border">
|
|
|
{/* Header */}
|
|
|
<div className="px-6 py-4 border-b theme-border flex justify-between items-center bg-gray-50/50">
|
|
|
- <h3 className="text-lg font-bold theme-text">Media Gallery</h3>
|
|
|
+ <h3 className="text-lg font-bold theme-text">{modalTitle}</h3>
|
|
|
<div className="flex items-center space-x-4">
|
|
|
<label className="cursor-pointer btn-theme-primary text-white px-4 py-2 rounded-lg transition-colors text-sm font-medium">
|
|
|
- {uploading ? "Uploading..." : "Upload Image"}
|
|
|
+ {uploading ? "Uploading..." : uploadLabel}
|
|
|
<input
|
|
|
type="file"
|
|
|
className="hidden"
|
|
|
- accept="image/*"
|
|
|
+ accept={acceptAttr}
|
|
|
onChange={handleUpload}
|
|
|
disabled={uploading}
|
|
|
/>
|
|
|
@@ -120,24 +124,31 @@ function MediaGalleryModal({ isOpen, onClose, onSelect, slug }) {
|
|
|
<div className="flex justify-center py-12">
|
|
|
<div className="animate-spin h-8 w-8 border-4 border-blue-500 rounded-full border-t-transparent"></div>
|
|
|
</div>
|
|
|
- ) : images.length === 0 ? (
|
|
|
+ ) : media.length === 0 ? (
|
|
|
<div className="text-center py-12 theme-text-secondary">
|
|
|
- No images found in this folder.
|
|
|
+ No {typeFilter === 'csv' ? 'CSV files' : 'images'} found in this folder.
|
|
|
</div>
|
|
|
) : (
|
|
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4">
|
|
|
- {images.map((img) => (
|
|
|
+ {media.map((img) => (
|
|
|
<div
|
|
|
key={img.name}
|
|
|
className="group relative theme-surface rounded-lg shadow-sm border theme-border overflow-hidden hover:shadow-md transition-shadow cursor-pointer aspect-square"
|
|
|
onClick={() => onSelect(`${API_BASE}${img.url}`, img.name)}
|
|
|
>
|
|
|
- <img
|
|
|
- src={`${API_BASE}${img.url}`}
|
|
|
- alt={img.name}
|
|
|
- className="w-full h-full object-cover"
|
|
|
- loading="lazy"
|
|
|
- />
|
|
|
+ {img.type === 'csv' || img.name.endsWith('.csv') ? (
|
|
|
+ <div className="w-full h-full flex flex-col items-center justify-center bg-green-50 dark:bg-green-900/10 text-green-600 dark:text-green-400">
|
|
|
+ <svg className="w-12 h-12" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></svg>
|
|
|
+ <span className="text-[10px] font-bold mt-2 font-mono">.CSV</span>
|
|
|
+ </div>
|
|
|
+ ) : (
|
|
|
+ <img
|
|
|
+ src={`${API_BASE}${img.url}`}
|
|
|
+ alt={img.name}
|
|
|
+ className="w-full h-full object-cover"
|
|
|
+ loading="lazy"
|
|
|
+ />
|
|
|
+ )}
|
|
|
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/10 transition-colors" />
|
|
|
|
|
|
{/* Delete Button */}
|
|
|
@@ -160,7 +171,7 @@ function MediaGalleryModal({ isOpen, onClose, onSelect, slug }) {
|
|
|
</div>
|
|
|
|
|
|
<div className="p-4 bg-gray-50/50 border-t theme-border text-sm theme-text-secondary text-center">
|
|
|
- Click an image to insert it into the editor.
|
|
|
+ Click to insert into the editor.
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|