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 = ({ +