React Integration: Complete Example
A React component that loads the Wedding Band Builder with full API access.
Component
jsx
// WeddingBandBuilder.jsx
import { useEffect, useRef, useState } from 'react';
// Load the mini-viewer script once
const SCRIPT_URL = 'https://releases.ijewel3d.com/libs/mini-viewer/latest/bundle.iife.js';
let scriptLoaded = false;
function loadScript() {
if (scriptLoaded) return Promise.resolve();
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = SCRIPT_URL;
script.onload = () => { scriptLoaded = true; resolve(); };
script.onerror = reject;
document.head.appendChild(script);
});
}
export default function WeddingBandBuilder({
manifestUrl = 'wedding-band-project.json',
basePath = 'https://your-cdn.com/wbb-assets/',
showUI = true,
theme = null,
onReady = null,
onPriceChange = null,
}) {
const containerRef = useRef(null);
const apiRef = useRef(null);
const [price, setPrice] = useState(null);
useEffect(() => {
let disposed = false;
async function init() {
await loadScript();
if (disposed || !containerRef.current) return;
const project = {
name: 'Wedding Band Builder',
version: 'v5',
basePath,
plugins: {
WeddingBandBuilder: { manifestUrl, showUI },
},
};
new window.ijewelViewer.Viewer(containerRef.current, project, {
showCard: false,
showSwitchNode: false,
showUiButtons: showUI,
showConfigurator: false,
showZoomButtons: showUI,
enableZoom: true,
});
}
function handleReady(e) {
const viewer = e.detail.viewer;
const api = viewer.getPluginByType('WeddingBandBuilder')?.controller;
if (!api) return;
apiRef.current = api;
if (theme) api.setTheme(theme);
api.events.on('price:updated', (data) => {
if (data.pricing) {
setPrice(data.pricing.totalUsd);
onPriceChange?.(data.pricing);
}
});
onReady?.(api);
}
window.addEventListener('ijewel-viewer-ready', handleReady);
init();
return () => {
disposed = true;
window.removeEventListener('ijewel-viewer-ready', handleReady);
};
}, [manifestUrl, basePath, showUI]);
return (
<div style={{ width: '100%', height: '100%' }}>
<div ref={containerRef} style={{ width: '100%', height: '100%' }} />
</div>
);
}Usage
jsx
// App.jsx
import WeddingBandBuilder from './WeddingBandBuilder';
function App() {
const handleReady = (api) => {
console.log('WBB ready, available profiles:', api.getAvailableProfiles());
};
const handlePrice = (pricing) => {
console.log(`Total: $${pricing.totalUsd.toFixed(2)}`);
};
return (
<div style={{ width: '100vw', height: '100vh' }}>
<WeddingBandBuilder
basePath="https://your-cdn.com/wbb-assets/"
showUI={true}
theme="luxury-gold"
onReady={handleReady}
onPriceChange={handlePrice}
/>
</div>
);
}Headless with Custom Controls
jsx
import { useState, useCallback } from 'react';
import WeddingBandBuilder from './WeddingBandBuilder';
function CustomConfigurator() {
const [api, setApi] = useState(null);
const [price, setPrice] = useState(null);
const [activeProfile, setActiveProfile] = useState(0);
const handleReady = useCallback((wbbApi) => {
setApi(wbbApi);
}, []);
const profiles = api?.getAvailableProfiles() || [];
const metals = api?.getAvailableMetals() || [];
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
<div style={{ flex: 1 }}>
<WeddingBandBuilder
showUI={false}
onReady={handleReady}
onPriceChange={(p) => setPrice(p.totalUsd)}
/>
</div>
<div style={{ padding: 16, background: '#fff', borderTop: '1px solid #eee' }}>
<div style={{ display: 'flex', gap: 8, marginBottom: 12 }}>
{profiles.map((p) => (
<button
key={p.index}
onClick={async () => {
await api.setProfile(p.index);
setActiveProfile(p.index);
}}
style={{
padding: '8px 16px',
border: '1px solid #ddd',
borderRadius: 6,
background: activeProfile === p.index ? '#333' : '#fff',
color: activeProfile === p.index ? '#fff' : '#333',
cursor: 'pointer',
}}
>
{p.name}
</button>
))}
</div>
<div style={{ display: 'flex', gap: 8 }}>
{metals.map((m) => (
<button
key={m.id}
onClick={() => api.setMaterial(1, m.id, 'Polished')}
style={{
padding: '8px 16px',
border: '1px solid #ddd',
borderRadius: 6,
background: '#fff',
cursor: 'pointer',
}}
>
{m.name}
</button>
))}
</div>
{price && (
<div style={{ marginTop: 16, fontSize: 24, fontWeight: 600 }}>
${price.toFixed(2)}
</div>
)}
</div>
</div>
);
}