fix(lint): resolve all linting and type errors

- Remove unused imports (useRef, faExclamationCircle, getProfileUrl, Observable, UserSettings)
- Remove unused error state and setError calls in Explore and Me components
- Remove unused 'events' variable from exploreService and nostrverseService
- Remove unused '_relays' parameter from saveSettings
- Remove unused '_settings' parameter from publishEvent
- Update all callers of publishEvent and saveSettings to match new signatures
- Add eslint-disable comment for intentional dependency omission in Explore
- Update BookmarkList to use new pull-to-refresh library and RefreshIndicator
- All type checks and linting now pass
This commit is contained in:
Gigi
2025-10-15 09:42:56 +02:00
parent a4548306e7
commit 2939747ebf
11 changed files with 29 additions and 51 deletions

View File

@@ -10,8 +10,8 @@ import IconButton from './IconButton'
import { ViewMode } from './Bookmarks' import { ViewMode } from './Bookmarks'
import { extractUrlsFromContent } from '../services/bookmarkHelpers' import { extractUrlsFromContent } from '../services/bookmarkHelpers'
import { UserSettings } from '../services/settingsService' import { UserSettings } from '../services/settingsService'
import { usePullToRefresh } from '../hooks/usePullToRefresh' import { usePullToRefresh } from 'use-pull-to-refresh'
import PullToRefreshIndicator from './PullToRefreshIndicator' import RefreshIndicator from './RefreshIndicator'
import { BookmarkSkeleton } from './Skeletons' import { BookmarkSkeleton } from './Skeletons'
interface BookmarkListProps { interface BookmarkListProps {
@@ -54,14 +54,15 @@ export const BookmarkList: React.FC<BookmarkListProps> = ({
const bookmarksListRef = useRef<HTMLDivElement>(null) const bookmarksListRef = useRef<HTMLDivElement>(null)
// Pull-to-refresh for bookmarks // Pull-to-refresh for bookmarks
const pullToRefreshState = usePullToRefresh(bookmarksListRef, { const { isRefreshing: isPulling, pullPosition } = usePullToRefresh({
onRefresh: () => { onRefresh: () => {
if (onRefresh) { if (onRefresh) {
onRefresh() onRefresh()
} }
}, },
isRefreshing: isRefreshing || false, maximumPullLength: 240,
disabled: !onRefresh refreshThreshold: 80,
isDisabled: !onRefresh
}) })
// Helper to check if a bookmark has either content or a URL // Helper to check if a bookmark has either content or a URL
@@ -146,13 +147,11 @@ export const BookmarkList: React.FC<BookmarkListProps> = ({
) : ( ) : (
<div <div
ref={bookmarksListRef} ref={bookmarksListRef}
className={`bookmarks-list pull-to-refresh-container ${pullToRefreshState.isPulling ? 'is-pulling' : ''}`} className="bookmarks-list"
> >
<PullToRefreshIndicator <RefreshIndicator
isPulling={pullToRefreshState.isPulling} isRefreshing={isPulling || isRefreshing || false}
pullDistance={pullToRefreshState.pullDistance} pullPosition={pullPosition}
canRefresh={pullToRefreshState.canRefresh}
isRefreshing={isRefreshing || false}
/> />
<div className={`bookmarks-grid bookmarks-${viewMode}`}> <div className={`bookmarks-grid bookmarks-${viewMode}`}>
{allIndividualBookmarks.map((individualBookmark, index) => {allIndividualBookmarks.map((individualBookmark, index) =>

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef, useMemo } from 'react' import React, { useState, useEffect, useMemo } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faExclamationCircle, faNewspaper, faPenToSquare, faHighlighter, faUser, faUserGroup, faNetworkWired } from '@fortawesome/free-solid-svg-icons' import { faNewspaper, faPenToSquare, faHighlighter, faUser, faUserGroup, faNetworkWired } from '@fortawesome/free-solid-svg-icons'
import IconButton from './IconButton' import IconButton from './IconButton'
import { BlogPostSkeleton, HighlightSkeleton } from './Skeletons' import { BlogPostSkeleton, HighlightSkeleton } from './Skeletons'
import { Hooks } from 'applesauce-react' import { Hooks } from 'applesauce-react'
@@ -40,7 +40,6 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
const [highlights, setHighlights] = useState<Highlight[]>([]) const [highlights, setHighlights] = useState<Highlight[]>([])
const [followedPubkeys, setFollowedPubkeys] = useState<Set<string>>(new Set()) const [followedPubkeys, setFollowedPubkeys] = useState<Set<string>>(new Set())
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [refreshTrigger, setRefreshTrigger] = useState(0) const [refreshTrigger, setRefreshTrigger] = useState(0)
// Visibility filters (defaults from settings) // Visibility filters (defaults from settings)
@@ -60,7 +59,6 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
useEffect(() => { useEffect(() => {
const loadData = async () => { const loadData = async () => {
if (!activeAccount) { if (!activeAccount) {
setError('Please log in to explore content from your friends')
setLoading(false) setLoading(false)
return return
} }
@@ -68,7 +66,6 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
try { try {
// show spinner but keep existing data // show spinner but keep existing data
setLoading(true) setLoading(true)
setError(null)
// Seed from in-memory cache if available to avoid empty flash // Seed from in-memory cache if available to avoid empty flash
const cachedPosts = getCachedPosts(activeAccount.pubkey) const cachedPosts = getCachedPosts(activeAccount.pubkey)
@@ -150,15 +147,8 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
} }
) )
if (contacts.size === 0) { // Always proceed to load nostrverse content even if no contacts
// If we already have any cached or previously shown data, do not block the UI. // (removed blocking error for empty contacts)
const hasAnyData = (blogPosts.length > 0) || (highlights.length > 0)
if (!hasAnyData) {
// No friends and no cached content: set a soft hint, but still proceed to load nostrverse.
setError(null)
}
// Continue without returning: still fetch nostrverse content below.
}
// Store final followed pubkeys // Store final followed pubkeys
setFollowedPubkeys(contacts) setFollowedPubkeys(contacts)
@@ -205,12 +195,7 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
}) })
} }
if (contacts.size === 0 && uniquePosts.length === 0 && uniqueHighlights.length === 0) { // No blocking errors - let empty states handle messaging
setError('You are not following anyone yet. Follow some people to see their content!')
} else if (uniquePosts.length === 0 && uniqueHighlights.length === 0) {
setError('No content found yet')
}
setBlogPosts(uniquePosts) setBlogPosts(uniquePosts)
setCachedPosts(activeAccount.pubkey, uniquePosts) setCachedPosts(activeAccount.pubkey, uniquePosts)
@@ -218,13 +203,15 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
setCachedHighlights(activeAccount.pubkey, uniqueHighlights) setCachedHighlights(activeAccount.pubkey, uniqueHighlights)
} catch (err) { } catch (err) {
console.error('Failed to load data:', err) console.error('Failed to load data:', err)
setError('Failed to load content. Please try again.') // No blocking error - user can pull-to-refresh
} finally { } finally {
setLoading(false) setLoading(false)
} }
} }
loadData() loadData()
// Note: intentionally not including blogPosts/highlights length to avoid re-fetch loops
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [relayPool, activeAccount, refreshTrigger, eventStore, settings]) }, [relayPool, activeAccount, refreshTrigger, eventStore, settings])
// Pull-to-refresh // Pull-to-refresh

