// screens-desktop.jsx — adapter and missing desktop-only screens for the new desktop bundle function desktopListings(ctx = {}) { return Array.isArray(ctx.listings) ? ctx.listings : (window.LISTINGS || []); } function desktopSearches(ctx = {}) { return Array.isArray(ctx.searches) ? ctx.searches : (window.SEARCHES || []); } function desktopNotifications(ctx = {}) { return Array.isArray(ctx.notifications) ? ctx.notifications : (window.NOTIFICATIONS || []); } function desktopJobs(ctx = {}) { return ctx.admin?.jobs || window.JOBS || []; } function desktopEvaluationMap(ctx = {}) { return ctx.evaluations || window.AI_EVALS || {}; } function desktopEvaluationFor(ctx = {}, id) { const map = desktopEvaluationMap(ctx); return map[String(id)] || map[id] || null; } function desktopInvestmentItems(ctx = {}) { return ctx.investor?.evaluations || window.INVESTOR?.evaluations || []; } function desktopListingId(listing = {}) { return listing.id ?? listing.adId ?? listing.ad_id ?? null; } function desktopFindListing(ctx = {}, id = null) { if (id == null) return null; const key = String(id); const direct = desktopListings(ctx).find(item => ( String(desktopListingId(item)) === key || String(item.adId || '') === key || String(item.evaluationId || '') === key )); if (direct) return direct; const investment = desktopInvestmentItems(ctx).find(item => ( String(item.adId || '') === key || String(item.id || '') === key || String(desktopListingId(item.listing || {})) === key )); return investment?.listing || null; } function desktopEvaluationForListing(ctx = {}, listing = {}) { const id = desktopListingId(listing); const direct = desktopEvaluationFor(ctx, id); if (direct) return direct; const investment = desktopInvestmentItems(ctx).find(item => ( String(item.adId || '') === String(id) || String(desktopListingId(item.listing || {})) === String(id) )); return investment?.evaluation || null; } function desktopPhotos(listing = {}) { const fallback = [ 'data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201200%20800%22%3E%3Crect%20width%3D%221200%22%20height%3D%22800%22%20fill%3D%22%23E8E0D2%22%2F%3E%3Cpath%20d%3D%22M447%20497l89-112%2069%2082%2050-61%2098%20121H447z%22%20fill%3D%22%23B9A98F%22%2F%3E%3Ccircle%20cx%3D%22729%22%20cy%3D%22318%22%20r%3D%2242%22%20fill%3D%22%23CDBFAD%22%2F%3E%3Ctext%20x%3D%22600%22%20y%3D%22612%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2238%22%20fill%3D%22%237A6A56%22%3EBild%20nicht%20verfügbar%3C%2Ftext%3E%3C%2Fsvg%3E', ]; const photos = Array.isArray(listing.photos) && listing.photos.length ? listing.photos : fallback; return [0, 1, 2, 3].map(i => photos[i] || photos[0] || fallback[i]); } function desktopSearchPlatforms(search = {}) { return search.platforms || search.providers || ['kleinanzeigen']; } function desktopSearchKeywords(search = {}) { return search.keywords || search.requireKeywords || []; } function desktopSearchExcludeKeywords(search = {}) { return search.excludeKeywords || []; } function desktopMatchesQuery(item = {}, query = '') { const q = String(query || '').trim().toLowerCase(); if (!q) return true; const text = [ item.id, item.adId, item.taskId, item.searchId, item.name, item.title, item.city, item.district, item.address, item.plz, item.platform, item.sourceUrl, item.description, item.body, item.file, ...(Array.isArray(item.features) ? item.features : []), ...(Array.isArray(item.keywords) ? item.keywords : []), ...(Array.isArray(item.platforms) ? item.platforms : []), ].filter(Boolean).join(' ').toLowerCase(); return text.includes(q); } function desktopFilterByQuery(items = [], query = '') { return String(query || '').trim() ? items.filter(item => desktopMatchesQuery(item, query)) : items; } function desktopFeatureText(listing = {}) { return [ listing.title, listing.description, ...(Array.isArray(listing.features) ? listing.features : []), ].filter(Boolean).join(' ').toLowerCase(); } function desktopFirstName(user = {}) { return (user.name || 'ImmoBot').split(/\s+/)[0] || 'ImmoBot'; } function desktopUsedQuota(taskLimit = {}) { const max = taskLimit?.max_evaluations_per_day || 100; const remaining = taskLimit?.remaining_evaluations == null ? max : taskLimit.remaining_evaluations; return Math.max(0, max - remaining); } function fmtMetricPrice(value) { const n = Number(value || 0); if (!n) return '—'; if (Math.abs(n) >= 1000000) return `${(n / 1000000).toLocaleString('de-DE', { maximumFractionDigits: 1 })} Mio. €`; return fmtPriceShort(n); } function fmtPct(value) { if (value == null || value === '') return '—'; return `${Number(value).toLocaleString('de-DE', { maximumFractionDigits: 2 })}%`; } function desktopSyncGlobals(ctx = {}) { window.LISTINGS = desktopListings(ctx); window.SEARCHES = desktopSearches(ctx); window.NOTIFICATIONS = desktopNotifications(ctx); window.JOBS = desktopJobs(ctx); window.AI_EVALS = desktopEvaluationMap(ctx); } function desktopActiveFor(route) { if (['mietenDashboard', 'mietenResults', 'searchBuilderMiete'].includes(route)) return 'miete'; if (['investor', 'compare'].includes(route)) return 'investor'; if (route === 'portfolio') return 'merk'; if (route === 'notifications') return 'mitt'; if (['swipe', 'discover'].includes(route)) return 'entd'; if (route === 'admin') return 'jobs'; if (route === 'exports') return 'export'; if (['profile', 'security'].includes(route)) return 'profile'; return 'kauf'; } function DesktopRouteScreen({ ctx, current }) { desktopSyncGlobals(ctx); const [searchQuery, setSearchQuery] = React.useState(''); const active = desktopActiveFor(current.name); const setRoute = (route, tab, params = null) => { if (tab) ctx.setTab?.(tab); ctx.reset(route, params); }; const onNav = (id) => { const map = { kauf: ['dashboard', 'immobilien'], miete: ['mietenDashboard', 'mieten'], investor: ['investor', 'investor'], merk: ['portfolio', 'portfolio'], mitt: ['notifications', 'notifications'], entd: ['discover', 'immobilien'], jobs: ['admin', 'profile'], export: ['exports', 'profile'], profile: ['profile', 'profile'], newSearch: ['searchBuilder', 'immobilien'], }; const next = map[id] || map.kauf; setRoute(next[0], next[1]); }; const handleSearchSubmit = (value) => { const q = String(value || searchQuery || '').trim(); if (!q) return; const listing = desktopListings(ctx).find(item => desktopMatchesQuery(item, q)); if (listing) { ctx.navigate('listing', { id: listing.id }); return; } const search = desktopSearches(ctx).find(item => desktopMatchesQuery(item, q)); if (search) { const route = search.category === 'miete' ? 'mietenResults' : 'results'; ctx.navigate(route, { searchId: search.id }); } }; const topBarProps = { notifBadge: ctx.notifBadge || 0, searchValue: searchQuery, onSearchChange: setSearchQuery, onSearchSubmit: handleSearchSubmit, onNotifications: () => onNav('mitt'), }; const common = { active, onNav, ctx, searchQuery, setSearchQuery, topBarProps, onNewSearch: () => setRoute('searchBuilder', 'immobilien'), onOpenAdmin: () => setRoute('admin', 'profile'), onOpenResults: (searchId) => { const searches = desktopSearches(ctx); const search = searches.find(s => String(s.id) === String(searchId)) || searches.find(s => s.category === 'kauf') || searches[0]; const route = search?.category === 'miete' ? 'mietenResults' : 'results'; ctx.navigate(route, { searchId: search?.id || searchId }); }, onOpenDetail: (id) => ctx.navigate('listing', { id }), onOpenAI: (id) => ctx.navigate('aiEval', { id: id || current.params?.id }), onSetResultsView: (view, search) => { const route = search?.category === 'miete' ? 'mietenResults' : 'results'; ctx.navigate(route, { searchId: search?.id || current.params?.searchId, view }, true); }, onRefresh: (search) => ctx.refreshSearch?.(search.taskId || search.id), onEvaluate: (search) => ctx.evaluateSearch?.(search.taskId || search.id), onExport: (search) => ctx.exportSearch?.(search.taskId || search.id), onReload: () => ctx.reloadAdmin?.(), }; switch (current.name) { case 'searchBuilder': return ; case 'searchBuilderMiete': return ; case 'results': case 'mietenResults': return ; case 'listing': return ; case 'aiEval': return ; case 'mietenDashboard': return ; case 'swipe': case 'discover': return ; case 'investor': return ; case 'compare': return ; case 'portfolio': return ; case 'notifications': return ; case 'profile': return ; case 'security': return ; case 'admin': return ; case 'exports': return ; default: return ; } } function DesktopEmpty({ active = 'kauf', onNav = () => {}, ctx = {}, title, subtitle, icon }) { return (
{icon}

