From 14751c42e4b7bce767787c3685578287c8f3844e Mon Sep 17 00:00:00 2001 From: fahed Date: Wed, 11 Mar 2026 11:34:11 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20inline=20video=20upload=20in=20artefacts?= =?UTF-8?q?=20=E2=80=94=20drop=20modal,=20add=20drag-and-drop=20+=20progre?= =?UTF-8?q?ss=20bar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace the two-step video modal with inline drag-and-drop zone + click-to-browse - Add Google Drive URL input directly in the versions tab - Add upload progress bar with percentage via XHR - Support onUploadProgress in api.upload() Co-Authored-By: Claude Opus 4.6 --- client/src/components/ArtefactDetailPanel.jsx | 197 +++++++----------- client/src/i18n/ar.json | 1 + client/src/i18n/en.json | 1 + client/src/utils/api.js | 31 ++- 4 files changed, 104 insertions(+), 126 deletions(-) diff --git a/client/src/components/ArtefactDetailPanel.jsx b/client/src/components/ArtefactDetailPanel.jsx index 648797e..5c65623 100644 --- a/client/src/components/ArtefactDetailPanel.jsx +++ b/client/src/components/ArtefactDetailPanel.jsx @@ -73,10 +73,10 @@ export default function ArtefactDetailPanel({ artefact, onClose, onUpdate, onDel // File upload (for design/video) const [uploading, setUploading] = useState(false) - // Video modal (for video type with Drive link) - const [showVideoModal, setShowVideoModal] = useState(false) - const [videoMode, setVideoMode] = useState('upload') // 'upload' or 'drive' + // Video inline (Drive link input) const [driveUrl, setDriveUrl] = useState('') + const [dragOver, setDragOver] = useState(false) + const [uploadProgress, setUploadProgress] = useState(0) // Comments const [comments, setComments] = useState([]) @@ -190,15 +190,20 @@ export default function ArtefactDetailPanel({ artefact, onClose, onUpdate, onDel } } - const handleFileUpload = async (e) => { - const file = e.target.files?.[0] + const handleFileUpload = async (fileOrEvent) => { + const file = fileOrEvent instanceof File ? fileOrEvent : fileOrEvent.target?.files?.[0] if (!file) return setUploading(true) + setUploadProgress(0) try { const formData = new FormData() formData.append('file', file) - await api.upload(`/artefacts/${artefact.Id}/versions/${selectedVersion.Id}/attachments`, formData) + await api.upload(`/artefacts/${artefact.Id}/versions/${selectedVersion.Id}/attachments`, formData, { + onUploadProgress: (e) => { + if (e.total) setUploadProgress(Math.round((e.loaded / e.total) * 100)) + } + }) toast.success(t('artefacts.fileUploaded')) loadVersionData(selectedVersion.Id) } catch (err) { @@ -206,6 +211,16 @@ export default function ArtefactDetailPanel({ artefact, onClose, onUpdate, onDel toast.error(t('artefacts.uploadFailed')) } finally { setUploading(false) + setUploadProgress(0) + } + } + + const handleVideoDrop = (e) => { + e.preventDefault() + setDragOver(false) + const file = e.dataTransfer.files?.[0] + if (file && file.type.startsWith('video/')) { + handleFileUpload(file) } } @@ -221,7 +236,6 @@ export default function ArtefactDetailPanel({ artefact, onClose, onUpdate, onDel drive_url: driveUrl, }) toast.success(t('artefacts.videoLinkAdded')) - setShowVideoModal(false) setDriveUrl('') loadVersionData(selectedVersion.Id) } catch (err) { @@ -608,68 +622,87 @@ export default function ArtefactDetailPanel({ artefact, onClose, onUpdate, onDel )} - {/* VIDEO TYPE: Files and Drive links */} + {/* VIDEO TYPE: Files and Drive links — all inline */} {artefact.type === 'video' && (
-
-

{t('artefacts.videosLabel')}

- -
+

{t('artefacts.videosLabel')}

- {versionData.attachments && versionData.attachments.length > 0 ? ( -
+ {/* Existing attachments */} + {versionData.attachments && versionData.attachments.length > 0 && ( +
{versionData.attachments.map(att => (
{att.drive_url ? (
{t('artefacts.googleDriveVideo')} -
-