"use client"; import { useCallback, useEffect, useMemo, useState } from 'react'; export type Settings = { timezone: string; }; export function useSettings() { const [settings, setSettings] = useState({ timezone: '' }); useEffect(() => { try { const timezone = localStorage.getItem('user_timezone') || ''; setSettings({ timezone }); } catch {} }, []); const persist = useCallback((s: Settings) => { setSettings(s); try { localStorage.setItem('user_timezone', s.timezone); } catch {} }, []); return { settings, setSettings: persist } as const; } function coerceEmailFrom(value: unknown): string | null { if (typeof value === 'string' && value.includes('@')) { return value; } if (value && typeof value === 'object') { const candidate = (value as any).emailAddress ?? (value as any).email ?? (value as any).value ?? (value as any).address; if (typeof candidate === 'string' && candidate.includes('@')) { return candidate; } } return null; } function deriveEmailFromPayload(payload: any): string { if (!payload) return ''; const profileSlice = payload?.profile; const candidateObjects: any[] = []; if (profileSlice && typeof profileSlice === 'object') { candidateObjects.push(profileSlice); if ((profileSlice as any).response_data && typeof (profileSlice as any).response_data === 'object') { candidateObjects.push((profileSlice as any).response_data); } if (Array.isArray((profileSlice as any).items)) { for (const entry of (profileSlice as any).items as any[]) { if (entry && typeof entry === 'object') { if (typeof entry.data === 'object') candidateObjects.push(entry.data); if (typeof entry.response_data === 'object') candidateObjects.push(entry.response_data); if (typeof entry.profile === 'object') candidateObjects.push(entry.profile); } } } } const directCandidates = [payload?.email]; for (const obj of candidateObjects) { if (!obj || typeof obj !== 'object') continue; directCandidates.push( obj?.email, obj?.email_address, obj?.emailAddress, obj?.profile?.email, obj?.profile?.emailAddress, obj?.profile?.email_address, obj?.user?.email, obj?.user?.emailAddress, obj?.user?.email_address, obj?.data?.email, obj?.data?.emailAddress, obj?.data?.email_address, ); const emailAddresses = (obj as any).emailAddresses; if (Array.isArray(emailAddresses)) { for (const entry of emailAddresses) { const email = coerceEmailFrom(entry) ?? coerceEmailFrom((entry as any)?.value); if (email) return email; } } } for (const candidate of directCandidates) { const email = coerceEmailFrom(candidate); if (email) return email; } return ''; } export default function SettingsModal({ open, onClose, settings, onSave, }: { open: boolean; onClose: () => void; settings: Settings; onSave: (s: Settings) => void; }) { const [timezone, setTimezone] = useState(settings.timezone); const [connectingGmail, setConnectingGmail] = useState(false); const [isRefreshingGmail, setIsRefreshingGmail] = useState(false); const [isDisconnecting, setIsDisconnecting] = useState(false); const [gmailStatusMessage, setGmailStatusMessage] = useState(''); const [gmailConnected, setGmailConnected] = useState(false); const [gmailEmail, setGmailEmail] = useState(''); const [gmailConnId, setGmailConnId] = useState(''); const [gmailProfile, setGmailProfile] = useState | null>(null); const readStoredUserId = useCallback(() => { if (typeof window === 'undefined') return ''; try { return localStorage.getItem('openpoke_user_id') || ''; } catch { return ''; } }, []); const ensureUserId = useCallback(() => { if (typeof window === 'undefined') { return `web-${Math.random().toString(36).slice(2)}`; } try { const existing = localStorage.getItem('openpoke_user_id'); if (existing) return existing; const cryptoObj = (globalThis as { crypto?: Crypto }).crypto; const randomPart = cryptoObj && typeof cryptoObj.randomUUID === 'function' ? cryptoObj.randomUUID().replace(/-/g, '') : Math.random().toString(36).slice(2); const generated = `web-${randomPart}`; localStorage.setItem('openpoke_user_id', generated); return generated; } catch { return `web-${Math.random().toString(36).slice(2)}`; } }, []); const readStoredConnectionRequestId = useCallback(() => { if (gmailConnId) return gmailConnId; if (typeof window === 'undefined') return ''; try { return localStorage.getItem('gmail_connection_request_id') || ''; } catch { return ''; } }, [gmailConnId]); useEffect(() => { try { const savedConnected = localStorage.getItem('gmail_connected') === 'true'; const savedConnId = localStorage.getItem('gmail_connection_request_id') || ''; const savedEmail = localStorage.getItem('gmail_email') || ''; setGmailConnected(savedConnected); setGmailConnId(savedConnId); setGmailEmail(savedEmail); if (savedConnected && savedEmail) { setGmailStatusMessage(`Connected as ${savedEmail}`); } } catch {} }, []); const gmailProfileDetails = useMemo(() => { if (!gmailProfile) return [] as { label: string; value: string }[]; const details: { label: string; value: string }[] = []; const messagesTotal = (gmailProfile as any)?.messagesTotal; if (typeof messagesTotal === 'number') { details.push({ label: 'Messages', value: messagesTotal.toLocaleString() }); } const threadsTotal = (gmailProfile as any)?.threadsTotal; if (typeof threadsTotal === 'number') { details.push({ label: 'Threads', value: threadsTotal.toLocaleString() }); } const historyId = (gmailProfile as any)?.historyId ?? (gmailProfile as any)?.historyID; if (historyId !== undefined && historyId !== null && historyId !== '') { details.push({ label: 'History ID', value: String(historyId) }); } return details; }, [gmailProfile]); const handleConnectGmail = useCallback(async () => { try { setConnectingGmail(true); setGmailStatusMessage(''); const userId = ensureUserId(); const resp = await fetch('/api/gmail/connect', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId }), }); const data = await resp.json().catch(() => ({})); if (!resp.ok || !data?.ok) { const msg = data?.error || `Failed (${resp.status})`; setGmailStatusMessage(msg); return; } const url = data?.redirect_url; const connId = data?.connection_request_id || ''; if (connId) { setGmailConnId(connId); try { localStorage.setItem('gmail_connection_request_id', connId); } catch {} } setGmailConnected(false); setGmailEmail(''); setGmailProfile(null); if (url) { window.open(url, '_blank', 'noopener'); setGmailStatusMessage('Gmail authorization opened in a new tab. Complete it, then press “Refresh status”.'); } else { setGmailStatusMessage('Connection initiated. Refresh status once authorization completes.'); } } catch (e: any) { setGmailStatusMessage(e?.message || 'Failed to connect Gmail'); } finally { setConnectingGmail(false); } }, [ensureUserId]); const refreshGmailStatus = useCallback(async () => { const userId = readStoredUserId(); const connectionRequestId = readStoredConnectionRequestId(); if (!userId && !connectionRequestId) { setGmailConnected(false); setGmailProfile(null); setGmailEmail(''); setGmailStatusMessage('Connect Gmail to get started.'); return; } try { setIsRefreshingGmail(true); setGmailStatusMessage('Refreshing Gmail status…'); const resp = await fetch('/api/gmail/status', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId, connectionRequestId }), }); const data = await resp.json().catch(() => ({})); if (!resp.ok || !data?.ok) { const message = data?.error || `Failed (${resp.status})`; setGmailConnected(false); setGmailProfile(null); setGmailEmail(''); setGmailStatusMessage(message); return; } if (!gmailConnId && connectionRequestId) { setGmailConnId(connectionRequestId); } const profileData = data?.profile && typeof data.profile === 'object' ? (data.profile as Record) : null; setGmailProfile(profileData); const derivedEmail = deriveEmailFromPayload({ email: data?.email, profile: profileData }); const email = derivedEmail || (typeof data?.email === 'string' ? data.email : ''); const connected = Boolean(data?.connected); setGmailConnected(connected); setGmailEmail(email); if (connected) { const source = typeof data?.profile_source === 'string' ? data.profile_source : ''; const sourceNote = source === 'fetched' ? 'Verified moments ago.' : source === 'cache' ? 'Loaded from cache.' : ''; const message = email ? `Connected as ${email}` : 'Gmail connected.'; setGmailStatusMessage(sourceNote ? `${message} ${sourceNote}` : message); try { localStorage.setItem('gmail_connected', 'true'); if (email) localStorage.setItem('gmail_email', email); if (typeof data?.user_id === 'string' && data.user_id) { localStorage.setItem('openpoke_user_id', data.user_id); } } catch {} } else { const statusText = typeof data?.status === 'string' && data.status && data.status !== 'UNKNOWN' ? `Status: ${data.status}` : 'Not connected yet.'; setGmailStatusMessage(statusText); try { localStorage.removeItem('gmail_connected'); localStorage.removeItem('gmail_email'); } catch {} } } catch (e: any) { setGmailConnected(false); setGmailProfile(null); setGmailEmail(''); setGmailStatusMessage(e?.message || 'Failed to check Gmail status'); } finally { setIsRefreshingGmail(false); } }, [gmailConnId, readStoredConnectionRequestId, readStoredUserId]); const handleDisconnectGmail = useCallback(async () => { if (typeof window !== 'undefined') { const proceed = window.confirm('Disconnect Gmail from OpenPoke?'); if (!proceed) return; } try { setIsDisconnecting(true); setGmailStatusMessage('Disconnecting Gmail…'); const userId = readStoredUserId(); const connectionRequestId = readStoredConnectionRequestId(); const resp = await fetch('/api/gmail/disconnect', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId, connectionRequestId }), }); const data = await resp.json().catch(() => ({})); if (!resp.ok || !data?.ok) { const message = data?.error || `Failed (${resp.status})`; setGmailStatusMessage(message); return; } setGmailConnected(false); setGmailEmail(''); setGmailProfile(null); setGmailConnId(''); setGmailStatusMessage('Gmail disconnected.'); try { localStorage.removeItem('gmail_connected'); localStorage.removeItem('gmail_email'); localStorage.removeItem('gmail_connection_request_id'); localStorage.removeItem('openpoke_user_id'); } catch {} } catch (e: any) { setGmailStatusMessage(e?.message || 'Failed to disconnect Gmail'); } finally { setIsDisconnecting(false); } }, [readStoredConnectionRequestId, readStoredUserId]); useEffect(() => { setTimezone(settings.timezone); }, [settings]); useEffect(() => { if (!open) return; void refreshGmailStatus(); }, [open, refreshGmailStatus]); if (!open) return null; const connectButtonLabel = connectingGmail ? 'Opening…' : gmailConnected ? 'Reconnect' : 'Connect Gmail'; const refreshButtonLabel = isRefreshingGmail ? 'Refreshing…' : 'Refresh status'; const disconnectButtonLabel = isDisconnecting ? 'Disconnecting…' : 'Disconnect'; return (

Settings

setTimezone(e.target.value)} placeholder="e.g. America/New_York, Europe/London" readOnly={!timezone} />

{timezone ? 'Auto-detected from browser. Edit to override.' : 'Will be auto-detected on next page load.'}

Integrations
Gmail (via Composio)

Connect Gmail to unlock email search, drafting, and automations inside OpenPoke.

{gmailConnected ? 'Connected' : 'Not connected'}
{gmailConnected ? (
Connected account
{gmailEmail || 'Email unavailable'}
{gmailProfileDetails.length > 0 && (
{gmailProfileDetails.map((item) => (
{item.label}
{item.value}
))}
)} {gmailStatusMessage && (

{gmailStatusMessage}

)}
) : (
{gmailStatusMessage || 'Complete the connection to view your Gmail account details here.'}
)}
{gmailConnected && ( )}
); }