{title}

{subtitle &&

{subtitle}

}
); } function DesktopSearchBuilderScreen({ ctx, type = 'kauf', active = 'kauf', onNav = () => {}, topBarProps = {} }) { const [form, setForm] = React.useState({ name: '', city: 'Berlin', radius: 10, priceMin: type === 'kauf' ? 250000 : 800, priceMax: type === 'kauf' ? 650000 : 1800, roomsMin: 2, roomsMax: 4, areaMin: 50, areaMax: 140, keywords: '', excludeKeywords: '', }); const [message, setMessage] = React.useState(null); const update = (key, value) => setForm(v => ({ ...v, [key]: value })); const submit = async () => { setMessage(null); const created = await ctx.addSearch({ type, property_type: 'wohnung', city: form.city, radius: Number(form.radius), price_min: Number(form.priceMin), price_max: Number(form.priceMax), rooms_min: Number(form.roomsMin), rooms_max: Number(form.roomsMax), area_min: Number(form.areaMin), area_max: Number(form.areaMax), require_keywords: form.keywords.split(',').map(s => s.trim()).filter(Boolean), exclude_keywords: form.excludeKeywords.split(',').map(s => s.trim()).filter(Boolean), active: true, run_now: true, }); const route = type === 'miete' ? 'mietenResults' : 'results'; ctx.navigate(route, { searchId: created?.id }, true); }; return (
} onClick={submit}>Suche starten} {...topBarProps} />
update('name', v)} /> update('city', v)} /> update('radius', v)} /> update('roomsMin', v)} onRight={v => update('roomsMax', v)} /> update('priceMin', v)} onRight={v => update('priceMax', v)} /> update('areaMin', v)} onRight={v => update('areaMax', v)} /> update('keywords', v)} placeholder="balkon, altbau" /> update('excludeKeywords', v)} placeholder="wbs, tausch" />
{message &&
{message}
}
Abbrechen } onClick={submit}>Speichern und suchen
); } function DField({ label, children }) { return ( ); } function DInput({ value, onChange, type = 'text', placeholder }) { return ( onChange(e.target.value)} style={{ width: '100%', border: '1px solid var(--border)', background: 'var(--surface)', borderRadius: 10, padding: '11px 12px', fontFamily: 'var(--font-sans)', fontSize: 13, color: 'var(--ink)' }} /> ); } function DRange({ left, right, onLeft, onRight }) { return (
); } function DesktopCollectionScreen({ active, onNav, ctx, kind, onOpenDetail, onNewSearch, searchQuery = '', topBarProps = {} }) { const listings = desktopListings(ctx); const favList = listings.filter(l => ctx.favs?.has(String(l.id)) || ctx.favs?.has(l.id)); const configs = { rentals: { crumb: ['Arbeitsbereich', 'Mieten'], title: 'Miet-Suchen', subtitle: 'Aktive Mietprofile und neue Treffer', listings: listings.filter(l => l.type === 'miete'), action: } onClick={() => ctx.reset('searchBuilderMiete')}>Neue Mietsuche, }, portfolio: { crumb: ['Arbeitsbereich', 'Merkliste'], title: 'Merkliste', subtitle: 'Gespeicherte Objekte mit aktualisierten Bewertungen', listings: favList, action: } onClick={ctx.exportPortfolio}>Portfolio CSV, }, discover: { crumb: ['Arbeitsbereich', 'Entdecken'], title: 'Entdecken', subtitle: 'Neue Vorschläge aus Ihren Suchmustern', listings: listings.filter(l => l.aiScore).sort((a, b) => (b.aiScore || 0) - (a.aiScore || 0)).slice(0, 12), action: } onClick={onNewSearch}>Neue Suche, }, }; const cfg = configs[kind]; const [showTopOnly, setShowTopOnly] = React.useState(false); const shownListings = desktopFilterByQuery(cfg.listings, searchQuery) .filter(l => !showTopOnly || Number(l.aiScore || 0) >= 80); return (
{kind !== 'portfolio' && } onClick={() => setShowTopOnly(v => !v)}>Top-Score}{cfg.action}} {...topBarProps} />
{shownListings.length ? (
{shownListings.map(l => )}
) : (

Noch keine Einträge

Sobald passende Inserate vorhanden sind, erscheinen sie hier.

)}
); } function DesktopNotificationsScreen({ active, onNav, ctx, searchQuery = '', topBarProps = {} }) { React.useEffect(() => { ctx.loadNotificationSettings?.().catch(() => {}); }, []); const notifications = desktopFilterByQuery(desktopNotifications(ctx), searchQuery); const allNotifications = desktopNotifications(ctx); const markAll = () => ctx.setNotifications?.(allNotifications.map(n => ({ ...n, read: true }))); const click = (n) => { if (n.listingId) ctx.navigate('listing', { id: n.listingId }); else if (n.searchId) ctx.navigate('results', { searchId: n.searchId }); }; return (
} onClick={markAll}>Alle gelesen} {...topBarProps} />
{notifications.map((n, i) => ( ))}

