const { useState, useEffect, useRef } = React; /* Theme comes from the server (GET /campaign/active) and is applied on boot; useKioskTheme reflects whatever boot stored on window.GPS_KIOSK_THEME. */ function useKioskTheme() { const read = () => window.GPS_KIOSK_THEME || (window.GPSTheme ? window.GPSTheme.loadTheme() : { casinoName: "Riverwind", tagline: "Casino · Design Studio" }); const [t, setT] = React.useState(read); React.useEffect(() => { const h = () => setT(read()); window.addEventListener("kiosk-theme", h); return () => window.removeEventListener("kiosk-theme", h); }, []); return t; } function Landing({ onStart, onStaff, busy }) { const theme = useKioskTheme(); return (
LOGO
{theme.casinoName}
{theme.tagline}
Players Club
Design Studio

Design Your Custom Nikes

Create your one-of-a-kind pair. Start by checking in.

Presented by
GPS Promotional Gifting Solutions
); } /* Check-in form — logged the moment the guest starts. Styled to the kiosk. */ function Intake({ onSubmit, onBack }) { const theme = useKioskTheme(); const [f, setF] = useState({ guestName: "", playerId: "", shoeSize: "", shoeGender: "" }); const [err, setErr] = useState(""); const set = (k) => (e) => setF((s) => ({ ...s, [k]: e.target.value })); const submit = (e) => { e.preventDefault(); if (!f.guestName.trim() || !f.playerId.trim() || !f.shoeSize.trim() || !f.shoeGender) { setErr("Please fill in every field."); return; } setErr(""); onSubmit(f); }; return (
LOGO
{theme.casinoName}
{theme.tagline}
Check In

Tell us about your fit

Fit
{["mens", "womens"].map((g) => ( ))}
{err &&
{err}
}
); } function ShoeImg({ src, label, children }) { const [err, setErr] = useState(false); return (
{src && !err ? {label} setErr(true)} /> : {label} shot} {children}
); } function Gallery({ shoes, onBack, onPick, busy }) { const theme = useKioskTheme(); return (

Pick Your Shoe

Let's design
{theme.casinoName} Players Club

Choose your silhouette

Tap a shoe to open the Nike By You designer and make it yours.

{shoes.map((s, i) => ( ))}
); } /* Kiosk-shell handoff: a fixed status bar on top; the real Nike By You customizer fills the rest of the screen via the Electron BrowserView. The shell watches the clipboard + navigation for the share link and pushes it via gpsKiosk.onShareLink — we POST it to the backend against this guest's entry. */ function KioskHandoff({ shoe, guestName, entryId, nikeUrl, onCapture, onStartOver }) { const BAR = 92; // must match the y-offset passed to openNike const [saving, setSaving] = useState(false); const [msg, setMsg] = useState("Design your shoe below. When you're happy, tap Share → Copy Link on Nike."); const done = useRef(false); useEffect(() => { const k = window.gpsKiosk; if (k) k.openNike(nikeUrl, BAR, entryId); let unsub = null; if (k && k.onShareLink) { unsub = k.onShareLink(async (data) => { if (!data || data.entryId !== entryId || done.current) return; done.current = true; setSaving(true); setMsg("Saving your design…"); try { // On success the parent closes the Nike view and shows the Thank-You page. await onCapture(data.url); } catch (e) { done.current = false; setSaving(false); setMsg(e.message || "Couldn't save — try Share → Copy Link again."); } }); } return () => { if (unsub) unsub(); if (k) k.closeNike(); }; }, []); return (
GPS Design Studio {guestName} · {shoe ? shoe.name : "Nike By You"}
{saving ? "Saving…" : "○ Waiting for your design"}
Tap Share → Copy Link on Nike
{msg}
); } /* Browser fallback (no kiosk shell): open the customizer in a new tab and paste the share link. Also the way to exercise the whole flow without Electron. */ function DesignBrowser({ shoe, guestName, onCapture, onStartOver }) { const [pasted, setPasted] = useState(""); const [saving, setSaving] = useState(false); const [err, setErr] = useState(""); const builderUrl = (shoe && shoe.customUrl) || "https://www.nike.com/nike-by-you"; const save = async () => { setSaving(true); setErr(""); try { await onCapture(pasted.trim()); } catch (e) { setErr("That didn't look like a Nike share link. Use Share → Copy Link on Nike."); } finally { setSaving(false); } }; return (

Design your {shoe ? shoe.name : "shoe"}

{guestName}
  1. Open the Nike By You customizer and design your shoe.
  2. Tap Share → Copy Link on Nike.
  3. Paste the link below to save it to {guestName}.
Open Nike By You ↗ setPasted(e.target.value)} placeholder="https://www.nike.com/u/…?mid=…" /> {err &&
{err}
}
); } /* After picking a silhouette: "Customize Your Own" (emphasized) vs. 2–3 prebuilt designs. Picking a prebuilt records its stored share link on the entry. */ function ChooseStyle({ shoe, busy, onBack, onCustomize, onPremade }) { const premades = shoe.premades || []; // Live share-link capture works for both Nike By You (mid) and Converse (metricId). const allowCustom = true; return (

{shoe.name}

Let's design
{allowCustom && ( )} {premades.length > 0 &&
{allowCustom ? "Or pick a ready-made design" : "Pick a ready-made design"}
}
{premades.map((pm, i) => (
{(pm.colors || []).length > 0 && (
{(pm.colors || []).map((c, j) => )}
)}
{pm.name}
Ready-made design
))}
{!allowCustom && premades.length === 0 && (
Ready-made designs are coming soon for this style — please ask an attendant.
)}
); } function ThankYou({ guestName, onDone }) { const theme = useKioskTheme(); useEffect(() => { const id = setTimeout(onDone, 12000); return () => clearTimeout(id); }, []); return (
LOGO
{theme.casinoName}{theme.tagline}

Your Design Is Saved!

Thanks{guestName ? `, ${guestName}` : ""} — your custom design is locked to your entry. Our team will take it from here.

This kiosk resets automatically for the next guest.
); } function Unavailable({ onRetry }) { return (
One moment

Kiosk temporarily unavailable

We're having trouble reaching the service. Please try again.

); } function App() { const [screen, setScreen] = useState("boot"); const [catalog, setCatalog] = useState([]); const [campaign, setCampaign] = useState(null); const [intake, setIntake] = useState(null); // the check-in form const [model, setModel] = useState(null); // silhouette picked in the gallery const [shoe, setShoe] = useState(null); // silhouette being designed const [entryId, setEntryId] = useState(null); const [busy, setBusy] = useState(false); const Admin = window.Admin; const inShell = !!(window.gpsKiosk && window.gpsKiosk.isKiosk); const boot = async () => { setScreen("boot"); try { const data = await window.GPSApi.campaignActive(); setCampaign(data.campaign); setCatalog(data.shoes || []); if (data.theme && window.GPSTheme) { window.GPS_KIOSK_THEME = data.theme; window.GPSTheme.applyTheme(data.theme); window.dispatchEvent(new CustomEvent("kiosk-theme", { detail: data.theme })); } setScreen((s) => (s === "boot" ? "landing" : s)); } catch (e) { setScreen((s) => (s === "boot" ? "unavailable" : s)); } }; useEffect(() => { boot(); }, []); const resetToLanding = () => { if (entryId) { try { window.GPSApi.abandonEntry(entryId); } catch (e) {} } setEntryId(null); setIntake(null); setModel(null); setShoe(null); boot(); }; // Create the guest entry (intake + chosen silhouette). Returns the entry id. const beginEntry = async (form, chosenShoe) => { const res = await window.GPSApi.createEntry({ ...form, shoeId: chosenShoe ? chosenShoe.id : undefined }); setEntryId(res.entryId); return res.entryId; }; // "Customize Your Own": create the entry, open the live Nike By You designer. const startCustom = async (chosenShoe) => { setBusy(true); try { await beginEntry(intake, chosenShoe); setShoe(chosenShoe || null); setScreen("design"); } catch (e) { alert(e.message || "Couldn't start your design — please try again."); } finally { setBusy(false); } }; // Prebuilt design: create the entry, record the premade's stored share link. const startPremade = async (chosenShoe, pm) => { setBusy(true); try { const id = await beginEntry(intake, chosenShoe); await window.GPSApi.selectPremade(id, pm.id); setScreen("thankyou"); } catch (e) { alert(e.message || "Couldn't save that design — please try again."); } finally { setBusy(false); } }; const onIntake = (form) => { setIntake(form); if (catalog.length > 1) { setScreen("gallery"); } else if (catalog.length === 1) { setModel(catalog[0]); setScreen("style"); } else { beginEntry(form, null).then(() => { setShoe(null); setScreen("design"); }); } // no silhouettes → generic designer }; const captureLink = async (url) => { await window.GPSApi.captureShareLink(entryId, url); // Close the Nike view (shell) and show the Thank-You page in both modes. if (inShell && window.gpsKiosk) { try { await window.gpsKiosk.closeNike(); } catch (e) {} } setScreen("thankyou"); }; if (screen === "admin") return boot()} />; if (screen === "boot") return
Loading…
; if (screen === "unavailable") return ; if (screen === "thankyou") return ; if (screen === "design" && entryId) { if (inShell) { return ( ); } return ( ); } if (screen === "style" && model) { return ( (catalog.length > 1 ? setScreen("gallery") : resetToLanding())} onCustomize={() => startCustom(model)} onPremade={(pm) => startPremade(model, pm)} /> ); } if (screen === "gallery") { return { setModel(s); setScreen("style"); }} />; } if (screen === "intake") { return ; } return setScreen("intake")} onStaff={() => setScreen("admin")} busy={busy} />; } ReactDOM.createRoot(document.getElementById("root")).render();