import { kinds, nip19 } from 'nostr-tools' import { useMemo, useState } from 'react' import { Link, useNavigate, useNavigation } from 'react-router-dom' import { A11y, Autoplay, Navigation, Pagination } from 'swiper/modules' import { Swiper, SwiperSlide } from 'swiper/react' import { BlogCard } from '../components/BlogCard' import { GameCard } from '../components/GameCard' import { ModCard } from '../components/ModCard' import { LANDING_PAGE_DATA, PROFILE_BLOG_FILTER_LIMIT } from '../constants' import { useAppSelector, useDidMount, useGames, useLocalStorage, useMuteLists, useNDKContext, useNSFWList, useRepostList } from '../hooks' import { appRoutes, getModPageRoute } from '../routes' import { BlogCardDetails, ModDetails, NSFWFilter, SortBy } from '../types' import { extractBlogCardDetails, extractModData, handleModImageError, log, LogType, npubToHex } from '../utils' import '../styles/cardLists.css' import '../styles/SimpleSlider.css' import '../styles/styles.css' // Import Swiper styles import { NDKFilter } from '@nostr-dev-kit/ndk' import 'swiper/css' import 'swiper/css/navigation' import 'swiper/css/pagination' import { LoadingSpinner } from 'components/LoadingSpinner' import { Spinner } from 'components/Spinner' import { isInWoT } from 'utils/wot' export const HomePage = () => { const navigate = useNavigate() const games = useGames() const featuredGames = useMemo(() => { return games.filter((game) => LANDING_PAGE_DATA.featuredGames.includes(game['Game Name']) ) }, [games]) return (
{LANDING_PAGE_DATA.featuredSlider.map((naddr) => ( ))}

Cool Games

{featuredGames.map((game) => ( ))}
navigate(appRoutes.games)} > View All

Awesome Mods

