// ============================================================================
// Mini-CRM — "Today at Aurora One" + the studio session controls.
// ----------------------------------------------------------------------------
// Three pieces, all gated on a signed-in presenter (uni_presenter_v1 with a PIN):
// • MiniCRM — a registered screen (#/minicrm) listing today's walk-ins from
// the relay; tap an un-attended guest to "Start presenting".
// • StudioBar — a floating control cluster (sits by the presenter chip):
// "Today" (opens the mini-CRM) + "End session" (opens the modal).
// Also the shim that STARTS the journey tracker the moment a
// customer is claimed, so per-screen dwell times are captured.
// • EndSessionModal — disposition + notes + the auto-collected screen-time
// summary → POST /api/studio/session-end → CRM lead timeline.
// data-crm="1" everywhere so the salesperson's taps never pollute the journey.
// Matches the app's ivory / gold / ink aesthetic.
// ============================================================================
(function () {
// Same-origin base: '' → calls resolve to /api/studio/* on whichever host serves
// this page (vega.khosha.tech, century.thephygital.studio, …). Never hard-code a host.
const RELAY = '';
const PKEY = 'uni_presenter_v1';
function presenter() { try { return JSON.parse(sessionStorage.getItem(PKEY)) || null; } catch (e) { return null; } }
function passcode() { const p = presenter(); return p && p.passcode ? p.passcode : ''; }
function signedIn() { const p = presenter(); return !!(p && p.passcode && !p.skipped); }
const fmtMins = (ms) => { const m = Math.round((ms || 0) / 60000); return m < 1 ? '<1m' : m + 'm'; };
// ── RealDesk analytics: "a guest was checked in and a journey started".
// PRIVACY: lead id + non-identifying stage only — never the guest's name,
// phone or email. Guarded so telemetry can never break a live demo.
function trackCheckin(leadId, row) {
try {
if (!window.RDA) return;
window.RDA.track('checkin', leadId || null, 1, {
via: 'minicrm-screen',
stage: (row && row.stage) || null,
resumed: !!(row && row.attended),
});
} catch (e) {}
}
// ── The mini-CRM screen ─────────────────────────────────────────────────
function MiniCRM() {
const [rows, setRows] = React.useState([]);
const [state, setState] = React.useState('idle'); // idle | loading | ready | error | denied
const [err, setErr] = React.useState('');
const [claiming, setClaiming] = React.useState(null); // lead id currently being claimed
const [claimErr, setClaimErr] = React.useState('');
const load = React.useCallback(async () => {
let pc = passcode();
if (!pc && window.UNI_PRESENTER && window.UNI_PRESENTER.autoUnlock) {
// Cold visitor landing straight on #/minicrm: the demo auto-unlock
// (presenter.jsx) may still be in flight — wait for it before showing
// the locked state.
const p = await window.UNI_PRESENTER.autoUnlock();
pc = (p && p.passcode) || passcode();
}
if (!pc) { setState('denied'); return; }
setState('loading'); setErr('');
try {
const res = await fetch(`${RELAY}/api/studio/walkins?passcode=${encodeURIComponent(pc)}`);
const data = await res.json();
if (data && data.ok) { setRows(data.walkins || []); setState('ready'); }
else { setErr(data && data.error === 'invalid_passcode' ? 'PIN not recognised — unlock again.' : 'Couldn’t load walk-ins.'); setState('error'); }
} catch (e) { setErr('Couldn’t reach the studio service.'); setState('error'); }
}, []);
React.useEffect(() => { load(); }, [load]);
// Selecting a customer + starting their journey is what LINKS them to the
// salesperson — no typed phone number. Claim the lead by id via the relay,
// then set them as the active session and drop onto Home to present.
async function startPresenting(row) {
if (claiming) return;
const pc = passcode();
if (!pc) { setState('denied'); return; }
setClaiming(row.id); setClaimErr('');
try {
const res = await fetch(`${RELAY}/api/studio/claim-by-id`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ passcode: pc, lead_id: row.id }),
});
const data = await res.json();
if (data && data.ok) {
const cust = { id: data.lead_id || row.id, name: data.lead_name || row.name };
trackCheckin(data.lead_id || row.id, row);
if (window.UNI_SESSION) {
window.UNI_SESSION.setCustomer(cust);
if (!window.UNI_SESSION.isActive()) window.UNI_SESSION.start(cust);
}
if (window.navigate) window.navigate('home');
} else {
setClaimErr(data && data.error === 'invalid_passcode' ? 'PIN not recognised — unlock again.'
: data && data.error === 'lead_not_found' ? 'This lead is no longer active.'
: 'Couldn’t start the journey.');
setClaiming(null);
}
} catch (e) {
setClaimErr('Couldn’t reach the studio service.');
setClaiming(null);
}
}
return (
Vega Realty · Sales Studio
Today at Aurora One
Walk-ins registered today · tap an awaiting guest to begin their tour.
{state === 'denied' &&
Unlock the studio first (triple-tap the centre logo on Home).