Browser-Push

{ctx.notificationSettings?.hasSubscriptions ? `${ctx.notificationSettings.subscriptions.length} Gerät(e) verbunden.` : 'Noch kein Gerät verbunden.'}

{(ctx.notificationSettings?.subscriptions || []).map(device => (
{device.label}
{device.endpointPreview}
ctx.revokePushSubscription?.(device.endpoint)}>Entfernen
))}
Test senden Alle entfernen
); } function DesktopExportsScreen({ active, onNav, ctx, searchQuery = '', topBarProps = {} }) { const rows = desktopFilterByQuery(desktopSearches(ctx).map(s => ({ id: s.taskId || s.id, file: `${s.name || 'suche'}-${s.id}.csv`, type: 'CSV', created: s.lastRun ? fmtDateTime(s.lastRun) : 'Noch nicht gelaufen', size: `${s.matches || 0} Zeilen`, name: s.name, city: s.city, })), searchQuery).slice(0, 6); return (
} onClick={ctx.exportPortfolio}>Portfolio exportieren} {...topBarProps} />
DateiTypErstelltUmfang
{rows.map((row, i) => (
{row.file} {row.type} {row.created} {row.size} } onClick={() => ctx.exportSearch?.(row.id)} />
))}
); } function investorListingKey(item = {}) { return String(item.adId || desktopListingId(item.listing) || item.id || ''); } function investorTopMetric(metrics = {}) { const entries = [ ['gross_yield', metrics?.gross_yield], ['net_yield', metrics?.net_yield], ['cap_rate', metrics?.cap_rate], ['cash_on_cash', metrics?.cash_on_cash], ].filter(([, value]) => value != null && !Number.isNaN(Number(value))); if (!entries.length) return null; entries.sort((a, b) => Number(b[1]) - Number(a[1])); return entries[0]; } function InvestorScreen({ ctx, active = 'investor', onNav = () => {}, searchQuery = '', topBarProps = {} }) { const [loaded, setLoaded] = React.useState(false); React.useEffect(() => { if (!loaded) { setLoaded(true); ctx.loadInvestor?.().catch(() => {}); } }, [loaded]); const data = ctx.investor || {}; const items = (data.evaluations || []).filter(item => ( desktopMatchesQuery(item, searchQuery) || desktopMatchesQuery(item.listing || {}, searchQuery) )); const summary = data.summary || {}; const locations = data.locations || []; const distribution = data.distribution || {}; const topDeals = items.slice(0, 12); const heroDeal = topDeals[0] || null; const goListing = (item) => ctx.navigate('listing', { id: investorListingKey(item) }); const avgNet = summary.averageNetYield ?? null; const avgGross = summary.averageGrossYield ?? null; const annualIncome = summary.estimatedAnnualIncome ?? data.portfolio?.summary?.estimatedAnnualIncome ?? null; const bestCashflow = items.reduce((best, item) => { const value = Number(item.metrics?.monthly_cash_flow?.net_cash_flow ?? -Infinity); return value > (best.value ?? -Infinity) ? { item, value } : best; }, {}); const bestYield = items.reduce((best, item) => { const metric = investorTopMetric(item.metrics || {}); const value = metric ? Number(metric[1]) : -Infinity; return value > (best.value ?? -Infinity) ? { item, value, key: metric?.[0] } : best; }, {}); if (!ctx.isDesktop) { return (
Investment Cockpit

Investor

Deal-Pipeline
{summary.count || items.length || 0} Objekte
Top-Score {summary.topScore || '—'} · Ø Score {summary.averageScore || '—'} · Volumen {fmtMetricPrice(summary.totalValue)}
-Infinity ? fmtMetricPrice(bestCashflow.value) : '—'} label="Top Cashflow/Mon." delta={bestCashflow.item?.listing?.city || '—'} />

Top-Deals

Nach Score, Rendite und Risiko priorisiert
{topDeals.map((item, i) => goListing(item)} />)} {!topDeals.length && } title="Noch keine Investmentdaten" subtitle="Sobald Kaufobjekte bewertet sind, erscheinen hier Rendite, Risiko und Cashflow." />}
); } return (
} onClick={ctx.exportPortfolio}>CSV} onClick={() => ctx.navigate('compare', { ids: (data.compare?.ids || topDeals.slice(0, 4).map(x => x.id)).slice(0, 4) })}>Top 4 vergleichen} {...topBarProps} />
Investment Radar

