import { kinds, nip19 } from 'nostr-tools' import React, { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from 'react' import { useNavigate } from 'react-router-dom' import { LoadingSpinner } from '../components/LoadingSpinner' import { ModCard } from '../components/ModCard' import { MetadataController } from '../controllers' import { useAppSelector, useDidMount } from '../hooks' import { getModsInnerPageRoute } from '../routes' import '../styles/filters.css' import '../styles/pagination.css' import '../styles/search.css' import '../styles/styles.css' import { ModDetails, MuteLists } from '../types' import { fetchMods } from '../utils' import { MOD_FILTER_LIMIT } from '../constants' enum SortBy { Latest = 'Latest', Oldest = 'Oldest', Best_Rated = 'Best Rated', Worst_Rated = 'Worst Rated' } enum NSFWFilter { Hide_NSFW = 'Hide NSFW', Show_NSFW = 'Show NSFW', Only_NSFW = 'Only NSFW' } enum ModeratedFilter { Moderated = 'Moderated', Unmoderated = 'Unmoderated', Unmoderated_Fully = 'Unmoderated Fully' } interface FilterOptions { sort: SortBy nsfw: NSFWFilter source: string moderated: ModeratedFilter } export const ModsPage = () => { const navigate = useNavigate() const [isFetching, setIsFetching] = useState(false) const [mods, setMods] = useState([]) const [filterOptions, setFilterOptions] = useState({ sort: SortBy.Latest, nsfw: NSFWFilter.Hide_NSFW, source: window.location.host, moderated: ModeratedFilter.Moderated }) const [muteLists, setMuteLists] = useState<{ admin: MuteLists user: MuteLists }>({ admin: { authors: [], replaceableEvents: [] }, user: { authors: [], replaceableEvents: [] } }) const [nsfwList, setNSFWList] = useState([]) const [page, setPage] = useState(1) const userState = useAppSelector((state) => state.user) useDidMount(async () => { const pubkey = userState.user?.pubkey as string | undefined const metadataController = await MetadataController.getInstance() metadataController.getMuteLists(pubkey).then((lists) => { setMuteLists(lists) }) metadataController.getNSFWList().then((list) => { setNSFWList(list) }) }) useEffect(() => { setIsFetching(true) fetchMods(filterOptions.source) .then((res) => { setMods(res) }) .finally(() => { setIsFetching(false) }) }, [filterOptions.source]) const handleNext = useCallback(() => { setIsFetching(true) const until = mods.length > 0 ? mods[mods.length - 1].published_at - 1 : undefined fetchMods(filterOptions.source, until) .then((res) => { setMods(res) setPage((prev) => prev + 1) }) .finally(() => { setIsFetching(false) }) }, [filterOptions.source, mods]) const handlePrev = useCallback(() => { setIsFetching(true) const since = mods.length > 0 ? mods[0].published_at + 1 : undefined fetchMods(filterOptions.source, undefined, since) .then((res) => { setMods(res) setPage((prev) => prev - 1) }) .finally(() => { setIsFetching(false) }) }, [filterOptions.source, mods]) const filteredModList = useMemo(() => { const nsfwFilter = (mods: ModDetails[]) => { // Determine the filtering logic based on the NSFW filter option switch (filterOptions.nsfw) { case NSFWFilter.Hide_NSFW: // If 'Hide_NSFW' is selected, filter out NSFW mods return mods.filter((mod) => !mod.nsfw && !nsfwList.includes(mod.aTag)) case NSFWFilter.Show_NSFW: // If 'Show_NSFW' is selected, return all mods (no filtering) return mods case NSFWFilter.Only_NSFW: // If 'Only_NSFW' is selected, filter to show only NSFW mods return mods.filter((mod) => mod.nsfw || nsfwList.includes(mod.aTag)) } } let filtered = nsfwFilter(mods) const isAdmin = userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB const isUnmoderatedFully = filterOptions.moderated === ModeratedFilter.Unmoderated_Fully // Only apply filtering if the user is not an admin or the admin has not selected "Unmoderated Fully" if (!(isAdmin && isUnmoderatedFully)) { filtered = filtered.filter( (mod) => !muteLists.admin.authors.includes(mod.author) && !muteLists.admin.replaceableEvents.includes(mod.aTag) ) } if (filterOptions.moderated === ModeratedFilter.Moderated) { filtered = filtered.filter( (mod) => !muteLists.user.authors.includes(mod.author) && !muteLists.user.replaceableEvents.includes(mod.aTag) ) } if (filterOptions.sort === SortBy.Latest) { filtered.sort((a, b) => b.published_at - a.published_at) } else if (filterOptions.sort === SortBy.Oldest) { filtered.sort((a, b) => a.published_at - b.published_at) } return filtered }, [ userState.user?.npub, filterOptions.sort, filterOptions.moderated, filterOptions.nsfw, mods, muteLists, nsfwList ]) return ( <> {isFetching && }
{filteredModList.map((mod) => { const route = getModsInnerPageRoute( nip19.naddrEncode({ identifier: mod.aTag, pubkey: mod.author, kind: kinds.ClassifiedListing }) ) return ( navigate(route)} /> ) })}
) } const PageTitleRow = React.memo(() => { return (

Mods

) }) type FiltersProps = { filterOptions: FilterOptions setFilterOptions: Dispatch> } const Filters = React.memo( ({ filterOptions, setFilterOptions }: FiltersProps) => { const userState = useAppSelector((state) => state.user) return (
{Object.values(SortBy).map((item, index) => (
setFilterOptions((prev) => ({ ...prev, sort: item })) } > {item}
))}
{Object.values(ModeratedFilter).map((item, index) => { if (item === ModeratedFilter.Unmoderated_Fully) { const isAdmin = userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB if (!isAdmin) return null } return (
setFilterOptions((prev) => ({ ...prev, moderated: item })) } > {item}
) })}
{Object.values(NSFWFilter).map((item, index) => (
setFilterOptions((prev) => ({ ...prev, nsfw: item })) } > {item}
))}
setFilterOptions((prev) => ({ ...prev, source: window.location.host })) } > Show From: {window.location.host}
setFilterOptions((prev) => ({ ...prev, source: 'Show All' })) } > Show All
) } ) type PaginationProps = { page: number disabledNext: boolean handlePrev: () => void handleNext: () => void } const Pagination = React.memo( ({ page, disabledNext, handlePrev, handleNext }: PaginationProps) => { return (
) } )