View File

@@ -1,4 +1,4 @@
import React, { useState, useRef } from 'react' import React, { useState } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faHighlighter } from '@fortawesome/free-solid-svg-icons' import { faHighlighter } from '@fortawesome/free-solid-svg-icons'
import { Highlight } from '../types/highlights' import { Highlight } from '../types/highlights'

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef } from 'react' import React, { useState, useEffect } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faSpinner, faExclamationCircle, faHighlighter, faBookmark, faList, faThLarge, faImage, faPenToSquare } from '@fortawesome/free-solid-svg-icons' import { faSpinner, faHighlighter, faBookmark, faList, faThLarge, faImage, faPenToSquare } from '@fortawesome/free-solid-svg-icons'
import { Hooks } from 'applesauce-react' import { Hooks } from 'applesauce-react'
import { BlogPostSkeleton, HighlightSkeleton, BookmarkSkeleton } from './Skeletons' import { BlogPostSkeleton, HighlightSkeleton, BookmarkSkeleton } from './Skeletons'
import { RelayPool } from 'applesauce-relay' import { RelayPool } from 'applesauce-relay'
@@ -24,7 +24,6 @@ import { getCachedMeData, setCachedMeData, updateCachedHighlights } from '../ser
import { faBooks } from '../icons/customIcons' import { faBooks } from '../icons/customIcons'
import { usePullToRefresh } from 'use-pull-to-refresh' import { usePullToRefresh } from 'use-pull-to-refresh'
import RefreshIndicator from './RefreshIndicator' import RefreshIndicator from './RefreshIndicator'
import { getProfileUrl } from '../config/nostrGateways'
interface MeProps { interface MeProps {
relayPool: RelayPool relayPool: RelayPool
@@ -47,7 +46,6 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
const [readArticles, setReadArticles] = useState<BlogPostPreview[]>([]) const [readArticles, setReadArticles] = useState<BlogPostPreview[]>([])
const [writings, setWritings] = useState<BlogPostPreview[]>([]) const [writings, setWritings] = useState<BlogPostPreview[]>([])
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [viewMode, setViewMode] = useState<ViewMode>('cards') const [viewMode, setViewMode] = useState<ViewMode>('cards')
const [refreshTrigger, setRefreshTrigger] = useState(0) const [refreshTrigger, setRefreshTrigger] = useState(0)
@@ -61,14 +59,12 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
useEffect(() => { useEffect(() => {
const loadData = async () => { const loadData = async () => {
if (!viewingPubkey) { if (!viewingPubkey) {
setError(isOwnProfile ? 'Please log in to view your data' : 'Invalid profile')
setLoading(false) setLoading(false)
return return
} }
try { try {
setLoading(true) setLoading(true)
setError(null)
// Seed from cache if available to avoid empty flash (own profile only) // Seed from cache if available to avoid empty flash (own profile only)
if (isOwnProfile) { if (isOwnProfile) {
@@ -114,7 +110,7 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
} }
} catch (err) { } catch (err) {
console.error('Failed to load data:', err) console.error('Failed to load data:', err)
setError('Failed to load data. Please try again.') // No blocking error - user can pull-to-refresh
} finally { } finally {
setLoading(false) setLoading(false)
} }

View File

@@ -85,7 +85,7 @@ export function useSettings({ relayPool, eventStore, pubkey, accountManager }: U
const fullAccount = accountManager.getActive() const fullAccount = accountManager.getActive()
if (!fullAccount) throw new Error('No active account') if (!fullAccount) throw new Error('No active account')
const factory = new EventFactory({ signer: fullAccount }) const factory = new EventFactory({ signer: fullAccount })
await saveSettings(relayPool, eventStore, factory, newSettings, RELAYS) await saveSettings(relayPool, eventStore, factory, newSettings)
setSettings(newSettings) setSettings(newSettings)
setToastType('success') setToastType('success')
setToastMessage('Settings saved') setToastMessage('Settings saved')

View File

@@ -1,5 +1,4 @@
import { RelayPool } from 'applesauce-relay' import { RelayPool } from 'applesauce-relay'
import { Observable } from 'rxjs'
import { prioritizeLocalRelays } from '../utils/helpers' import { prioritizeLocalRelays } from '../utils/helpers'
import { queryEvents } from './dataFetch' import { queryEvents } from './dataFetch'
import { CONTACTS_REMOTE_TIMEOUT_MS } from '../config/network' import { CONTACTS_REMOTE_TIMEOUT_MS } from '../config/network'

View File

@@ -39,7 +39,7 @@ export const fetchBlogPostsFromAuthors = async (
// Group by author + d-tag identifier // Group by author + d-tag identifier
const uniqueEvents = new Map<string, NostrEvent>() const uniqueEvents = new Map<string, NostrEvent>()
const events = await queryEvents( await queryEvents(
relayPool, relayPool,
{ kinds: [30023], authors: pubkeys, limit: 100 }, { kinds: [30023], authors: pubkeys, limit: 100 },
{ {

View File

@@ -119,7 +119,7 @@ export async function createHighlight(
const signedEvent = await factory.sign(highlightEvent) const signedEvent = await factory.sign(highlightEvent)
// Use unified write service to store and publish // Use unified write service to store and publish
await publishEvent(relayPool, eventStore, signedEvent, settings) await publishEvent(relayPool, eventStore, signedEvent)
// Check current connection status for UI feedback // Check current connection status for UI feedback
const connectedRelays = Array.from(relayPool.relays.values()) const connectedRelays = Array.from(relayPool.relays.values())

View File

@@ -26,7 +26,7 @@ export const fetchNostrverseBlogPosts = async (
// Deduplicate replaceable events by keeping the most recent version // Deduplicate replaceable events by keeping the most recent version
const uniqueEvents = new Map<string, NostrEvent>() const uniqueEvents = new Map<string, NostrEvent>()
const events = await queryEvents( await queryEvents(
relayPool, relayPool,
{ kinds: [30023], limit }, { kinds: [30023], limit },
{ {

View File

@@ -148,8 +148,7 @@ export async function saveSettings(
relayPool: RelayPool, relayPool: RelayPool,
eventStore: IEventStore, eventStore: IEventStore,
factory: EventFactory, factory: EventFactory,
settings: UserSettings, settings: UserSettings
_relays: string[]
): Promise<void> { ): Promise<void> {
console.log('💾 Saving settings to nostr:', settings) console.log('💾 Saving settings to nostr:', settings)
@@ -165,7 +164,7 @@ export async function saveSettings(
const signed = await factory.sign(draft) const signed = await factory.sign(draft)
// Use unified write service // Use unified write service
await publishEvent(relayPool, eventStore, signed, settings) await publishEvent(relayPool, eventStore, signed)
console.log('✅ Settings published successfully') console.log('✅ Settings published successfully')
} }

View File

@@ -1,7 +1,6 @@
import { RelayPool } from 'applesauce-relay' import { RelayPool } from 'applesauce-relay'
import { NostrEvent } from 'nostr-tools' import { NostrEvent } from 'nostr-tools'
import { IEventStore } from 'applesauce-core' import { IEventStore } from 'applesauce-core'
import { UserSettings } from './settingsService'
import { RELAYS } from '../config/relays' import { RELAYS } from '../config/relays'
import { isLocalRelay, areAllRelaysLocal } from '../utils/helpers' import { isLocalRelay, areAllRelaysLocal } from '../utils/helpers'
import { markEventAsOfflineCreated } from './offlineSyncService' import { markEventAsOfflineCreated } from './offlineSyncService'
@@ -13,8 +12,7 @@ import { markEventAsOfflineCreated } from './offlineSyncService'
export async function publishEvent( export async function publishEvent(
relayPool: RelayPool, relayPool: RelayPool,
eventStore: IEventStore, eventStore: IEventStore,
event: NostrEvent, event: NostrEvent
settings?: UserSettings
): Promise<void> { ): Promise<void> {
// Store the event in the local EventStore FIRST for immediate UI display // Store the event in the local EventStore FIRST for immediate UI display
eventStore.add(event) eventStore.add(event)