You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
146 lines
4.4 KiB
146 lines
4.4 KiB
import { Skeleton } from '@/components/ui/skeleton' |
|
import { useFetchRelayInfo } from '@/hooks' |
|
import { toRelay } from '@/lib/link' |
|
import { useSmartRelayNavigation } from '@/PageManager' |
|
import relayInfoService from '@/services/relay-info.service' |
|
import { TAwesomeRelayCollection } from '@/types' |
|
import { useEffect, useState } from 'react' |
|
import RelaySimpleInfo, { RelaySimpleInfoSkeleton } from '../RelaySimpleInfo' |
|
import logger from '@/lib/logger' |
|
|
|
export default function Explore() { |
|
const [collections, setCollections] = useState<TAwesomeRelayCollection[] | null>(null) |
|
const [error, setError] = useState<string | null>(null) |
|
|
|
useEffect(() => { |
|
let cancelled = false |
|
let timeoutId: ReturnType<typeof setTimeout> | null = null |
|
|
|
// Add timeout to prevent hanging forever |
|
timeoutId = setTimeout(() => { |
|
if (!cancelled) { |
|
logger.warn('[Explore] Timeout loading relay collections after 10 seconds') |
|
setError('Timeout loading relay collections') |
|
setCollections([]) // Set empty array to stop showing skeletons |
|
} |
|
}, 10000) // 10 second timeout |
|
|
|
logger.debug('[Explore] Fetching awesome relay collections') |
|
relayInfoService.getAwesomeRelayCollections() |
|
.then((data) => { |
|
if (!cancelled) { |
|
if (timeoutId) clearTimeout(timeoutId) |
|
logger.debug('[Explore] Loaded collections', { count: data?.length || 0 }) |
|
setCollections(data || []) |
|
} |
|
}) |
|
.catch((err) => { |
|
if (!cancelled) { |
|
if (timeoutId) clearTimeout(timeoutId) |
|
logger.error('[Explore] Error loading collections', { error: err }) |
|
setError(err instanceof Error ? err.message : 'Failed to load relay collections') |
|
setCollections([]) // Set empty array to stop showing skeletons |
|
} |
|
}) |
|
|
|
return () => { |
|
cancelled = true |
|
if (timeoutId) clearTimeout(timeoutId) |
|
} |
|
}, []) |
|
|
|
if (collections === null) { |
|
return ( |
|
<div> |
|
<div className="p-4 max-md:border-b"> |
|
<Skeleton className="h-6 w-20" /> |
|
</div> |
|
<div className="grid md:px-4 md:grid-cols-2 md:gap-2"> |
|
<RelaySimpleInfoSkeleton className="h-auto px-4 py-3 md:rounded-lg md:border" /> |
|
</div> |
|
</div> |
|
) |
|
} |
|
|
|
if (error) { |
|
return ( |
|
<div className="p-4"> |
|
<div className="text-red-500 mb-2">Error: {error}</div> |
|
<button |
|
onClick={() => { |
|
setCollections(null) |
|
setError(null) |
|
// Trigger reload |
|
relayInfoService.getAwesomeRelayCollections() |
|
.then(setCollections) |
|
.catch((err) => { |
|
setError(err instanceof Error ? err.message : 'Failed to load') |
|
setCollections([]) |
|
}) |
|
}} |
|
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" |
|
> |
|
Retry |
|
</button> |
|
</div> |
|
) |
|
} |
|
|
|
if (collections.length === 0) { |
|
return ( |
|
<div className="p-4 text-center text-muted-foreground"> |
|
No relay collections available |
|
</div> |
|
) |
|
} |
|
|
|
return ( |
|
<div className="min-w-0 w-full overflow-x-hidden space-y-6 pb-8"> |
|
{collections.map((collection) => ( |
|
<RelayCollection key={collection.id} collection={collection} /> |
|
))} |
|
</div> |
|
) |
|
} |
|
|
|
function RelayCollection({ collection }: { collection: TAwesomeRelayCollection }) { |
|
return ( |
|
<div className="min-w-0"> |
|
<div className="px-4 pt-3 pb-3.5 text-2xl font-semibold max-md:border-b min-w-0 break-words"> |
|
{collection.name} |
|
</div> |
|
<div className="grid min-w-0 md:px-4 md:grid-cols-2 md:gap-3"> |
|
{collection.relays.map((url) => ( |
|
<RelayItem key={url} url={url} /> |
|
))} |
|
</div> |
|
</div> |
|
) |
|
} |
|
|
|
function RelayItem({ url }: { url: string }) { |
|
const { navigateToRelay } = useSmartRelayNavigation() |
|
const { relayInfo, isFetching } = useFetchRelayInfo(url) |
|
|
|
if (isFetching) { |
|
return <RelaySimpleInfoSkeleton className="h-auto px-4 py-3 border-b md:rounded-lg md:border" /> |
|
} |
|
|
|
if (!relayInfo) { |
|
return null |
|
} |
|
|
|
return ( |
|
<div className="min-w-0"> |
|
<RelaySimpleInfo |
|
key={relayInfo.url} |
|
className="clickable h-auto px-4 py-3 border-b md:rounded-lg md:border min-w-0" |
|
relayInfo={relayInfo} |
|
onClick={(e) => { |
|
e.stopPropagation() |
|
navigateToRelay(toRelay(relayInfo.url)) |
|
}} |
|
/> |
|
</div> |
|
) |
|
}
|
|
|