Die besten Objekte zuerst.

Kombiniert KI-Score, Renditeannahmen, Cashflow, CapEx, Leerstandsrisiko und Standortsignale zu einer schnellen Investment-Priorisierung.

{heroDeal && }

Portfolio-Qualität

Priorisierte Deal-Liste

Score · Yield · Cashflow · Risiko · CapEx
{items.length} Bewertungen
RangObjektPreisNettoCashflowRisikoScore
{topDeals.map((item, i) => goListing(item)} last={i === topDeals.length - 1} />)} {!topDeals.length &&
Noch keine Investment-Bewertungen vorhanden.
}

Investment-These

{heroDeal?.evaluation?.investment_thesis || heroDeal?.evaluation?.summary || 'Noch keine Investment-These vorhanden.'}
{heroDeal?.evaluation?.next_steps?.length > 0 &&
{heroDeal.evaluation.next_steps.slice(0, 4).map((step, idx) =>
{idx + 1}{step}
)}
}

Top-Lagen

{locations.slice(0, 6).map(loc => )} {!locations.length &&
Noch keine Standortdaten.
}

Due-Diligence Fokus

); } function InvestorHeroMetric({ label, value, accent = false }) { return
{label}
{value || '—'}
; } function InvestorMini({ label, value, dark = false }) { return
{label}
{value || '—'}
; } function TinyBars({ title, data = {} }) { const max = Math.max(1, ...Object.values(data || { x: 1 })); return
{title}
{Object.entries(data).map(([label, count]) =>
{label}{count}
)}
; } function LocationBar({ loc }) { return
{loc.city}{loc.count} · {loc.share}%
; } function InvestorChecklist({ items = [] }) { const risks = items.flatMap(item => (item.evaluation?.risks || item.evaluation?.key_risks || []).slice(0, 2)).slice(0, 5); const list = risks.length ? risks : ['Mietannahmen prüfen', 'Hausgeld/Nebenkosten verifizieren', 'Energieausweis und Sanierungsbedarf prüfen']; return
{list.map((text, i) =>
{i + 1}{text}
)}
; } function InvestorMobileCard({ item, rank, onOpen }) { const l = item.listing || {}; const e = item.evaluation || {}; const m = item.metrics || {}; const cash = m.monthly_cash_flow?.net_cash_flow; const risks = (e.risks || e.key_risks || []).slice(0, 2); return ; } function InvestorRow({ item, rank, onOpen, last }) { const l = item.listing || {}; const e = item.evaluation || {}; const m = item.metrics || {}; const cash = m.monthly_cash_flow?.net_cash_flow; const risk = (e.risks || e.key_risks || [])[0] || (m.vacancy_risk_pct ? `Leerstand ${fmtPct(m.vacancy_risk_pct)}` : 'Keine Hauptrisiken'); return ( ); } function InvestorCompareScreen({ ctx, active = 'investor', onNav = () => {}, ids = [], topBarProps = {} }) { const [payload, setPayload] = React.useState(null); React.useEffect(() => { ctx.loadInvestorCompare?.(ids).then(setPayload).catch(() => {}); }, [ids.join(',')]); const items = payload?.items || ctx.investor?.compare?.items || []; const content = (
ObjektPreisScoreBruttoCap Rate
{items.map((item, i) => )}
); if (!ctx.isDesktop) return
{content}
; return (
{content}
); } function DesktopProfileScreen({ ctx, active = 'profile', onNav = () => {}, topBarProps = {} }) { const user = ctx.user || {}; const inquiryRef = React.useRef(null); const [form, setForm] = React.useState(() => ({ name: user.name || '', email: user.email || '', telegram_chat_id: user.telegram_chat_id || '', ebay_userid: user.ebay_userid || '', ebay_contactname: user.ebay_contactname || '', ebay_email: user.ebay_email || '', ebay_password: '', default_reply_message: user.default_reply_message || '', rental_profile: { ...RENTAL_PROFILE_DEFAULTS, ...(user.rental_profile || {}) }, })); const [message, setMessage] = React.useState(null); const update = (key, value) => setForm(v => ({ ...v, [key]: value })); const updateRentalProfile = (key, value) => setForm(v => ({ ...v, rental_profile: { ...RENTAL_PROFILE_DEFAULTS, ...(v.rental_profile || {}), [key]: value }, })); const save = async () => { setMessage(null); await ctx.updateProfile?.(form); setMessage('Gespeichert'); update('ebay_password', ''); }; React.useEffect(() => { const shouldScroll = sessionStorage.getItem('immobotScrollInquiryProfile') === '1' || window.location.hash.includes('inquiry=1'); if (!shouldScroll) return; sessionStorage.removeItem('immobotScrollInquiryProfile'); setTimeout(() => inquiryRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }), 120); }, []); return (
} onClick={() => ctx.navigate('security')}>Sicherheit} onClick={ctx.signOut}>Abmelden} {...topBarProps} />
{user.initials || 'I'}

