added NSFW filter and source filter to a game's page to see their mods, adjusted landing page content #69
155
src/components/ModsFilter.tsx
Normal file
155
src/components/ModsFilter.tsx
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
import { useAppSelector } from 'hooks'
|
||||||
|
import React from 'react'
|
||||||
|
import { Dispatch, SetStateAction } from 'react'
|
||||||
|
import { FilterOptions, ModeratedFilter, NSFWFilter, SortBy } from 'types'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
filterOptions: FilterOptions
|
||||||
|
setFilterOptions: Dispatch<SetStateAction<FilterOptions>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ModFilter = React.memo(
|
||||||
|
({ filterOptions, setFilterOptions }: Props) => {
|
||||||
|
const userState = useAppSelector((state) => state.user)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='IBMSecMain'>
|
||||||
|
<div className='FiltersMain'>
|
||||||
|
<div className='FiltersMainElement'>
|
||||||
|
<div className='dropdown dropdownMain'>
|
||||||
|
<button
|
||||||
|
className='btn dropdown-toggle btnMain btnMainDropdown'
|
||||||
|
aria-expanded='false'
|
||||||
|
data-bs-toggle='dropdown'
|
||||||
|
type='button'
|
||||||
|
>
|
||||||
|
{filterOptions.sort}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className='dropdown-menu dropdownMainMenu'>
|
||||||
|
{Object.values(SortBy).map((item, index) => (
|
||||||
|
<div
|
||||||
|
key={`sortByItem-${index}`}
|
||||||
|
className='dropdown-item dropdownMainMenuItem'
|
||||||
|
onClick={() =>
|
||||||
|
setFilterOptions((prev) => ({
|
||||||
|
...prev,
|
||||||
|
sort: item
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='FiltersMainElement'>
|
||||||
|
<div className='dropdown dropdownMain'>
|
||||||
|
<button
|
||||||
|
className='btn dropdown-toggle btnMain btnMainDropdown'
|
||||||
|
aria-expanded='false'
|
||||||
|
data-bs-toggle='dropdown'
|
||||||
|
type='button'
|
||||||
|
>
|
||||||
|
{filterOptions.moderated}
|
||||||
|
</button>
|
||||||
|
<div className='dropdown-menu dropdownMainMenu'>
|
||||||
|
{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 (
|
||||||
|
<div
|
||||||
|
key={`moderatedFilterItem-${index}`}
|
||||||
|
className='dropdown-item dropdownMainMenuItem'
|
||||||
|
onClick={() =>
|
||||||
|
setFilterOptions((prev) => ({
|
||||||
|
...prev,
|
||||||
|
moderated: item
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='FiltersMainElement'>
|
||||||
|
<div className='dropdown dropdownMain'>
|
||||||
|
<button
|
||||||
|
className='btn dropdown-toggle btnMain btnMainDropdown'
|
||||||
|
aria-expanded='false'
|
||||||
|
data-bs-toggle='dropdown'
|
||||||
|
type='button'
|
||||||
|
>
|
||||||
|
{filterOptions.nsfw}
|
||||||
|
</button>
|
||||||
|
<div className='dropdown-menu dropdownMainMenu'>
|
||||||
|
{Object.values(NSFWFilter).map((item, index) => (
|
||||||
|
<div
|
||||||
|
key={`nsfwFilterItem-${index}`}
|
||||||
|
className='dropdown-item dropdownMainMenuItem'
|
||||||
|
onClick={() =>
|
||||||
|
setFilterOptions((prev) => ({
|
||||||
|
...prev,
|
||||||
|
nsfw: item
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='FiltersMainElement'>
|
||||||
|
<div className='dropdown dropdownMain'>
|
||||||
|
<button
|
||||||
|
className='btn dropdown-toggle btnMain btnMainDropdown'
|
||||||
|
aria-expanded='false'
|
||||||
|
data-bs-toggle='dropdown'
|
||||||
|
type='button'
|
||||||
|
>
|
||||||
|
{filterOptions.source === window.location.host
|
||||||
|
? `Show From: ${filterOptions.source}`
|
||||||
|
: 'Show All'}
|
||||||
|
</button>
|
||||||
|
<div className='dropdown-menu dropdownMainMenu'>
|
||||||
|
<div
|
||||||
|
className='dropdown-item dropdownMainMenuItem'
|
||||||
|
onClick={() =>
|
||||||
|
setFilterOptions((prev) => ({
|
||||||
|
...prev,
|
||||||
|
source: window.location.host
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Show From: {window.location.host}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className='dropdown-item dropdownMainMenuItem'
|
||||||
|
onClick={() =>
|
||||||
|
setFilterOptions((prev) => ({
|
||||||
|
...prev,
|
||||||
|
source: 'Show All'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Show All
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
@ -6,7 +6,8 @@ export const LANDING_PAGE_DATA = {
|
|||||||
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5vpcxs6nwwp3x5knyd3evckngetxxcknjdfkx5kngdfhvgukvwfjxsunseqnend73',
|
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5vpcxs6nwwp3x5knyd3evckngetxxcknjdfkx5kngdfhvgukvwfjxsunseqnend73',
|
||||||
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5d3excenzvf5xgkkvdny8qkngveex5knjcnxxqkn2efnx3jrxvpcxgukxdggsmal6',
|
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5d3excenzvf5xgkkvdny8qkngveex5knjcnxxqkn2efnx3jrxvpcxgukxdggsmal6',
|
||||||
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5dp4xsex2e3cxuknsdryvvkngc3sxcknjef4vcknvvmyvcukyd3kvd3rxdgnuver5',
|
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5dp4xsex2e3cxuknsdryvvkngc3sxcknjef4vcknvvmyvcukyd3kvd3rxdgnuver5',
|
||||||
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5vf5x9nrxcekxvknjvmzxvkngcfsx5kkzcf3xqknsvmrvgenwe3j8p3nzwgka59vj'
|
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5vf5x9nrxcekxvknjvmzxvkngcfsx5kkzcf3xqknsvmrvgenwe3j8p3nzwgka59vj',
|
||||||
|
'naddr1qvzqqqrkcgpzph2jv2ejvdk27hn36dt57j6f69f5h0zccve3xceujq5z9jk8ym8wqp4nxvp5xqer5eryx5ervvnzxvervvekvdskvdt9xuckgve4xu6xvdrzxsukgvf4xv6xycnrx5uxxvenxvcnxd3nxd3njvpj8qerycmpvvmnydnrv4jn5wrpv5mrvwpsxgknxwp4xqkngetpxqknjd35xcknsv3cv9jnxvtp8ycxyegq5ndhc'
|
||||||
],
|
],
|
||||||
awesomeMods: [
|
awesomeMods: [
|
||||||
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5d3excenzvf5xgkkvdny8qkngveex5knjcnxxqkn2efnx3jrxvpcxgukxdggsmal6',
|
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5d3excenzvf5xgkkvdny8qkngveex5knjcnxxqkn2efnx3jrxvpcxgukxdggsmal6',
|
||||||
@ -18,7 +19,7 @@ export const LANDING_PAGE_DATA = {
|
|||||||
"Baldur's Gate 3",
|
"Baldur's Gate 3",
|
||||||
'Cyberpunk 2077',
|
'Cyberpunk 2077',
|
||||||
'ELDEN RING',
|
'ELDEN RING',
|
||||||
'FINAL FANTASY VII REMAKE INTERGRADE'
|
'The Coffin of Andy and Leyley'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
// we use this object to check if a user has reacted positively or negatively to a post
|
// we use this object to check if a user has reacted positively or negatively to a post
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
export * from './redux'
|
export * from './redux'
|
||||||
export * from './useDidMount'
|
export * from './useDidMount'
|
||||||
|
export * from './useFilteredMods'
|
||||||
export * from './useGames'
|
export * from './useGames'
|
||||||
export * from './useMuteLists'
|
export * from './useMuteLists'
|
||||||
export * from './useNSFWList'
|
export * from './useNSFWList'
|
||||||
|
77
src/hooks/useFilteredMods.ts
Normal file
77
src/hooks/useFilteredMods.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
|
import { IUserState } from 'store/reducers/user'
|
||||||
|
import {
|
||||||
|
FilterOptions,
|
||||||
|
ModDetails,
|
||||||
|
ModeratedFilter,
|
||||||
|
MuteLists,
|
||||||
|
NSFWFilter,
|
||||||
|
SortBy
|
||||||
|
} from 'types'
|
||||||
|
|
||||||
|
export const useFilteredMods = (
|
||||||
|
mods: ModDetails[],
|
||||||
|
userState: IUserState,
|
||||||
|
filterOptions: FilterOptions,
|
||||||
|
nsfwList: string[],
|
||||||
|
muteLists: {
|
||||||
|
admin: MuteLists
|
||||||
|
user: MuteLists
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
return 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
|
||||||
|
])
|
||||||
|
}
|
@ -1,50 +1,40 @@
|
|||||||
import { LoadingSpinner } from 'components/LoadingSpinner'
|
import { LoadingSpinner } from 'components/LoadingSpinner'
|
||||||
import { ModCard } from 'components/ModCard'
|
import { ModCard } from 'components/ModCard'
|
||||||
|
import { ModFilter } from 'components/ModsFilter'
|
||||||
import { PaginationWithPageNumbers } from 'components/Pagination'
|
import { PaginationWithPageNumbers } from 'components/Pagination'
|
||||||
import { MAX_MODS_PER_PAGE, T_TAG_VALUE } from 'constants.ts'
|
import { MAX_MODS_PER_PAGE, T_TAG_VALUE } from 'constants.ts'
|
||||||
import { RelayController } from 'controllers'
|
import { RelayController } from 'controllers'
|
||||||
import { useAppSelector, useMuteLists } from 'hooks'
|
import {
|
||||||
|
useAppSelector,
|
||||||
|
useFilteredMods,
|
||||||
|
useMuteLists,
|
||||||
|
useNSFWList
|
||||||
|
} from 'hooks'
|
||||||
import { Filter, kinds } from 'nostr-tools'
|
import { Filter, kinds } from 'nostr-tools'
|
||||||
import { Subscription } from 'nostr-tools/abstract-relay'
|
import { Subscription } from 'nostr-tools/abstract-relay'
|
||||||
import React, {
|
import { useEffect, useRef, useState } from 'react'
|
||||||
Dispatch,
|
|
||||||
SetStateAction,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState
|
|
||||||
} from 'react'
|
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { ModDetails } from 'types'
|
import {
|
||||||
|
FilterOptions,
|
||||||
|
ModDetails,
|
||||||
|
ModeratedFilter,
|
||||||
|
NSFWFilter,
|
||||||
|
SortBy
|
||||||
|
} from 'types'
|
||||||
import { extractModData, isModDataComplete, log, LogType } from 'utils'
|
import { extractModData, isModDataComplete, log, LogType } from 'utils'
|
||||||
|
|
||||||
enum SortByEnum {
|
|
||||||
Latest = 'Latest',
|
|
||||||
Oldest = 'Oldest',
|
|
||||||
Best_Rated = 'Best Rated',
|
|
||||||
Worst_Rated = 'Worst Rated'
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ModeratedFilterEnum {
|
|
||||||
Moderated = 'Moderated',
|
|
||||||
Unmoderated = 'Unmoderated',
|
|
||||||
Unmoderated_Fully = 'Unmoderated Fully'
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FilterOptions {
|
|
||||||
sort: SortByEnum
|
|
||||||
moderated: ModeratedFilterEnum
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GamePage = () => {
|
export const GamePage = () => {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const { name: gameName } = params
|
const { name: gameName } = params
|
||||||
const muteLists = useMuteLists()
|
const muteLists = useMuteLists()
|
||||||
|
const nsfwList = useNSFWList()
|
||||||
|
|
||||||
const [filterOptions, setFilterOptions] = useState<FilterOptions>({
|
const [filterOptions, setFilterOptions] = useState<FilterOptions>({
|
||||||
sort: SortByEnum.Latest,
|
sort: SortBy.Latest,
|
||||||
moderated: ModeratedFilterEnum.Moderated
|
nsfw: NSFWFilter.Hide_NSFW,
|
||||||
|
source: window.location.host,
|
||||||
|
moderated: ModeratedFilter.Moderated
|
||||||
})
|
})
|
||||||
const [mods, setMods] = useState<ModDetails[]>([])
|
const [mods, setMods] = useState<ModDetails[]>([])
|
||||||
|
|
||||||
@ -54,43 +44,13 @@ export const GamePage = () => {
|
|||||||
|
|
||||||
const userState = useAppSelector((state) => state.user)
|
const userState = useAppSelector((state) => state.user)
|
||||||
|
|
||||||
const filteredMods = useMemo(() => {
|
const filteredMods = useFilteredMods(
|
||||||
let filtered: ModDetails[] = [...mods]
|
|
||||||
const isAdmin = userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB
|
|
||||||
const isUnmoderatedFully =
|
|
||||||
filterOptions.moderated === ModeratedFilterEnum.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 === ModeratedFilterEnum.Moderated) {
|
|
||||||
filtered = filtered.filter(
|
|
||||||
(mod) =>
|
|
||||||
!muteLists.user.authors.includes(mod.author) &&
|
|
||||||
!muteLists.user.replaceableEvents.includes(mod.aTag)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filterOptions.sort === SortByEnum.Latest) {
|
|
||||||
filtered.sort((a, b) => b.published_at - a.published_at)
|
|
||||||
} else if (filterOptions.sort === SortByEnum.Oldest) {
|
|
||||||
filtered.sort((a, b) => a.published_at - b.published_at)
|
|
||||||
}
|
|
||||||
|
|
||||||
return filtered
|
|
||||||
}, [
|
|
||||||
mods,
|
mods,
|
||||||
userState.user?.npub,
|
userState,
|
||||||
filterOptions.sort,
|
filterOptions,
|
||||||
filterOptions.moderated,
|
nsfwList,
|
||||||
muteLists
|
muteLists
|
||||||
])
|
)
|
||||||
|
|
||||||
// Pagination logic
|
// Pagination logic
|
||||||
const totalGames = filteredMods.length
|
const totalGames = filteredMods.length
|
||||||
@ -172,7 +132,7 @@ export const GamePage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Filters
|
<ModFilter
|
||||||
filterOptions={filterOptions}
|
filterOptions={filterOptions}
|
||||||
setFilterOptions={setFilterOptions}
|
setFilterOptions={setFilterOptions}
|
||||||
/>
|
/>
|
||||||
@ -194,88 +154,3 @@ export const GamePage = () => {
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type FiltersProps = {
|
|
||||||
filterOptions: FilterOptions
|
|
||||||
setFilterOptions: Dispatch<SetStateAction<FilterOptions>>
|
|
||||||
}
|
|
||||||
|
|
||||||
const Filters = React.memo(
|
|
||||||
({ filterOptions, setFilterOptions }: FiltersProps) => {
|
|
||||||
const userState = useAppSelector((state) => state.user)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='IBMSecMain'>
|
|
||||||
<div className='FiltersMain'>
|
|
||||||
<div className='FiltersMainElement'>
|
|
||||||
<div className='dropdown dropdownMain'>
|
|
||||||
<button
|
|
||||||
className='btn dropdown-toggle btnMain btnMainDropdown'
|
|
||||||
aria-expanded='false'
|
|
||||||
data-bs-toggle='dropdown'
|
|
||||||
type='button'
|
|
||||||
>
|
|
||||||
{filterOptions.sort}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div className='dropdown-menu dropdownMainMenu'>
|
|
||||||
{Object.values(SortByEnum).map((item, index) => (
|
|
||||||
<div
|
|
||||||
key={`sortByItem-${index}`}
|
|
||||||
className='dropdown-item dropdownMainMenuItem'
|
|
||||||
onClick={() =>
|
|
||||||
setFilterOptions((prev) => ({
|
|
||||||
...prev,
|
|
||||||
sort: item
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{item}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='FiltersMainElement'>
|
|
||||||
<div className='dropdown dropdownMain'>
|
|
||||||
<button
|
|
||||||
className='btn dropdown-toggle btnMain btnMainDropdown'
|
|
||||||
aria-expanded='false'
|
|
||||||
data-bs-toggle='dropdown'
|
|
||||||
type='button'
|
|
||||||
>
|
|
||||||
{filterOptions.moderated}
|
|
||||||
</button>
|
|
||||||
<div className='dropdown-menu dropdownMainMenu'>
|
|
||||||
{Object.values(ModeratedFilterEnum).map((item, index) => {
|
|
||||||
if (item === ModeratedFilterEnum.Unmoderated_Fully) {
|
|
||||||
const isAdmin =
|
|
||||||
userState.user?.npub ===
|
|
||||||
import.meta.env.VITE_REPORTING_NPUB
|
|
||||||
|
|
||||||
if (!isAdmin) return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={`moderatedFilterItem-${index}`}
|
|
||||||
className='dropdown-item dropdownMainMenuItem'
|
|
||||||
onClick={() =>
|
|
||||||
setFilterOptions((prev) => ({
|
|
||||||
...prev,
|
|
||||||
moderated: item
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{item}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
@ -1,52 +1,30 @@
|
|||||||
|
import { ModFilter } from 'components/ModsFilter'
|
||||||
import { Pagination } from 'components/Pagination'
|
import { Pagination } from 'components/Pagination'
|
||||||
import React, {
|
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
Dispatch,
|
|
||||||
SetStateAction,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState
|
|
||||||
} from 'react'
|
|
||||||
import { createSearchParams, useNavigate } from 'react-router-dom'
|
import { createSearchParams, useNavigate } from 'react-router-dom'
|
||||||
import { LoadingSpinner } from '../components/LoadingSpinner'
|
import { LoadingSpinner } from '../components/LoadingSpinner'
|
||||||
import { ModCard } from '../components/ModCard'
|
import { ModCard } from '../components/ModCard'
|
||||||
import { MOD_FILTER_LIMIT } from '../constants'
|
import { MOD_FILTER_LIMIT } from '../constants'
|
||||||
import { useAppSelector, useMuteLists, useNSFWList } from '../hooks'
|
import {
|
||||||
|
useAppSelector,
|
||||||
|
useFilteredMods,
|
||||||
|
useMuteLists,
|
||||||
|
useNSFWList
|
||||||
|
} from '../hooks'
|
||||||
import { appRoutes } from '../routes'
|
import { appRoutes } from '../routes'
|
||||||
import '../styles/filters.css'
|
import '../styles/filters.css'
|
||||||
import '../styles/pagination.css'
|
import '../styles/pagination.css'
|
||||||
import '../styles/search.css'
|
import '../styles/search.css'
|
||||||
import '../styles/styles.css'
|
import '../styles/styles.css'
|
||||||
import { ModDetails } from '../types'
|
import {
|
||||||
|
FilterOptions,
|
||||||
|
ModDetails,
|
||||||
|
ModeratedFilter,
|
||||||
|
NSFWFilter,
|
||||||
|
SortBy
|
||||||
|
} from '../types'
|
||||||
import { fetchMods } from '../utils'
|
import { fetchMods } from '../utils'
|
||||||
|
|
||||||
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 = () => {
|
export const ModsPage = () => {
|
||||||
const [isFetching, setIsFetching] = useState(false)
|
const [isFetching, setIsFetching] = useState(false)
|
||||||
const [mods, setMods] = useState<ModDetails[]>([])
|
const [mods, setMods] = useState<ModDetails[]>([])
|
||||||
@ -111,61 +89,13 @@ export const ModsPage = () => {
|
|||||||
})
|
})
|
||||||
}, [filterOptions.source, mods])
|
}, [filterOptions.source, mods])
|
||||||
|
|
||||||
const filteredModList = useMemo(() => {
|
const filteredModList = useFilteredMods(
|
||||||
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,
|
mods,
|
||||||
muteLists,
|
userState,
|
||||||
nsfwList
|
filterOptions,
|
||||||
])
|
nsfwList,
|
||||||
|
muteLists
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -174,7 +104,7 @@ export const ModsPage = () => {
|
|||||||
<div className='ContainerMain'>
|
<div className='ContainerMain'>
|
||||||
<div className='IBMSecMainGroup IBMSecMainGroupAlt'>
|
<div className='IBMSecMainGroup IBMSecMainGroupAlt'>
|
||||||
<PageTitleRow />
|
<PageTitleRow />
|
||||||
<Filters
|
<ModFilter
|
||||||
filterOptions={filterOptions}
|
filterOptions={filterOptions}
|
||||||
setFilterOptions={setFilterOptions}
|
setFilterOptions={setFilterOptions}
|
||||||
/>
|
/>
|
||||||
@ -261,154 +191,3 @@ const PageTitleRow = React.memo(() => {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
type FiltersProps = {
|
|
||||||
filterOptions: FilterOptions
|
|
||||||
setFilterOptions: Dispatch<SetStateAction<FilterOptions>>
|
|
||||||
}
|
|
||||||
|
|
||||||
const Filters = React.memo(
|
|
||||||
({ filterOptions, setFilterOptions }: FiltersProps) => {
|
|
||||||
const userState = useAppSelector((state) => state.user)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='IBMSecMain'>
|
|
||||||
<div className='FiltersMain'>
|
|
||||||
<div className='FiltersMainElement'>
|
|
||||||
<div className='dropdown dropdownMain'>
|
|
||||||
<button
|
|
||||||
className='btn dropdown-toggle btnMain btnMainDropdown'
|
|
||||||
aria-expanded='false'
|
|
||||||
data-bs-toggle='dropdown'
|
|
||||||
type='button'
|
|
||||||
>
|
|
||||||
{filterOptions.sort}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div className='dropdown-menu dropdownMainMenu'>
|
|
||||||
{Object.values(SortBy).map((item, index) => (
|
|
||||||
<div
|
|
||||||
key={`sortByItem-${index}`}
|
|
||||||
className='dropdown-item dropdownMainMenuItem'
|
|
||||||
onClick={() =>
|
|
||||||
setFilterOptions((prev) => ({
|
|
||||||
...prev,
|
|
||||||
sort: item
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{item}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='FiltersMainElement'>
|
|
||||||
<div className='dropdown dropdownMain'>
|
|
||||||
<button
|
|
||||||
className='btn dropdown-toggle btnMain btnMainDropdown'
|
|
||||||
aria-expanded='false'
|
|
||||||
data-bs-toggle='dropdown'
|
|
||||||
type='button'
|
|
||||||
>
|
|
||||||
{filterOptions.moderated}
|
|
||||||
</button>
|
|
||||||
<div className='dropdown-menu dropdownMainMenu'>
|
|
||||||
{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 (
|
|
||||||
<div
|
|
||||||
key={`moderatedFilterItem-${index}`}
|
|
||||||
className='dropdown-item dropdownMainMenuItem'
|
|
||||||
onClick={() =>
|
|
||||||
setFilterOptions((prev) => ({
|
|
||||||
...prev,
|
|
||||||
moderated: item
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{item}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='FiltersMainElement'>
|
|
||||||
<div className='dropdown dropdownMain'>
|
|
||||||
<button
|
|
||||||
className='btn dropdown-toggle btnMain btnMainDropdown'
|
|
||||||
aria-expanded='false'
|
|
||||||
data-bs-toggle='dropdown'
|
|
||||||
type='button'
|
|
||||||
>
|
|
||||||
{filterOptions.nsfw}
|
|
||||||
</button>
|
|
||||||
<div className='dropdown-menu dropdownMainMenu'>
|
|
||||||
{Object.values(NSFWFilter).map((item, index) => (
|
|
||||||
<div
|
|
||||||
key={`nsfwFilterItem-${index}`}
|
|
||||||
className='dropdown-item dropdownMainMenuItem'
|
|
||||||
onClick={() =>
|
|
||||||
setFilterOptions((prev) => ({
|
|
||||||
...prev,
|
|
||||||
nsfw: item
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{item}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='FiltersMainElement'>
|
|
||||||
<div className='dropdown dropdownMain'>
|
|
||||||
<button
|
|
||||||
className='btn dropdown-toggle btnMain btnMainDropdown'
|
|
||||||
aria-expanded='false'
|
|
||||||
data-bs-toggle='dropdown'
|
|
||||||
type='button'
|
|
||||||
>
|
|
||||||
{filterOptions.source === window.location.host
|
|
||||||
? `Show From: ${filterOptions.source}`
|
|
||||||
: 'Show All'}
|
|
||||||
</button>
|
|
||||||
<div className='dropdown-menu dropdownMainMenu'>
|
|
||||||
<div
|
|
||||||
className='dropdown-item dropdownMainMenuItem'
|
|
||||||
onClick={() =>
|
|
||||||
setFilterOptions((prev) => ({
|
|
||||||
...prev,
|
|
||||||
source: window.location.host
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Show From: {window.location.host}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className='dropdown-item dropdownMainMenuItem'
|
|
||||||
onClick={() =>
|
|
||||||
setFilterOptions((prev) => ({
|
|
||||||
...prev,
|
|
||||||
source: 'Show All'
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Show All
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
@ -3,6 +3,7 @@ import { ErrorBoundary } from 'components/ErrorBoundary'
|
|||||||
import { GameCard } from 'components/GameCard'
|
import { GameCard } from 'components/GameCard'
|
||||||
import { LoadingSpinner } from 'components/LoadingSpinner'
|
import { LoadingSpinner } from 'components/LoadingSpinner'
|
||||||
import { ModCard } from 'components/ModCard'
|
import { ModCard } from 'components/ModCard'
|
||||||
|
import { ModFilter } from 'components/ModsFilter'
|
||||||
import { Pagination } from 'components/Pagination'
|
import { Pagination } from 'components/Pagination'
|
||||||
import { Profile } from 'components/ProfileSection'
|
import { Profile } from 'components/ProfileSection'
|
||||||
import {
|
import {
|
||||||
@ -11,7 +12,13 @@ import {
|
|||||||
T_TAG_VALUE
|
T_TAG_VALUE
|
||||||
} from 'constants.ts'
|
} from 'constants.ts'
|
||||||
import { RelayController } from 'controllers'
|
import { RelayController } from 'controllers'
|
||||||
import { useAppSelector, useGames, useMuteLists } from 'hooks'
|
import {
|
||||||
|
useAppSelector,
|
||||||
|
useFilteredMods,
|
||||||
|
useGames,
|
||||||
|
useMuteLists,
|
||||||
|
useNSFWList
|
||||||
|
} from 'hooks'
|
||||||
import { Filter, kinds } from 'nostr-tools'
|
import { Filter, kinds } from 'nostr-tools'
|
||||||
import { Subscription } from 'nostr-tools/abstract-relay'
|
import { Subscription } from 'nostr-tools/abstract-relay'
|
||||||
import React, {
|
import React, {
|
||||||
@ -24,48 +31,40 @@ import React, {
|
|||||||
} from 'react'
|
} from 'react'
|
||||||
import { useSearchParams } from 'react-router-dom'
|
import { useSearchParams } from 'react-router-dom'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { ModDetails, MuteLists } from 'types'
|
import {
|
||||||
|
FilterOptions,
|
||||||
|
ModDetails,
|
||||||
|
ModeratedFilter,
|
||||||
|
MuteLists,
|
||||||
|
NSFWFilter,
|
||||||
|
SortBy
|
||||||
|
} from 'types'
|
||||||
import { extractModData, isModDataComplete, log, LogType } from 'utils'
|
import { extractModData, isModDataComplete, log, LogType } from 'utils'
|
||||||
|
|
||||||
enum SortByEnum {
|
enum SearchKindEnum {
|
||||||
Latest = 'Latest',
|
|
||||||
Oldest = 'Oldest',
|
|
||||||
Best_Rated = 'Best Rated',
|
|
||||||
Worst_Rated = 'Worst Rated'
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ModeratedFilterEnum {
|
|
||||||
Moderated = 'Moderated',
|
|
||||||
Unmoderated = 'Unmoderated',
|
|
||||||
Unmoderated_Fully = 'Unmoderated Fully'
|
|
||||||
}
|
|
||||||
|
|
||||||
enum SearchingFilterEnum {
|
|
||||||
Mods = 'Mods',
|
Mods = 'Mods',
|
||||||
Games = 'Games',
|
Games = 'Games',
|
||||||
Users = 'Users'
|
Users = 'Users'
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FilterOptions {
|
|
||||||
sort: SortByEnum
|
|
||||||
moderated: ModeratedFilterEnum
|
|
||||||
searching: SearchingFilterEnum
|
|
||||||
source: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SearchPage = () => {
|
export const SearchPage = () => {
|
||||||
const [searchParams] = useSearchParams()
|
const [searchParams] = useSearchParams()
|
||||||
|
|
||||||
const muteLists = useMuteLists()
|
const muteLists = useMuteLists()
|
||||||
|
const nsfwList = useNSFWList()
|
||||||
const searchTermRef = useRef<HTMLInputElement>(null)
|
const searchTermRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
|
const [searchKind, setSearchKind] = useState(
|
||||||
|
(searchParams.get('searching') as SearchKindEnum) || SearchKindEnum.Mods
|
||||||
|
)
|
||||||
|
|
||||||
const [filterOptions, setFilterOptions] = useState<FilterOptions>({
|
const [filterOptions, setFilterOptions] = useState<FilterOptions>({
|
||||||
sort: SortByEnum.Latest,
|
sort: SortBy.Latest,
|
||||||
moderated: ModeratedFilterEnum.Moderated,
|
nsfw: NSFWFilter.Hide_NSFW,
|
||||||
source: window.location.host,
|
source: window.location.host,
|
||||||
searching:
|
moderated: ModeratedFilter.Moderated
|
||||||
(searchParams.get('searching') as SearchingFilterEnum) ||
|
|
||||||
SearchingFilterEnum.Mods
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const [searchTerm, setSearchTerm] = useState(
|
const [searchTerm, setSearchTerm] = useState(
|
||||||
searchParams.get('searchTerm') || ''
|
searchParams.get('searchTerm') || ''
|
||||||
)
|
)
|
||||||
@ -129,22 +128,25 @@ export const SearchPage = () => {
|
|||||||
<Filters
|
<Filters
|
||||||
filterOptions={filterOptions}
|
filterOptions={filterOptions}
|
||||||
setFilterOptions={setFilterOptions}
|
setFilterOptions={setFilterOptions}
|
||||||
|
searchKind={searchKind}
|
||||||
|
setSearchKind={setSearchKind}
|
||||||
/>
|
/>
|
||||||
{filterOptions.searching === SearchingFilterEnum.Mods && (
|
{searchKind === SearchKindEnum.Mods && (
|
||||||
<ModsResult
|
<ModsResult
|
||||||
searchTerm={searchTerm}
|
searchTerm={searchTerm}
|
||||||
filterOptions={filterOptions}
|
filterOptions={filterOptions}
|
||||||
muteLists={muteLists}
|
muteLists={muteLists}
|
||||||
|
nsfwList={nsfwList}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{filterOptions.searching === SearchingFilterEnum.Users && (
|
{searchKind === SearchKindEnum.Users && (
|
||||||
<UsersResult
|
<UsersResult
|
||||||
searchTerm={searchTerm}
|
searchTerm={searchTerm}
|
||||||
muteLists={muteLists}
|
muteLists={muteLists}
|
||||||
moderationFilter={filterOptions.moderated}
|
moderationFilter={filterOptions.moderated}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{filterOptions.searching === SearchingFilterEnum.Games && (
|
{searchKind === SearchKindEnum.Games && (
|
||||||
<GamesResult searchTerm={searchTerm} />
|
<GamesResult searchTerm={searchTerm} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -156,49 +158,30 @@ export const SearchPage = () => {
|
|||||||
type FiltersProps = {
|
type FiltersProps = {
|
||||||
filterOptions: FilterOptions
|
filterOptions: FilterOptions
|
||||||
setFilterOptions: Dispatch<SetStateAction<FilterOptions>>
|
setFilterOptions: Dispatch<SetStateAction<FilterOptions>>
|
||||||
|
searchKind: SearchKindEnum
|
||||||
|
setSearchKind: Dispatch<SetStateAction<SearchKindEnum>>
|
||||||
}
|
}
|
||||||
|
|
||||||
const Filters = React.memo(
|
const Filters = React.memo(
|
||||||
({ filterOptions, setFilterOptions }: FiltersProps) => {
|
({
|
||||||
|
filterOptions,
|
||||||
|
setFilterOptions,
|
||||||
|
searchKind,
|
||||||
|
setSearchKind
|
||||||
|
}: FiltersProps) => {
|
||||||
const userState = useAppSelector((state) => state.user)
|
const userState = useAppSelector((state) => state.user)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='IBMSecMain'>
|
<div className='IBMSecMain'>
|
||||||
<div className='FiltersMain'>
|
<div className='FiltersMain'>
|
||||||
{filterOptions.searching === SearchingFilterEnum.Mods && (
|
{searchKind === SearchKindEnum.Mods && (
|
||||||
<div className='FiltersMainElement'>
|
<ModFilter
|
||||||
<div className='dropdown dropdownMain'>
|
filterOptions={filterOptions}
|
||||||
<button
|
setFilterOptions={setFilterOptions}
|
||||||
className='btn dropdown-toggle btnMain btnMainDropdown'
|
/>
|
||||||
aria-expanded='false'
|
|
||||||
data-bs-toggle='dropdown'
|
|
||||||
type='button'
|
|
||||||
>
|
|
||||||
{filterOptions.sort}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div className='dropdown-menu dropdownMainMenu'>
|
|
||||||
{Object.values(SortByEnum).map((item, index) => (
|
|
||||||
<div
|
|
||||||
key={`sortByItem-${index}`}
|
|
||||||
className='dropdown-item dropdownMainMenuItem'
|
|
||||||
onClick={() =>
|
|
||||||
setFilterOptions((prev) => ({
|
|
||||||
...prev,
|
|
||||||
sort: item
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{item}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(filterOptions.searching === SearchingFilterEnum.Mods ||
|
{searchKind === SearchKindEnum.Users && (
|
||||||
filterOptions.searching === SearchingFilterEnum.Users) && (
|
|
||||||
<div className='FiltersMainElement'>
|
<div className='FiltersMainElement'>
|
||||||
<div className='dropdown dropdownMain'>
|
<div className='dropdown dropdownMain'>
|
||||||
<button
|
<button
|
||||||
@ -210,8 +193,8 @@ const Filters = React.memo(
|
|||||||
{filterOptions.moderated}
|
{filterOptions.moderated}
|
||||||
</button>
|
</button>
|
||||||
<div className='dropdown-menu dropdownMainMenu'>
|
<div className='dropdown-menu dropdownMainMenu'>
|
||||||
{Object.values(ModeratedFilterEnum).map((item, index) => {
|
{Object.values(ModeratedFilter).map((item, index) => {
|
||||||
if (item === ModeratedFilterEnum.Unmoderated_Fully) {
|
if (item === ModeratedFilter.Unmoderated_Fully) {
|
||||||
const isAdmin =
|
const isAdmin =
|
||||||
userState.user?.npub ===
|
userState.user?.npub ===
|
||||||
import.meta.env.VITE_REPORTING_NPUB
|
import.meta.env.VITE_REPORTING_NPUB
|
||||||
@ -239,7 +222,6 @@ const Filters = React.memo(
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{filterOptions.searching === SearchingFilterEnum.Mods && (
|
|
||||||
<div className='FiltersMainElement'>
|
<div className='FiltersMainElement'>
|
||||||
<div className='dropdown dropdownMain'>
|
<div className='dropdown dropdownMain'>
|
||||||
<button
|
<button
|
||||||
@ -248,59 +230,14 @@ const Filters = React.memo(
|
|||||||
data-bs-toggle='dropdown'
|
data-bs-toggle='dropdown'
|
||||||
type='button'
|
type='button'
|
||||||
>
|
>
|
||||||
{filterOptions.source === window.location.host
|
Searching: {searchKind}
|
||||||
? `Show From: ${filterOptions.source}`
|
|
||||||
: 'Show All'}
|
|
||||||
</button>
|
</button>
|
||||||
<div className='dropdown-menu dropdownMainMenu'>
|
<div className='dropdown-menu dropdownMainMenu'>
|
||||||
<div
|
{Object.values(SearchKindEnum).map((item, index) => (
|
||||||
className='dropdown-item dropdownMainMenuItem'
|
|
||||||
onClick={() =>
|
|
||||||
setFilterOptions((prev) => ({
|
|
||||||
...prev,
|
|
||||||
source: window.location.host
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Show From: {window.location.host}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className='dropdown-item dropdownMainMenuItem'
|
|
||||||
onClick={() =>
|
|
||||||
setFilterOptions((prev) => ({
|
|
||||||
...prev,
|
|
||||||
source: 'Show All'
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Show All
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className='FiltersMainElement'>
|
|
||||||
<div className='dropdown dropdownMain'>
|
|
||||||
<button
|
|
||||||
className='btn dropdown-toggle btnMain btnMainDropdown'
|
|
||||||
aria-expanded='false'
|
|
||||||
data-bs-toggle='dropdown'
|
|
||||||
type='button'
|
|
||||||
>
|
|
||||||
Searching: {filterOptions.searching}
|
|
||||||
</button>
|
|
||||||
<div className='dropdown-menu dropdownMainMenu'>
|
|
||||||
{Object.values(SearchingFilterEnum).map((item, index) => (
|
|
||||||
<div
|
<div
|
||||||
key={`searchingFilterItem-${index}`}
|
key={`searchingFilterItem-${index}`}
|
||||||
className='dropdown-item dropdownMainMenuItem'
|
className='dropdown-item dropdownMainMenuItem'
|
||||||
onClick={() =>
|
onClick={() => setSearchKind(item)}
|
||||||
setFilterOptions((prev) => ({
|
|
||||||
...prev,
|
|
||||||
searching: item
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{item}
|
{item}
|
||||||
</div>
|
</div>
|
||||||
@ -321,12 +258,14 @@ type ModsResultProps = {
|
|||||||
admin: MuteLists
|
admin: MuteLists
|
||||||
user: MuteLists
|
user: MuteLists
|
||||||
}
|
}
|
||||||
|
nsfwList: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModsResult = ({
|
const ModsResult = ({
|
||||||
filterOptions,
|
filterOptions,
|
||||||
searchTerm,
|
searchTerm,
|
||||||
muteLists
|
muteLists,
|
||||||
|
nsfwList
|
||||||
}: ModsResultProps) => {
|
}: ModsResultProps) => {
|
||||||
const hasEffectRun = useRef(false)
|
const hasEffectRun = useRef(false)
|
||||||
const [isSubscribing, setIsSubscribing] = useState(false)
|
const [isSubscribing, setIsSubscribing] = useState(false)
|
||||||
@ -400,49 +339,13 @@ const ModsResult = ({
|
|||||||
return mods.filter(filterFn)
|
return mods.filter(filterFn)
|
||||||
}, [mods, searchTerm])
|
}, [mods, searchTerm])
|
||||||
|
|
||||||
const filteredModList = useMemo(() => {
|
const filteredModList = useFilteredMods(
|
||||||
let filtered: ModDetails[] = [...filteredMods]
|
|
||||||
|
|
||||||
if (filterOptions.source === window.location.host) {
|
|
||||||
filtered = filtered.filter((mod) => mod.rTag === window.location.host)
|
|
||||||
}
|
|
||||||
|
|
||||||
const isAdmin = userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB
|
|
||||||
const isUnmoderatedFully =
|
|
||||||
filterOptions.moderated === ModeratedFilterEnum.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 === ModeratedFilterEnum.Moderated) {
|
|
||||||
filtered = filtered.filter(
|
|
||||||
(mod) =>
|
|
||||||
!muteLists.user.authors.includes(mod.author) &&
|
|
||||||
!muteLists.user.replaceableEvents.includes(mod.aTag)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filterOptions.sort === SortByEnum.Latest) {
|
|
||||||
filtered.sort((a, b) => b.published_at - a.published_at)
|
|
||||||
} else if (filterOptions.sort === SortByEnum.Oldest) {
|
|
||||||
filtered.sort((a, b) => a.published_at - b.published_at)
|
|
||||||
}
|
|
||||||
|
|
||||||
return filtered
|
|
||||||
}, [
|
|
||||||
filteredMods,
|
filteredMods,
|
||||||
userState.user?.npub,
|
userState,
|
||||||
filterOptions.sort,
|
filterOptions,
|
||||||
filterOptions.moderated,
|
nsfwList,
|
||||||
filterOptions.source,
|
|
||||||
muteLists
|
muteLists
|
||||||
])
|
)
|
||||||
|
|
||||||
const handleNext = () => {
|
const handleNext = () => {
|
||||||
setPage((prev) => prev + 1)
|
setPage((prev) => prev + 1)
|
||||||
@ -478,7 +381,7 @@ const ModsResult = ({
|
|||||||
|
|
||||||
type UsersResultProps = {
|
type UsersResultProps = {
|
||||||
searchTerm: string
|
searchTerm: string
|
||||||
moderationFilter: ModeratedFilterEnum
|
moderationFilter: ModeratedFilter
|
||||||
muteLists: {
|
muteLists: {
|
||||||
admin: MuteLists
|
admin: MuteLists
|
||||||
user: MuteLists
|
user: MuteLists
|
||||||
@ -528,7 +431,7 @@ const UsersResult = ({
|
|||||||
let filtered = [...profiles]
|
let filtered = [...profiles]
|
||||||
const isAdmin = userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB
|
const isAdmin = userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB
|
||||||
const isUnmoderatedFully =
|
const isUnmoderatedFully =
|
||||||
moderationFilter === ModeratedFilterEnum.Unmoderated_Fully
|
moderationFilter === ModeratedFilter.Unmoderated_Fully
|
||||||
|
|
||||||
// Only apply filtering if the user is not an admin or the admin has not selected "Unmoderated Fully"
|
// Only apply filtering if the user is not an admin or the admin has not selected "Unmoderated Fully"
|
||||||
if (!(isAdmin && isUnmoderatedFully)) {
|
if (!(isAdmin && isUnmoderatedFully)) {
|
||||||
@ -537,7 +440,7 @@ const UsersResult = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (moderationFilter === ModeratedFilterEnum.Moderated) {
|
if (moderationFilter === ModeratedFilter.Moderated) {
|
||||||
filtered = filtered.filter(
|
filtered = filtered.filter(
|
||||||
(profile) => !muteLists.user.authors.includes(profile.pubkey as string)
|
(profile) => !muteLists.user.authors.includes(profile.pubkey as string)
|
||||||
)
|
)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
export * from './mod'
|
export * from './mod'
|
||||||
|
export * from './modsFilter'
|
||||||
export * from './nostr'
|
export * from './nostr'
|
||||||
export * from './user'
|
export * from './user'
|
||||||
export * from './zap'
|
export * from './zap'
|
||||||
|
25
src/types/modsFilter.ts
Normal file
25
src/types/modsFilter.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
export enum SortBy {
|
||||||
|
Latest = 'Latest',
|
||||||
|
Oldest = 'Oldest',
|
||||||
|
Best_Rated = 'Best Rated',
|
||||||
|
Worst_Rated = 'Worst Rated'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum NSFWFilter {
|
||||||
|
Hide_NSFW = 'Hide NSFW',
|
||||||
|
Show_NSFW = 'Show NSFW',
|
||||||
|
Only_NSFW = 'Only NSFW'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ModeratedFilter {
|
||||||
|
Moderated = 'Moderated',
|
||||||
|
Unmoderated = 'Unmoderated',
|
||||||
|
Unmoderated_Fully = 'Unmoderated Fully'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FilterOptions {
|
||||||
|
sort: SortBy
|
||||||
|
nsfw: NSFWFilter
|
||||||
|
source: string
|
||||||
|
moderated: ModeratedFilter
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user