import React, { useState, useRef, useEffect } from "react"; import QRCode from "qrcode"; import { Html5Qrcode } from "html5-qrcode"; // Default export: a single-file React component you can drop into a small React app (Vite/Create React App). // Dependencies: qrcode, html5-qrcode // Install: npm install qrcode html5-qrcode file-saver export default function JacquelineCheckinApp() { const [guests, setGuests] = useState([]); const [nameInput, setNameInput] = useState(""); const [phoneInput, setPhoneInput] = useState(""); const [eventName] = useState("Jacqueline Send-off Party"); const [welcomeText] = useState("Welcome to Jacqueline's send-off party"); const [scanning, setScanning] = useState(false); const scannerRef = useRef(null); const html5QrcodeRef = useRef(null); // Utility: create a unique guest id const makeGuestId = (name, phone) => { const base = ${name.trim().slice(0,20)}|${phone.trim()}; // deterministic hash (simple) let h = 0; for (let i = 0; i < base.length; i++) { h = (h << 5) - h + base.charCodeAt(i); h |= 0; } return G${Math.abs(h)}; }; // Add guest manually const addGuest = async (e) => { e && e.preventDefault(); if (!nameInput.trim() || !phoneInput.trim()) return; const id = makeGuestId(nameInput, phoneInput); // don't duplicate if (guests.some(g => g.id === id)) { alert('Guest already exists (matching name+phone).'); return; } const qrData = JSON.stringify({ id, name: nameInput.trim(), phone: phoneInput.trim() }); const qrUrl = await QRCode.toDataURL(qrData, { margin: 1, scale: 6 }); const newGuest = { id, name: nameInput.trim(), phone: phoneInput.trim(), qrUrl, checkedIn: false, time: null }; setGuests(prev => [newGuest, ...prev]); setNameInput(""); setPhoneInput(""); }; // Remove guest const removeGuest = (id) => { if (!confirm('Remove guest?')) return; setGuests(prev => prev.filter(g => g.id !== id)); } // Start camera scanner using html5-qrcode const startScanner = async () => { if (scanning) return stopScanner(); setScanning(true); const elementId = "qr-reader"; try { html5QrcodeRef.current = new Html5Qrcode(elementId); const config = { fps: 10, qrbox: { width: 250, height: 250 } }; await html5QrcodeRef.current.start( { facingMode: "environment" }, config, (decodedText, decodedResult) => { try { const parsed = JSON.parse(decodedText); if (parsed && parsed.id) { handleGuestScan(parsed.id); } else { // fallback: treat decodedText as id handleGuestScan(decodedText); } } catch (err) { // not JSON, treat it as id handleGuestScan(decodedText); } } ); } catch (err) { console.error('Scanner start failed', err); alert('Could not access camera or start scanner on this device/browser.'); setScanning(false); } }; const stopScanner = async () => { try { if (html5QrcodeRef.current) { await html5QrcodeRef.current.stop(); html5QrcodeRef.current.clear(); } } catch (err) { console.warn('Error stopping scanner', err); } setScanning(false); }; // When scanning finds an id const handleGuestScan = (idOrPayload) => { // idOrPayload may be the id or full JSON string let id = idOrPayload; if (typeof idOrPayload === 'string') { // if it looks like JSON try parse if (idOrPayload.trim().startsWith('{')) { try { const p = JSON.parse(idOrPayload); id = p.id || idOrPayload; } catch (e) { id = idOrPayload; } } } // find guest setGuests(prev => { const updated = prev.map(g => { if (g.id === id || g.phone === id || g.name === id) { if (!g.checkedIn) { return { ...g, checkedIn: true, time: new Date().toISOString() }; } } return g; }); return updated; }); }; // Manual check-in (by clicking) const toggleCheck = (id) => { setGuests(prev => prev.map(g => g.id === id ? { ...g, checkedIn: !g.checkedIn, time: !g.checkedIn ? new Date().toISOString() : null } : g)); } // Export CSV const exportCSV = () => { const header = ['id','name','phone','checkedIn','time']; const rows = guests.map(g => [g.id, g.name, g.phone, g.checkedIn ? 'YES' : 'NO', g.time || '']); const csv = [header, ...rows].map(r => r.map(cell => '"' + (''+cell).replace(/"/g,'""') + '"').join(',')).join('\n'); const blob = new Blob([csv], { type: 'text/csv' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = ${eventName.replace(/\s+/g,'_')}_attendance.csv; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); } // Quick sample data (demo) useEffect(() => { if (guests.length === 0) { // add 3 demo guests to show layout (remove in production) (async () => { const demo = [ { name: 'John Mwanga', phone: '0755123456' }, { name: 'Asha Mohamed', phone: '0766123456' }, { name: 'Peter L', phone: '0677123456' } ]; for (let d of demo) { const id = makeGuestId(d.name, d.phone); const qrData = JSON.stringify({ id, name: d.name, phone: d.phone }); const qrUrl = await QRCode.toDataURL(qrData, { margin: 1, scale: 6 }); setGuests(prev => [{ id, name: d.name, phone: d.phone, qrUrl, checkedIn: false, time: null }, ...prev]); } })(); } }, []); return (
{welcomeText}
| QR | Name | Phone | Status | Action |
|---|---|---|---|---|
| {g.name} | {g.phone} | {g.checkedIn ? (<>Checked in {new Date(g.time).toLocaleString()} >) : (Not yet)} |
Download QR |