From f077adc11f2f8dcf0ace3d82d7e9a9ba55f04d3b Mon Sep 17 00:00:00 2001 From: Gabriel Bo Date: Fri, 13 Mar 2026 15:17:13 -0700 Subject: [PATCH] feat: add required email field to leaderboard opt-in --- desktop/src/components/SavingsDashboard.tsx | 41 ++++++++++++++++- docs/javascripts/leaderboard.js | 2 +- frontend/src/App.tsx | 2 + frontend/src/components/OptInModal.tsx | 49 ++++++++++++++++++--- frontend/src/lib/api.ts | 1 + frontend/src/lib/store.ts | 10 +++-- 6 files changed, 92 insertions(+), 13 deletions(-) diff --git a/desktop/src/components/SavingsDashboard.tsx b/desktop/src/components/SavingsDashboard.tsx index 4e393ba7..f0f5463b 100644 --- a/desktop/src/components/SavingsDashboard.tsx +++ b/desktop/src/components/SavingsDashboard.tsx @@ -267,6 +267,7 @@ function isProfane(text: string): boolean { const OPTIN_KEY = 'openjarvis-desktop-optin'; const OPTIN_NAME_KEY = 'openjarvis-desktop-display-name'; +const OPTIN_EMAIL_KEY = 'openjarvis-desktop-email'; const OPTIN_ANONID_KEY = 'openjarvis-desktop-anon-id'; function getOrCreateAnonId(): string { @@ -289,7 +290,9 @@ export function SavingsDashboard({ apiUrl }: { apiUrl: string }) { const [optInEnabled, setOptInEnabled] = useState(localStorage.getItem(OPTIN_KEY) === 'true'); const [displayName, setDisplayName] = useState(localStorage.getItem(OPTIN_NAME_KEY) || ''); + const [emailStored, setEmailStored] = useState(localStorage.getItem(OPTIN_EMAIL_KEY) || ''); const [nameInput, setNameInput] = useState(localStorage.getItem(OPTIN_NAME_KEY) || ''); + const [emailInput, setEmailInput] = useState(localStorage.getItem(OPTIN_EMAIL_KEY) || ''); const [nameError, setNameError] = useState(''); const [showOptIn, setShowOptIn] = useState(false); const anonId = getOrCreateAnonId(); @@ -326,6 +329,7 @@ export function SavingsDashboard({ apiUrl }: { apiUrl: string }) { payload: { anon_id: anonId, display_name: displayName, + email: emailStored, total_calls: data.total_calls, total_tokens: data.total_tokens, dollar_savings: dollarSavings, @@ -337,6 +341,7 @@ export function SavingsDashboard({ apiUrl }: { apiUrl: string }) { const handleOptInJoin = () => { const trimmed = nameInput.trim(); + const trimmedEmail = emailInput.trim(); if (!trimmed || trimmed.length < 2 || trimmed.length > 30) { setNameError('Name must be 2-30 characters'); return; @@ -345,19 +350,27 @@ export function SavingsDashboard({ apiUrl }: { apiUrl: string }) { setNameError('Please choose a different name'); return; } + if (!trimmedEmail || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmedEmail)) { + setNameError('Please enter a valid email address'); + return; + } setNameError(''); localStorage.setItem(OPTIN_KEY, 'true'); localStorage.setItem(OPTIN_NAME_KEY, trimmed); + localStorage.setItem(OPTIN_EMAIL_KEY, trimmedEmail); setOptInEnabled(true); setDisplayName(trimmed); + setEmailStored(trimmedEmail); setShowOptIn(false); }; const handleOptOut = () => { localStorage.removeItem(OPTIN_KEY); localStorage.removeItem(OPTIN_NAME_KEY); + localStorage.removeItem(OPTIN_EMAIL_KEY); setOptInEnabled(false); setDisplayName(''); + setEmailStored(''); setShowOptIn(false); }; @@ -411,7 +424,7 @@ export function SavingsDashboard({ apiUrl }: { apiUrl: string }) {
Opt in to privately share your savings for the chance to win a Mac Mini!
-
+
+
+
+
+ Email * + (never shown publicly) +
+ { setEmailInput(e.target.value); setNameError(''); }} + onKeyDown={(e) => { if (e.key === 'Enter') handleOptInJoin(); }} + placeholder="your@email.com" + style={{ + width: '100%', + padding: '8px 12px', + borderRadius: 8, + background: colors.bg, + border: `1px solid ${colors.border}`, color: colors.text, fontSize: 14, outline: 'none', diff --git a/docs/javascripts/leaderboard.js b/docs/javascripts/leaderboard.js index baf8146f..b1a04598 100644 --- a/docs/javascripts/leaderboard.js +++ b/docs/javascripts/leaderboard.js @@ -32,7 +32,7 @@ fetch( SUPABASE_URL + - "/rest/v1/savings_entries?select=*&order=dollar_savings.desc&limit=100", + "/rest/v1/savings_entries?select=display_name,dollar_savings,energy_wh_saved,flops_saved,total_calls,total_tokens&order=dollar_savings.desc&limit=100", { headers: { apikey: SUPABASE_ANON_KEY, diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 543ce47b..16482cf6 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -26,6 +26,7 @@ export default function App() { const setCommandPaletteOpen = useAppStore((s) => s.setCommandPaletteOpen); const optInEnabled = useAppStore((s) => s.optInEnabled); const optInDisplayName = useAppStore((s) => s.optInDisplayName); + const optInEmail = useAppStore((s) => s.optInEmail); const optInAnonId = useAppStore((s) => s.optInAnonId); const optInModalSeen = useAppStore((s) => s.optInModalSeen); const optInModalOpen = useAppStore((s) => s.optInModalOpen); @@ -79,6 +80,7 @@ export default function App() { submitSavings({ anon_id: optInAnonId, display_name: optInDisplayName, + email: optInEmail, total_calls: data.total_calls, total_tokens: data.total_tokens, dollar_savings: dollarSavings, diff --git a/frontend/src/components/OptInModal.tsx b/frontend/src/components/OptInModal.tsx index 48dcacb3..a0225b6d 100644 --- a/frontend/src/components/OptInModal.tsx +++ b/frontend/src/components/OptInModal.tsx @@ -12,12 +12,16 @@ export function OptInModal({ onClose }: OptInModalProps) { const optInDisplayName = useAppStore((s) => s.optInDisplayName); const optInEnabled = useAppStore((s) => s.optInEnabled); + const optInEmail = useAppStore((s) => s.optInEmail); + const [name, setName] = useState(optInDisplayName); + const [email, setEmail] = useState(optInEmail); const [error, setError] = useState(''); const [joined, setJoined] = useState(optInEnabled); const handleJoin = () => { const trimmed = name.trim(); + const trimmedEmail = email.trim(); if (!trimmed) { setError('Please enter a display name'); return; @@ -30,19 +34,23 @@ export function OptInModal({ onClose }: OptInModalProps) { setError('Please choose a different name'); return; } + if (!trimmedEmail || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmedEmail)) { + setError('Please enter a valid email address'); + return; + } setError(''); - setOptIn(true, trimmed); + setOptIn(true, trimmed, trimmedEmail); setJoined(true); }; const handleOptOut = () => { - setOptIn(false, ''); + setOptIn(false, '', ''); setJoined(false); onClose(); }; const features = [ - { icon: Lock, label: 'Fully anonymous — no email, no IP, no hardware info' }, + { icon: Lock, label: 'Private — no IP or hardware info shared publicly' }, { icon: Zap, label: 'Track energy savings from local inference' }, { icon: DollarSign, label: 'See how much you save vs cloud providers' }, { icon: Cpu, label: 'Measure FLOPs and request efficiency' }, @@ -160,7 +168,7 @@ export function OptInModal({ onClose }: OptInModalProps) { ) : ( <> {/* Name input */} -
+
+ + {/* Email input */} +
+ + { + setEmail(e.target.value); + if (error) setError(''); + }} + onKeyDown={(e) => { + if (e.key === 'Enter') handleJoin(); + }} + placeholder="your@email.com" + className="w-full px-3 py-2.5 rounded-xl text-sm outline-none transition-colors" + style={{ + background: 'var(--color-bg-secondary)', + border: '1.5px solid var(--color-border)', + color: 'var(--color-text)', + }} + /> {error && (

void; // Actions: opt-in sharing - setOptIn: (enabled: boolean, displayName: string) => void; + setOptIn: (enabled: boolean, displayName: string, email: string) => void; setOptInModalOpen: (open: boolean) => void; markOptInModalSeen: () => void; } @@ -215,6 +217,7 @@ export const useAppStore = create((set, get) => { optInEnabled: localStorage.getItem(OPTIN_KEY) === 'true', optInDisplayName: localStorage.getItem(OPTIN_NAME_KEY) || '', + optInEmail: localStorage.getItem(OPTIN_EMAIL_KEY) || '', optInAnonId: localStorage.getItem(OPTIN_ANONID_KEY) || crypto.randomUUID(), optInModalSeen: localStorage.getItem(OPTIN_SEEN_KEY) === 'true', optInModalOpen: false, @@ -386,12 +389,13 @@ export const useAppStore = create((set, get) => { // ── Opt-in sharing ────────────────────────────────────────────── - setOptIn: (enabled: boolean, displayName: string) => { + setOptIn: (enabled: boolean, displayName: string, email: string) => { const anonId = get().optInAnonId; localStorage.setItem(OPTIN_KEY, String(enabled)); localStorage.setItem(OPTIN_NAME_KEY, displayName); + localStorage.setItem(OPTIN_EMAIL_KEY, email); localStorage.setItem(OPTIN_ANONID_KEY, anonId); - set({ optInEnabled: enabled, optInDisplayName: displayName }); + set({ optInEnabled: enabled, optInDisplayName: displayName, optInEmail: email }); }, setOptInModalOpen: (open: boolean) => set({ optInModalOpen: open }), markOptInModalSeen: () => {