fix: code review — security, dead code, performance, consistency
Critical fixes: - XSS: escapeHtml() on all user-supplied text in email notifications - Budget PATCH: added mutex lock + availability validation (prevents corruption) - batchResolveNames: fixed wrong signature for budget request earmark names Dead code cleanup: - Deleted 8 unused PostComposition* files (replaced by PostDetail full page) Performance: - budget-helpers: single-fetch with computeFromEntries(), optional prefetch param - post-composition: parallelized text + thumbnail fetches with Promise.all Consistency: - PostDetail.jsx: native <select> → PortalSelect (matches all panels) - Finance.jsx: 11 hardcoded English table headers → t() with i18n keys - PostCalendar.jsx: day names, Month/Week labels → t() with i18n keys - App.jsx Suspense: "Loading..." → brand spinner (can't use i18n in fallback) - UploadZone: proper useRef pattern, no vanilla JS document.createElement - All file inputs: className="hidden" → absolute w-0 h-0 opacity-0 - ArtefactDetailPanel: removed campaign/project selects (inherited from post) - TranslationDetailPanel: removed brand/linked post selects (inherited from post) - ApproverMultiSelect: portal-based dropdown (fixes clipping in modals) - Thumbnail fix: post-composition constructs URL from filename (was undefined) - Upload fix: UploadZone with drag-and-drop for design + video artefacts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -35,8 +35,10 @@ async function getPostComposition(postId) {
|
||||
return texts.map(tt => ({ language: tt.language_code || tt.language, status: tt.status || 'draft' }));
|
||||
} catch { return []; }
|
||||
};
|
||||
const captionTexts = caption ? await getTexts(caption.Id) : [];
|
||||
const bodyTexts = bodyCopy ? await getTexts(bodyCopy.Id) : [];
|
||||
const [captionTexts, bodyTexts] = await Promise.all([
|
||||
caption ? getTexts(caption.Id) : [],
|
||||
bodyCopy ? getTexts(bodyCopy.Id) : [],
|
||||
]);
|
||||
|
||||
// Get first attachment for design/video thumbnail
|
||||
const getFirstAttachment = async (artefactId) => {
|
||||
@@ -44,11 +46,15 @@ async function getPostComposition(postId) {
|
||||
const versions = await nocodb.list('ArtefactVersions', { where: `(artefact_id,eq,${artefactId})`, sort: '-version_number', limit: 1 });
|
||||
if (versions.length === 0) return null;
|
||||
const attachments = await nocodb.list('ArtefactAttachments', { where: `(version_id,eq,${versions[0].Id})`, limit: 1 });
|
||||
return attachments.length > 0 ? (attachments[0].url || attachments[0].file_url || null) : null;
|
||||
if (attachments.length === 0) return null;
|
||||
const att = attachments[0];
|
||||
return att.drive_url || (att.filename ? `/api/uploads/${att.filename}` : null);
|
||||
} catch { return null; }
|
||||
};
|
||||
const designThumb = design ? (design.thumbnail_url || await getFirstAttachment(design.Id)) : null;
|
||||
const videoThumb = video ? (video.thumbnail_url || await getFirstAttachment(video.Id)) : null;
|
||||
const [designThumb, videoThumb] = await Promise.all([
|
||||
design ? (design.thumbnail_url || getFirstAttachment(design.Id)) : null,
|
||||
video ? (video.thumbnail_url || getFirstAttachment(video.Id)) : null,
|
||||
]);
|
||||
|
||||
return {
|
||||
caption: caption ? { id: caption.Id, title: caption.title, status: caption.status, language: caption.source_language, content_preview: (caption.source_content || '').slice(0, 120), languages: captionTexts } : null,
|
||||
@@ -66,9 +72,7 @@ function computeStage(composition) {
|
||||
const { caption, body_copy, design, video, pieces_ready } = composition;
|
||||
if (pieces_ready) return 'post';
|
||||
if (design || video) return 'design';
|
||||
// Check if we have any copy at all
|
||||
const hasCopy = caption || body_copy;
|
||||
if (!hasCopy) return 'copy';
|
||||
if (caption || body_copy) return 'translate';
|
||||
return 'copy';
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user