From 4b9a4fb286159ca7d92c9d3f548ef7b3a8720016 Mon Sep 17 00:00:00 2001 From: Gigi Date: Sun, 5 Oct 2025 04:01:09 +0100 Subject: [PATCH] refactor: extract settings logic into custom hook - Create useSettings hook to handle settings loading, saving, and watching - Move settings-related logic out of Bookmarks component - Move font loading logic into the hook - Reduce Bookmarks.tsx from 221 to 167 lines (under 210 limit) - Keep code DRY by centralizing settings management - All lint and type checks pass --- src/components/Bookmarks.tsx | 83 +++++++----------------------------- src/hooks/useSettings.ts | 81 +++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 68 deletions(-) create mode 100644 src/hooks/useSettings.ts diff --git a/src/components/Bookmarks.tsx b/src/components/Bookmarks.tsx index 6a4c5acf..a5697ca9 100644 --- a/src/components/Bookmarks.tsx +++ b/src/components/Bookmarks.tsx @@ -1,8 +1,7 @@ -import React, { useState, useEffect, useCallback } from 'react' +import React, { useState, useEffect } from 'react' import { Hooks } from 'applesauce-react' import { useEventStore } from 'applesauce-react/hooks' import { RelayPool } from 'applesauce-relay' -import { EventFactory } from 'applesauce-factory' import { Bookmark } from '../types/bookmarks' import { Highlight } from '../types/highlights' import { BookmarkList } from './BookmarkList' @@ -12,9 +11,8 @@ import ContentPanel from './ContentPanel' import { HighlightsPanel } from './HighlightsPanel' import { fetchReadableContent, ReadableContent } from '../services/readerService' import Settings from './Settings' -import { UserSettings, loadSettings, saveSettings, watchSettings } from '../services/settingsService' -import { loadFont, getFontFamily } from '../utils/fontLoader' import Toast from './Toast' +import { useSettings } from '../hooks/useSettings' export type ViewMode = 'compact' | 'cards' | 'large' interface BookmarksProps { @@ -22,11 +20,6 @@ interface BookmarksProps { onLogout: () => void } -const RELAY_URLS = [ - 'wss://relay.damus.io', 'wss://nos.lol', 'wss://relay.nostr.band', - 'wss://relay.dergigi.com', 'wss://wot.dergigi.com' -] - const Bookmarks: React.FC = ({ relayPool, onLogout }) => { const [bookmarks, setBookmarks] = useState([]) const [highlights, setHighlights] = useState([]) @@ -40,47 +33,30 @@ const Bookmarks: React.FC = ({ relayPool, onLogout }) => { const [showUnderlines, setShowUnderlines] = useState(true) const [selectedHighlightId, setSelectedHighlightId] = useState(undefined) const [showSettings, setShowSettings] = useState(false) - const [settings, setSettings] = useState({}) - const [toastMessage, setToastMessage] = useState(null) - const [toastType, setToastType] = useState<'success' | 'error'>('success') const activeAccount = Hooks.useActiveAccount() const accountManager = Hooks.useAccountManager() const eventStore = useEventStore() + + const { settings, saveSettings, toastMessage, toastType, clearToast } = useSettings({ + relayPool, + eventStore, + pubkey: activeAccount?.pubkey, + accountManager + }) - // Load initial data and set up settings subscription on login + // Load initial data on login useEffect(() => { - if (!relayPool || !activeAccount || !eventStore) return - - // Fetch bookmarks and highlights + if (!relayPool || !activeAccount) return handleFetchBookmarks() handleFetchHighlights() + }, [relayPool, activeAccount?.pubkey]) - // Load settings from Nostr and set up live subscription - handleLoadSettings() - - const subscription = watchSettings(eventStore, activeAccount.pubkey, (loadedSettings) => { - if (loadedSettings) { - setSettings(loadedSettings) - } - }) - - return () => { - subscription.unsubscribe() - } - }, [relayPool, activeAccount?.pubkey, eventStore]) - + // Apply UI settings useEffect(() => { - const root = document.documentElement.style if (settings.defaultViewMode) setViewMode(settings.defaultViewMode) if (settings.showUnderlines !== undefined) setShowUnderlines(settings.showUnderlines) if (settings.sidebarCollapsed !== undefined) setIsCollapsed(settings.sidebarCollapsed) if (settings.highlightsCollapsed !== undefined) setIsHighlightsCollapsed(settings.highlightsCollapsed) - - // Always set font variables (use defaults if not specified) - const fontKey = settings.readingFont || 'system' - if (fontKey !== 'system') loadFont(fontKey) - root.setProperty('--reading-font', getFontFamily(fontKey)) - root.setProperty('--reading-font-size', `${settings.fontSize || 16}px`) }, [settings]) const handleFetchBookmarks = async () => { @@ -102,35 +78,6 @@ const Bookmarks: React.FC = ({ relayPool, onLogout }) => { } } - const handleLoadSettings = async () => { - if (!relayPool || !activeAccount) return - try { - const loadedSettings = await loadSettings(relayPool, eventStore, activeAccount.pubkey, RELAY_URLS) - if (loadedSettings) { - setSettings(loadedSettings) - } - } catch (err) { - console.error('Failed to load settings:', err) - } - } - - const handleSaveSettings = useCallback(async (newSettings: UserSettings) => { - if (!relayPool || !activeAccount) return - try { - const fullAccount = accountManager.getActive() - if (!fullAccount) throw new Error('No active account') - const factory = new EventFactory({ signer: fullAccount }) - await saveSettings(relayPool, eventStore, factory, newSettings, RELAY_URLS) - setSettings(newSettings) - setToastType('success') - setToastMessage('Settings saved') - } catch (err) { - console.error('Failed to save settings:', err) - setToastType('error') - setToastMessage('Failed to save settings') - } - }, [relayPool, activeAccount, accountManager, eventStore]) - const handleSelectUrl = async (url: string) => { setSelectedUrl(url) setReaderLoading(true) @@ -171,7 +118,7 @@ const Bookmarks: React.FC = ({ relayPool, onLogout }) => { {showSettings ? ( setShowSettings(false)} /> ) : ( @@ -210,7 +157,7 @@ const Bookmarks: React.FC = ({ relayPool, onLogout }) => { setToastMessage(null)} + onClose={clearToast} /> )} diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts new file mode 100644 index 00000000..219fb7c2 --- /dev/null +++ b/src/hooks/useSettings.ts @@ -0,0 +1,81 @@ +import { useState, useEffect, useCallback } from 'react' +import { IEventStore } from 'applesauce-core' +import { RelayPool } from 'applesauce-relay' +import { EventFactory } from 'applesauce-factory' +import { AccountManager } from 'applesauce-accounts' +import { UserSettings, loadSettings, saveSettings, watchSettings } from '../services/settingsService' +import { loadFont, getFontFamily } from '../utils/fontLoader' + +const RELAY_URLS = [ + 'wss://relay.damus.io', 'wss://nos.lol', 'wss://relay.nostr.band', + 'wss://relay.dergigi.com', 'wss://wot.dergigi.com' +] + +interface UseSettingsParams { + relayPool: RelayPool | null + eventStore: IEventStore + pubkey: string | undefined + accountManager: AccountManager +} + +export function useSettings({ relayPool, eventStore, pubkey, accountManager }: UseSettingsParams) { + const [settings, setSettings] = useState({}) + const [toastMessage, setToastMessage] = useState(null) + const [toastType, setToastType] = useState<'success' | 'error'>('success') + + // Load settings and set up subscription + useEffect(() => { + if (!relayPool || !pubkey || !eventStore) return + + const loadAndWatch = async () => { + try { + const loadedSettings = await loadSettings(relayPool, eventStore, pubkey, RELAY_URLS) + if (loadedSettings) setSettings(loadedSettings) + } catch (err) { + console.error('Failed to load settings:', err) + } + } + + loadAndWatch() + + const subscription = watchSettings(eventStore, pubkey, (loadedSettings) => { + if (loadedSettings) setSettings(loadedSettings) + }) + + return () => subscription.unsubscribe() + }, [relayPool, pubkey, eventStore]) + + // Apply settings to document + useEffect(() => { + const root = document.documentElement.style + const fontKey = settings.readingFont || 'system' + if (fontKey !== 'system') loadFont(fontKey) + root.setProperty('--reading-font', getFontFamily(fontKey)) + root.setProperty('--reading-font-size', `${settings.fontSize || 16}px`) + }, [settings]) + + const saveSettingsWithToast = useCallback(async (newSettings: UserSettings) => { + if (!relayPool || !pubkey) return + try { + const fullAccount = accountManager.getActive() + if (!fullAccount) throw new Error('No active account') + const factory = new EventFactory({ signer: fullAccount }) + await saveSettings(relayPool, eventStore, factory, newSettings, RELAY_URLS) + setSettings(newSettings) + setToastType('success') + setToastMessage('Settings saved') + } catch (err) { + console.error('Failed to save settings:', err) + setToastType('error') + setToastMessage('Failed to save settings') + } + }, [relayPool, pubkey, accountManager, eventStore]) + + return { + settings, + saveSettings: saveSettingsWithToast, + toastMessage, + toastType, + clearToast: () => setToastMessage(null) + } +}