/* Cumulative usage timer — after 10 min total, require sign-in to continue. */ const USAGE_KEY = 'fp-usage'; const LIMIT_MS = ((window.FP_CONFIG && window.FP_CONFIG.usageGateMinutes) || 10) * 60 * 1000; const TICK_MS = 1000; function readUsage() { try { const raw = localStorage.getItem(USAGE_KEY); if (!raw) return { totalMs: 0, signedIn: false, visitorId: null, name: '' }; const u = JSON.parse(raw); return { totalMs: Number(u.totalMs) || 0, signedIn: !!u.signedIn, visitorId: u.visitorId || null, name: u.name || '', }; } catch (_) { return { totalMs: 0, signedIn: false, visitorId: null, name: '' }; } } function writeUsage(u) { localStorage.setItem(USAGE_KEY, JSON.stringify(u)); } function ensureVisitorId(u) { if (u.visitorId) return u; const id = (typeof crypto !== 'undefined' && crypto.randomUUID) ? crypto.randomUUID() : ('v-' + Date.now() + '-' + Math.random().toString(36).slice(2, 9)); return { ...u, visitorId: id }; } function isAdminPath() { const p = (window.location.pathname || '').replace(/\/$/, ''); return p === '/admin'; } const UsageGateCtx = React.createContext(null); function useUsageGate() { return React.useContext(UsageGateCtx); } function SignInGateModal({ usage, onSuccess }) { const [name, setName] = useState(''); const [email, setEmail] = useState(''); const [phone, setPhone] = useState(''); const [busy, setBusy] = useState(false); const [err, setErr] = useState(''); async function submit(e) { e.preventDefault(); setErr(''); if (!name.trim() || !email.trim() || !phone.trim()) { setErr('Please fill in name, email, and phone.'); return; } setBusy(true); try { const res = await fetch('/api/auth/signin', { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, body: JSON.stringify({ name: name.trim(), email: email.trim(), phone: phone.trim(), visitorId: usage.visitorId, }), }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data.error || 'Could not save sign-in'); onSuccess(name.trim()); } catch (ex) { setErr(ex.message || 'Something went wrong. Try again.'); } finally { setBusy(false); } } const mins = Math.floor(usage.totalMs / 60000); return (
You've explored {mins}+ minutes of live market data. Enter your details to keep browsing Company Stock Price.
Free access · no password · your time is saved across visits on this device