From c957767e2e7b58a546f0efbc7e1650fbff466d99 Mon Sep 17 00:00:00 2001 From: David Montero Date: Fri, 29 May 2026 04:18:43 +0200 Subject: [PATCH] feat(editor): add Export-Screenshot button (Phase 3 D3.2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Front-end half of the schematic image export. New camera-icon button in the editor toolbar between BOM and Upload-Firmware. Handler: 1. POSTs to /api/pro/projects/{id}/screenshot.png (server renders the canvas with headless chromium, returns a PNG). 2. 402 → /pricing?from=screenshot_export 3. 401 → /login with redirect-back 4. 422 → friendly "add at least one component" toast 5. 200 → blob download with Content-Disposition filename 6. The "rendering..." toast surfaces during the 5-10 s of headless chromium time so users know to wait, not click again. i18n key editor.toolbar.exportScreenshot added in en/es/pt-br/zh-cn. Co-Authored-By: Claude Opus 4.7 --- .../src/components/editor/EditorToolbar.tsx | 59 +++++++++++++++++++ frontend/src/i18n/locales/en/common2.json | 3 +- frontend/src/i18n/locales/es/common2.json | 3 +- frontend/src/i18n/locales/pt-br/common2.json | 3 +- frontend/src/i18n/locales/zh-cn/common2.json | 3 +- 5 files changed, 67 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/editor/EditorToolbar.tsx b/frontend/src/components/editor/EditorToolbar.tsx index 64c6d072..586f9be5 100644 --- a/frontend/src/components/editor/EditorToolbar.tsx +++ b/frontend/src/components/editor/EditorToolbar.tsx @@ -881,6 +881,54 @@ export const EditorToolbar = ({ } }; + // Phase 3 D3.2 — Schematic screenshot. Pro-tier-gated by the backend. + // Same UX pattern as BOM export: everyone can click; 402 redirects to + // /pricing. The server-side headless chromium renders the canvas and + // returns a PNG, which we trigger a download for. + const handleExportScreenshot = async () => { + const projectId = currentProject?.id; + if (!projectId) { + setMessage({ type: 'error', text: 'Save the project before exporting an image.' }); + return; + } + setMessage({ type: 'info', text: 'Rendering screenshot — may take 5-10 seconds…' }); + try { + const resp = await fetch(`/api/pro/projects/${projectId}/screenshot.png`, { + credentials: 'include', + }); + if (resp.status === 402) { + window.location.href = '/pricing?from=screenshot_export'; + return; + } + if (resp.status === 401) { + window.location.href = `/login?redirect=${encodeURIComponent(window.location.pathname)}`; + return; + } + if (resp.status === 422) { + setMessage({ type: 'error', text: 'Add at least one component to export an image.' }); + return; + } + if (!resp.ok) { + setMessage({ type: 'error', text: 'Screenshot export failed.' }); + return; + } + const blob = await resp.blob(); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + const cd = resp.headers.get('Content-Disposition') || ''; + const m = /filename="?([^"]+)"?/.exec(cd); + a.download = m ? m[1] : `velxio-${projectId}.png`; + document.body.appendChild(a); + a.click(); + a.remove(); + URL.revokeObjectURL(url); + setMessage({ type: 'success', text: 'Screenshot downloaded.' }); + } catch { + setMessage({ type: 'error', text: 'Screenshot export failed.' }); + } + }; + // Phase 3 D3.1 — BOM export. Pro-tier-gated by the backend (402 if not pro). // We let everyone click; the 402 response feeds the upgrade prompt below // so free/maker users hit the funnel naturally instead of an obviously- @@ -1272,6 +1320,17 @@ export const EditorToolbar = ({ +