@megaads/wm
Version:
To install the library, use npm:
342 lines (316 loc) • 10.3 kB
JSX
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);
}