// rewards.jsx โ Tukar Poin: katalog voucher, redeem, kode, dompet warga. Global.
function discountLabel(v) {
if (!v) return '';
if (v.discount_type === 'amount') return '-Rp' + Math.round(v.discount_value || 0).toLocaleString('id-ID');
if (v.discount_type === 'percent') return '-' + (v.discount_value || 0) + '%';
return '';
}
function fmtDateID(d) {
if (!d) return '';
try { return new Date(d).toLocaleDateString('id-ID', { day: '2-digit', month: 'short', year: 'numeric' }); } catch (e) { return d; }
}
function copyText(t) {
try {
if (navigator.clipboard && navigator.clipboard.writeText) return navigator.clipboard.writeText(t);
} catch (e) {}
try {
var ta = document.createElement('textarea'); ta.value = t; ta.style.position = 'fixed'; ta.style.opacity = '0';
document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta);
} catch (e) {}
return Promise.resolve();
}
// ---------- copy button (kode) ----------
function CopyBtn({ code, style, big }) {
const [done, setDone] = React.useState(false);
const onCopy = () => { copyText(code); setDone(true); setTimeout(() => setDone(false), 1600); };
return (
);
}
// ---------- voucher card (photo-forward) ----------
function VoucherCard({ v, points, onRedeem }) {
const dc = DIR_CATS.find(d => d.key === v.category) || DIR_CATS[0];
const afford = points >= v.points_cost;
const kurang = v.points_cost - points;
return (
);
}
// ---------- redeem confirm sheet ----------
function RedeemSheet({ v, points, busy, err, onConfirm, onClose }) {
const after = points - v.points_cost;
const afford = after >= 0;
return (
e.stopPropagation()} style={{ background: 'var(--surface)', borderTopLeftRadius: 'var(--r-lg)', borderTopRightRadius: 'var(--r-lg)', padding: '10px 20px', paddingBottom: 'max(22px, env(safe-area-inset-bottom))', boxShadow: 'var(--sh-3)', animation: 'gsSheetUp 280ms var(--ease)' }}>
Tukar voucher?
{v.title}{v.place_name ? ' ยท ' + v.place_name : ''}
Saldo sekarang
{points.toLocaleString('id-ID')}
Sesudah
{after.toLocaleString('id-ID')}
-{v.points_cost}
{err &&
{err}
}
);
}
// ---------- redeem success (kode besar) ----------
function RedeemSuccess({ result, onClose }) {
return (
๐
Voucher ditukar!
{result.title}{result.place_name ? ' ยท ' + result.place_name : ''}
KODE VOUCHER
{result.code}
Tunjukkan kode ini ke kasir
Sisa poinmu: {(result.sisa_poin || 0).toLocaleString('id-ID')}
);
}
// ---------- wallet row (Voucherku) ----------
function WalletRow({ r }) {
const active = r.status === 'active';
return (
{active ? '๐๏ธ' : (r.status === 'used' ? 'โ
' : 'โ')}
{r.title}
{r.place_name || 'gs.town'} ยท {discountLabel(r) || (r.points_cost + ' poin')}
{active
? {r.code}
: {r.code}}
{active
?
: {r.status === 'used' ? 'DIPAKAI' : 'HANGUS'}}
);
}
// ---------- rewards screen (list + wallet) ----------
function RewardsScreen({ profile, authed, onRequireAuth, onClose }) {
const [tab, setTab] = React.useState('katalog');
const [rows, setRows] = React.useState(null);
const [notReady, setNotReady] = React.useState(false);
const [wallet, setWallet] = React.useState(null);
const [sel, setSel] = React.useState(null); // voucher yang mau ditukar
const [busy, setBusy] = React.useState(false);
const [err, setErr] = React.useState('');
const [success, setSuccess] = React.useState(null); // hasil redeem
const [points, setPoints] = React.useState((profile && profile.points) || 0);
const loadCatalog = React.useCallback(() => {
setRows(null);
gsData.loadVouchers().then(r => { if (r.notReady) { setNotReady(true); setRows([]); } else { setNotReady(false); setRows(r.rows); } });
}, []);
const loadWallet = React.useCallback(() => {
setWallet(null);
gsData.myWallet().then(w => { if (w.notReady) { setNotReady(true); setWallet({ points: points, redemptions: [] }); } else { setWallet(w); setPoints(w.points); } });
}, [points]);
React.useEffect(() => { loadCatalog(); }, []);
React.useEffect(() => { if (authed) gsData.myWallet().then(w => { if (!w.notReady) setPoints(w.points); }); }, [authed]);
React.useEffect(() => { if (tab === 'dompet' && wallet === null) loadWallet(); }, [tab]);
const onRedeem = (v) => {
if (!authed) { onRequireAuth && onRequireAuth(); return; }
setErr(''); setSel(v);
};
const confirmRedeem = async () => {
setBusy(true); setErr('');
const res = await gsData.redeemVoucher(sel.id);
setBusy(false);
if (res.notReady) { setErr('Fitur tukar poin segera aktif.'); return; }
if (res.error) { setErr(res.error); return; }
setPoints(res.data.sisa_poin);
setSel(null);
setSuccess(res.data);
setWallet(null); // paksa reload dompet nanti
loadCatalog(); // sisa kuota bisa berubah
};
const closeSuccess = () => { setSuccess(null); setTab('dompet'); };
const empty = rows && rows.length === 0 && !notReady;
return (
{/* header */}
Tukar Poin
{points.toLocaleString('id-ID')}
{[['katalog', 'Voucher'], ['dompet', 'Voucherku']].map(([k, l]) => (
))}
{tab === 'katalog' ? (
rows === null ? (
{[0, 1, 2].map(i => )}
) : notReady ? (
) : empty ? (
) : (
{rows.map(v => )}
)
) : (
!authed ? (
onRequireAuth && onRequireAuth()} />
) : wallet === null ? (
{[0, 1, 2].map(i => )}
) : wallet.redemptions.length === 0 ? (
setTab('katalog')} />
) : (
(() => {
const act = wallet.redemptions.filter(r => r.status === 'active');
const hist = wallet.redemptions.filter(r => r.status !== 'active');
return (
{act.length > 0 && (
AKTIF ยท {act.length}
{act.map(r => )}
)}
{hist.length > 0 && (
RIWAYAT
{hist.map(r => )}
)}
);
})()
)
)}
{sel &&
{ if (!busy) { setSel(null); setErr(''); } }} />}
{success && }
);
}
Object.assign(window, { RewardsScreen, VoucherCard, RedeemSheet, RedeemSuccess, WalletRow, CopyBtn, discountLabel });