{user.name || 'ImmoBot Nutzer'}

{user.email}

Kontingent

Basisdaten

update('name', v)} /> update('email', v)} type="email" /> update('telegram_chat_id', v)} /> update('ebay_contactname', v)} /> update('ebay_userid', v)} /> update('ebay_email', v)} type="email" />
update('default_reply_message', v)} />

Anfrageprofil

{message &&
{message}
}
Profil speichern
); } function AccountSecurityScreen({ ctx, active = 'profile', onNav = () => {}, topBarProps = {} }) { const [form, setForm] = React.useState({ current_password: '', password: '', password_confirmation: '' }); const [deletePassword, setDeletePassword] = React.useState(''); const [message, setMessage] = React.useState(null); const update = (key, value) => setForm(v => ({ ...v, [key]: value })); const save = async () => { setMessage(null); await ctx.updatePassword?.(form); setMessage('Passwort aktualisiert.'); setForm({ current_password: '', password: '', password_confirmation: '' }); }; const remove = async () => { if (confirm('Konto wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.')) { await ctx.deleteProfile?.(deletePassword); } }; if (!ctx.isDesktop) { return (
update('current_password', v)} type="password" /> update('password', v)} type="password" /> update('password_confirmation', v)} type="password" />
); } return (

Passwort ändern

update('current_password', v)} /> update('password', v)} /> update('password_confirmation', v)} /> {message &&
{message}
} Passwort speichern

Konto löschen

Löscht das Konto, Suchen und gespeicherte Einstellungen dauerhaft.

} onClick={remove}>Konto löschen
); } Object.assign(window, { DesktopRouteScreen, InvestorScreen, InvestorCompareScreen, AccountSecurityScreen, fmtMetricPrice, fmtPct, });