{LANDING_PAGE_DATA.awesomeMods.map((naddr) => ( ))}
navigate(appRoutes.mods)} > View All
) } type SlideContentProps = { naddr: string } const SlideContent = ({ naddr }: SlideContentProps) => { const navigate = useNavigate() const { fetchEvent } = useNDKContext() const [mod, setMod] = useState() useDidMount(() => { const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`) const { identifier, kind, pubkey } = decoded.data const ndkFilter: NDKFilter = { '#a': [identifier], authors: [pubkey], kinds: [kind] } fetchEvent(ndkFilter) .then((ndkEvent) => { if (ndkEvent) { const extracted = extractModData(ndkEvent) setMod(extracted) } }) .catch((err) => { log( true, LogType.Error, 'An error occurred in fetching mod details from relays', err ) }) }) if (!mod) return return ( <>

{mod.title}

{mod.summary}

{mod.game}

navigate(getModPageRoute(naddr))} > Check it out
) } type DisplayModProps = { naddr: string } const DisplayMod = ({ naddr }: DisplayModProps) => { const [mod, setMod] = useState() const { fetchEvent } = useNDKContext() useDidMount(() => { const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`) const { identifier, kind, pubkey } = decoded.data const ndkFilter: NDKFilter = { '#a': [identifier], authors: [pubkey], kinds: [kind] } fetchEvent(ndkFilter) .then((ndkEvent) => { if (ndkEvent) { const extracted = extractModData(ndkEvent) setMod(extracted) } }) .catch((err) => { log( true, LogType.Error, 'An error occurred in fetching mod details from relays', err ) }) }) if (!mod) return return } const DisplayLatestMods = () => { const navigate = useNavigate() const { fetchMods } = useNDKContext() const { siteWot, siteWotLevel, userWot, userWotLevel } = useAppSelector( (state) => state.wot ) const [isFetchingLatestMods, setIsFetchingLatestMods] = useState(true) const [latestMods, setLatestMods] = useState([]) const muteLists = useMuteLists() const nsfwList = useNSFWList() const repostList = useRepostList() useDidMount(() => { fetchMods({ source: window.location.host }) .then((mods) => { // Sort by the latest (published_at descending) mods.sort((a, b) => b.published_at - a.published_at) setLatestMods(mods) }) .finally(() => { setIsFetchingLatestMods(false) }) }) const filteredMods = useMemo(() => { const mutedAuthors = [...muteLists.admin.authors, ...muteLists.user.authors] const mutedEvents = [ ...muteLists.admin.replaceableEvents, ...muteLists.user.replaceableEvents ] const filtered = latestMods.filter( (mod) => !mutedAuthors.includes(mod.author) && !mutedEvents.includes(mod.aTag) && !nsfwList.includes(mod.aTag) && !mod.nsfw && isInWoT(siteWot, siteWotLevel, mod.author) && isInWoT(userWot, userWotLevel, mod.author) ) // Add repost tag if missing for (let i = 0; i < filtered.length; i++) { const mod = filtered[i] const isMissingRepostTag = !mod.repost && mod.aTag && repostList.includes(mod.aTag) if (isMissingRepostTag) { mod.repost = true } } return filtered.slice(0, 4) }, [ latestMods, muteLists.admin.authors, muteLists.admin.replaceableEvents, muteLists.user.authors, muteLists.user.replaceableEvents, nsfwList, repostList, siteWot, siteWotLevel, userWot, userWotLevel ]) return (

Latest Mods

{isFetchingLatestMods ? ( ) : ( filteredMods.map((mod) => { return }) )}
) } const DisplayLatestBlogs = () => { const [blogs, setBlogs] = useState[]>() const { fetchEvents } = useNDKContext() const [filterOptions] = useLocalStorage('filter-blog-curated', { sort: SortBy.Latest, nsfw: NSFWFilter.Hide_NSFW }) const navigation = useNavigation() useDidMount(() => { const fetchBlogs = async () => { try { // Show maximum of 4 blog posts // 2 should be featured and the most recent 2 from blog npubs // Populate the filter from known naddr (constants.ts) const filter: NDKFilter = { kinds: [kinds.LongFormArticle], authors: [], '#d': [] } for (let i = 0; i < LANDING_PAGE_DATA.featuredBlogPosts.length; i++) { try { const naddr = LANDING_PAGE_DATA.featuredBlogPosts[i] const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`) const { pubkey, identifier } = decoded.data if (!filter.authors?.includes(pubkey)) { filter.authors?.push(pubkey) } if (!filter.authors?.includes(identifier)) { filter['#d']?.push(identifier) } } catch (error) { // Silently ignore } } // Prepare filter for the latest const blogNpubs = import.meta.env.VITE_BLOG_NPUBS.split(',') const blogHexkeys = blogNpubs .map(npubToHex) .filter((hexkey) => hexkey !== null) // We fetch more posts in case of duplicates (from featured) const latestFilter: NDKFilter = { authors: blogHexkeys, kinds: [kinds.LongFormArticle], limit: PROFILE_BLOG_FILTER_LIMIT } // Filter by NSFW tag if (filterOptions.nsfw === NSFWFilter.Only_NSFW) { latestFilter['#L'] = ['content-warning'] } const results = await Promise.allSettled([ fetchEvents(filter), fetchEvents(latestFilter) ]) const events: Partial[] = [] // Get featured blogs posts result results.forEach((r) => { // Add events from both promises to the array if (r.status === 'fulfilled' && r.value) { events.push( ...r.value .map(extractBlogCardDetails) // Extract the blog card details .sort( // Sort each result by published_at in descending order // We can't sort everything at once we'd lose prefered (a, b) => a.published_at && b.published_at ? b.published_at - a.published_at : 0 ) ) } }) // Remove duplicates const unique = Array.from( events .filter((b) => b.id) .reduce((map, obj) => { map.set(obj.id!, obj) return map }, new Map>()) .values() ).filter( (b) => !(b.nsfw && filterOptions.nsfw === NSFWFilter.Hide_NSFW) ) const latest = unique.slice(0, 4) setBlogs(latest) } catch (error) { log( true, LogType.Error, 'An error occurred in fetching blog details from relays', error ) return null } } fetchBlogs() }) return (
{navigation.state !== 'idle' && }

Blog Posts

{blogs?.map((b) => ( ))}
View All
) }