UNPKG

@megaads/wm

Version:

To install the library, use npm:

342 lines (316 loc) 10.3 kB
import React, { useEffect, useMemo, useState } from 'react'; import WM from '@megaads/wm'; import Preview from './Preview.jsx'; import OptionsPanel from './OptionsPanel.jsx'; import { sampleMinimal, sampleMultiArtwork } from './samples.js'; const TABS = [ { id: 'preview', label: 'Preview' }, { id: 'config', label: 'Config payload' }, { id: 'state', label: 'State' }, { id: 'snapshot', label: 'Snapshot' }, ]; export default function App() { const [campaignText, setCampaignText] = useState(''); const [campaign, setCampaign] = useState(null); const [service, setService] = useState(null); const [snapshot, setSnapshot] = useState(null); const [tab, setTab] = useState('preview'); const [error, setError] = useState(null); const [fetchInput, setFetchInput] = useState('2638329004'); const [fetching, setFetching] = useState(false); useEffect(() => { if (!campaign) { setService(null); setSnapshot(null); return; } try { const s = WM.initCustomizationTeeinblue(campaign); setService(s); setSnapshot(s.getSnapshot()); setError(null); } catch (e) { console.error(e); setError(String(e?.message || e)); setService(null); setSnapshot(null); } }, [campaign]); const loadSample = (data) => { setCampaign(data); setCampaignText(JSON.stringify(data, null, 2)); }; const loadFromPaste = () => { try { const data = JSON.parse(campaignText); setCampaign(data); setError(null); } catch (e) { setError('Invalid JSON: ' + e.message); } }; const loadFile = async (file) => { if (!file) return; try { const text = await file.text(); const data = JSON.parse(text); setCampaignText(text); setCampaign(data); setError(null); } catch (e) { setError('Invalid file: ' + e.message); } }; const loadFromPrinterval = async () => { const productId = extractProductId(fetchInput); if (!productId) { setError('Không tìm thấy product_id. Nhập số ID hoặc URL chứa ?product_id=XXX'); return; } setFetching(true); setError(null); try { const res = await fetch('/api/printerval/get-campaign-data?product_id=' + productId); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); setCampaignText(JSON.stringify(data, null, 2)); setCampaign(data); } catch (e) { setError('Fetch failed: ' + e.message); } finally { setFetching(false); } }; const validation = snapshot?.validation; return ( <div className="app"> <header className="header"> <h1>WM Playground · TeeInBlue Customization</h1> <div className="meta"> {snapshot && ( <> {validation?.isValid ? ( <span style={{ color: '#22c55e' }}>✓ Valid</span> ) : ( <span style={{ color: '#ef4444' }}> ✗ {validation.errors.length} error(s) </span> )} <span style={{ marginLeft: 12 }}> {snapshot.layerOptions.length} option(s) · {snapshot.designLayers.length} layer(s) </span> </> )} </div> </header> <aside className="panel loader"> <div className="section"> <h3>Load campaign</h3> <button className="btn" onClick={() => loadSample(sampleMinimal)}> Sample (1 artwork) </button> <button className="btn" onClick={() => loadSample(sampleMultiArtwork)}> Sample (2 artworks) </button> <button className="btn secondary" onClick={() => { setCampaign(null); setCampaignText(''); }} > Clear </button> </div> <div className="section"> <h3>Fetch từ Printerval</h3> <input type="text" value={fetchInput} onChange={(e) => setFetchInput(e.target.value)} placeholder="product_id hoặc URL" disabled={fetching} /> <div style={{ marginTop: 6 }}> <button className="btn" onClick={loadFromPrinterval} disabled={fetching || !fetchInput.trim()} > {fetching ? 'Fetching…' : 'Fetch campaign'} </button> </div> <div style={{ fontSize: 11, color: '#64748b', marginTop: 4 }}> Gọi qua proxy: <code>/api/printerval/get-campaign-data?product_id=…</code> </div> </div> <div className="section"> <h3>Upload JSON</h3> <input type="file" accept="application/json" onChange={(e) => loadFile(e.target.files?.[0])} style={{ fontSize: 12 }} /> </div> <div className="section"> <h3>Paste JSON</h3> <textarea value={campaignText} onChange={(e) => setCampaignText(e.target.value)} placeholder='{"result": {"campaign_products": ...}}' spellCheck={false} /> <div style={{ marginTop: 6 }}> <button className="btn" onClick={loadFromPaste} disabled={!campaignText.trim()}> Load JSON </button> </div> </div> {error && <div className="error-box">{error}</div>} {snapshot && ( <div className="section"> <h3>State actions</h3> <button className="btn secondary" onClick={() => { const state = service.getState(); localStorage.setItem('wm_playground_state', JSON.stringify(state)); alert('State saved to localStorage'); }} > Save state </button> <button className="btn secondary" onClick={() => { const stored = localStorage.getItem('wm_playground_state'); if (!stored) return alert('No saved state'); const state = JSON.parse(stored); const s = WM.initCustomizationTeeinblue(campaign, { templateId: state.templateId, state, }); setService(s); setSnapshot(s.getSnapshot()); }} > Restore state </button> </div> )} {snapshot && validation && !validation.isValid && ( <div className="section"> <h3>Validation errors</h3> {validation.errors.map((err, i) => ( <div key={i} className="error-box"> [{err.type}] {err.label || err.artworkId}: {err.message} </div> ))} </div> )} {snapshot && validation?.isValid && ( <div className="section"> <div className="success-box">All required fields are filled.</div> </div> )} </aside> <main className="preview-area"> <div className="preview-tabs"> {TABS.map((t) => ( <button key={t.id} className={'tab-btn' + (tab === t.id ? ' active' : '')} onClick={() => setTab(t.id)} > {t.label} </button> ))} </div> {tab === 'preview' && ( snapshot ? ( <Preview snapshot={snapshot} /> ) : ( <div className="preview-canvas"> <div className="preview-empty">Load a campaign to begin</div> </div> ) )} {tab === 'config' && ( <div style={{ padding: 16, overflow: 'auto', flex: 1 }}> <pre className="json">{JSON.stringify(snapshot?.config, null, 2)}</pre> </div> )} {tab === 'state' && ( <div style={{ padding: 16, overflow: 'auto', flex: 1 }}> <pre className="json"> {JSON.stringify(service?.getState(), null, 2)} </pre> </div> )} {tab === 'snapshot' && ( <div style={{ padding: 16, overflow: 'auto', flex: 1 }}> <pre className="json">{snapshotSummary(snapshot)}</pre> </div> )} </main> <aside className="panel options"> <OptionsPanel snapshot={snapshot} service={service} onSnapshot={setSnapshot} /> </aside> </div> ); } function extractProductId(input) { if (!input) return null; const trimmed = String(input).trim(); // Pure numeric ID if (/^\d+$/.test(trimmed)) return trimmed; // URL with ?product_id= const match = trimmed.match(/[?&]product_id=(\d+)/); if (match) return match[1]; return null; } function snapshotSummary(snapshot) { if (!snapshot) return ''; const slim = { artwork: snapshot.artwork ? { id: snapshot.artwork.id, name: snapshot.artwork.name } : null, template: snapshot.template ? { id: snapshot.template.id, name: snapshot.template.name } : null, campaignMockup: snapshot.campaignMockup ? { id: snapshot.campaignMockup.id, width: snapshot.campaignMockup.width, height: snapshot.campaignMockup.height } : null, printAreas: snapshot.printAreas?.map((pa) => ({ id: pa.id, printarea_id: pa.printarea_id, width: pa.width, height: pa.height, artwork: pa.artwork, })), layerOptions: snapshot.layerOptions.map((l) => ({ id: l.id, form_label: l.form_label, input_type: l.input_type, value: l.value, show_value: l.show_value, required: l.required, form_visibility_value: l.form_visibility_value, items: (l.option_items || []).length, })), designLayers: snapshot.designLayers.map((l) => ({ id: l.id, type: l.type, top: l.top, left: l.left, width: l.width, height: l.height, text: l.text, src: l.src, })), fonts: snapshot.fonts, validation: snapshot.validation, }; return JSON.stringify(slim, null, 2); }