Files
boris/src/App.tsx
Gigi f455f61795 build: deep import EventStore to bypass re-export resolution on Vercel
- Import EventStore from applesauce-core/dist/event-store/event-store.js
- Add TS module declaration shim for deep import typing
- No functional changes, fixes Vercel bundling for async-event-store.js
2025-10-07 06:15:48 +01:00

170 lines
5.2 KiB
TypeScript

import { useState, useEffect } from 'react'
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faSpinner } from '@fortawesome/free-solid-svg-icons'
import { EventStoreProvider, AccountsProvider, Hooks } from 'applesauce-react'
import { EventStore } from 'applesauce-core/dist/event-store/event-store.js'
import { AccountManager } from 'applesauce-accounts'
import { registerCommonAccountTypes } from 'applesauce-accounts/accounts'
import { RelayPool } from 'applesauce-relay'
import { createAddressLoader } from 'applesauce-loaders/loaders'
import Bookmarks from './components/Bookmarks'
import Toast from './components/Toast'
import { useToast } from './hooks/useToast'
import { RELAYS } from './config/relays'
const DEFAULT_ARTICLE = import.meta.env.VITE_DEFAULT_ARTICLE_NADDR ||
'naddr1qvzqqqr4gupzqmjxss3dld622uu8q25gywum9qtg4w4cv4064jmg20xsac2aam5nqqxnzd3cxqmrzv3exgmr2wfesgsmew'
// AppRoutes component that has access to hooks
function AppRoutes({
relayPool,
showToast
}: {
relayPool: RelayPool
showToast: (message: string) => void
}) {
const accountManager = Hooks.useAccountManager()
const handleLogout = () => {
accountManager.setActive(undefined as never)
localStorage.removeItem('active')
showToast('Logged out successfully')
}
return (
<Routes>
<Route
path="/a/:naddr"
element={
<Bookmarks
relayPool={relayPool}
onLogout={handleLogout}
/>
}
/>
<Route
path="/r/*"
element={
<Bookmarks
relayPool={relayPool}
onLogout={handleLogout}
/>
}
/>
<Route path="/" element={<Navigate to={`/a/${DEFAULT_ARTICLE}`} replace />} />
</Routes>
)
}
function App() {
const [eventStore, setEventStore] = useState<EventStore | null>(null)
const [accountManager, setAccountManager] = useState<AccountManager | null>(null)
const [relayPool, setRelayPool] = useState<RelayPool | null>(null)
const { toastMessage, toastType, showToast, clearToast } = useToast()
useEffect(() => {
const initializeApp = async () => {
// Initialize event store, account manager, and relay pool
const store = new EventStore()
const accounts = new AccountManager()
// Register common account types (needed for deserialization)
registerCommonAccountTypes(accounts)
// Load persisted accounts from localStorage
try {
const json = JSON.parse(localStorage.getItem('accounts') || '[]')
await accounts.fromJSON(json)
console.log('Loaded', accounts.accounts.length, 'accounts from storage')
// Load active account from storage
const activeId = localStorage.getItem('active')
if (activeId && accounts.getAccount(activeId)) {
accounts.setActive(activeId)
console.log('Restored active account:', activeId)
}
} catch (err) {
console.error('Failed to load accounts from storage:', err)
}
// Subscribe to accounts changes and persist to localStorage
const accountsSub = accounts.accounts$.subscribe(() => {
localStorage.setItem('accounts', JSON.stringify(accounts.toJSON()))
})
// Subscribe to active account changes and persist to localStorage
const activeSub = accounts.active$.subscribe((account) => {
if (account) {
localStorage.setItem('active', account.id)
} else {
localStorage.removeItem('active')
}
})
const pool = new RelayPool()
// Create a relay group for better event deduplication and management
pool.group(RELAYS)
console.log('Created relay group with', RELAYS.length, 'relays (including local)')
console.log('Relay URLs:', RELAYS)
// Attach address/replaceable loaders so ProfileModel can fetch profiles
const addressLoader = createAddressLoader(pool, {
eventStore: store,
lookupRelays: RELAYS
})
store.addressableLoader = addressLoader
store.replaceableLoader = addressLoader
setEventStore(store)
setAccountManager(accounts)
setRelayPool(pool)
// Cleanup function
return () => {
accountsSub.unsubscribe()
activeSub.unsubscribe()
}
}
let cleanup: (() => void) | undefined
initializeApp().then((fn) => {
cleanup = fn
})
return () => {
if (cleanup) cleanup()
}
}, [])
if (!eventStore || !accountManager || !relayPool) {
return (
<div className="loading">
<FontAwesomeIcon icon={faSpinner} spin />
</div>
)
}
return (
<EventStoreProvider eventStore={eventStore}>
<AccountsProvider manager={accountManager}>
<BrowserRouter>
<div className="app">
<AppRoutes relayPool={relayPool} showToast={showToast} />
</div>
</BrowserRouter>
{toastMessage && (
<Toast
message={toastMessage}
type={toastType}
onClose={clearToast}
/>
)}
</AccountsProvider>
</EventStoreProvider>
)
}
export default App