Compare commits

..

3 Commits

Author SHA1 Message Date
freakoverse
25fcdce7b0 Merge pull request 'added NSFW filter and source filter to a game's page to see their mods, adjusted landing page content' (#69) from staging into master
All checks were successful
Release to Staging / build_and_release (push) Successful in 46s
Reviewed-on: #69
2024-10-07 12:05:03 +00:00
freakoverse
b48b4478af Update src/constants.ts
All checks were successful
Release to Staging / build_and_release (push) Successful in 48s
2024-10-07 12:02:20 +00:00
daniyal
b6a8fc435d chore: refactor code for mod filter
All checks were successful
Release to Staging / build_and_release (push) Successful in 46s
2024-10-07 15:45:21 +05:00
9 changed files with 373 additions and 556 deletions

View 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>
)
}
)

View File

@ -6,7 +6,8 @@ export const LANDING_PAGE_DATA = {
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5vpcxs6nwwp3x5knyd3evckngetxxcknjdfkx5kngdfhvgukvwfjxsunseqnend73',
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5d3excenzvf5xgkkvdny8qkngveex5knjcnxxqkn2efnx3jrxvpcxgukxdggsmal6',
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5dp4xsex2e3cxuknsdryvvkngc3sxcknjef4vcknvvmyvcukyd3kvd3rxdgnuver5',
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5vf5x9nrxcekxvknjvmzxvkngcfsx5kkzcf3xqknsvmrvgenwe3j8p3nzwgka59vj'
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5vf5x9nrxcekxvknjvmzxvkngcfsx5kkzcf3xqknsvmrvgenwe3j8p3nzwgka59vj',
'naddr1qvzqqqrkcgpzph2jv2ejvdk27hn36dt57j6f69f5h0zccve3xceujq5z9jk8ym8wqp4nxvp5xqer5eryx5ervvnzxvervvekvdskvdt9xuckgve4xu6xvdrzxsukgvf4xv6xycnrx5uxxvenxvcnxd3nxd3njvpj8qerycmpvvmnydnrv4jn5wrpv5mrvwpsxgknxwp4xqkngetpxqknjd35xcknsv3cv9jnxvtp8ycxyegq5ndhc'
],
awesomeMods: [
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5d3excenzvf5xgkkvdny8qkngveex5knjcnxxqkn2efnx3jrxvpcxgukxdggsmal6',
@ -18,7 +19,7 @@ export const LANDING_PAGE_DATA = {
"Baldur's Gate 3",
'Cyberpunk 2077',
'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

View File

@ -1,5 +1,6 @@
export * from './redux'
export * from './useDidMount'
export * from './useFilteredMods'
export * from './useGames'
export * from './useMuteLists'
export * from './useNSFWList'

View 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
])
}

View File

@ -1,50 +1,40 @@
import { LoadingSpinner } from 'components/LoadingSpinner'
import { ModCard } from 'components/ModCard'
import { ModFilter } from 'components/ModsFilter'
import { PaginationWithPageNumbers } from 'components/Pagination'
import { MAX_MODS_PER_PAGE, T_TAG_VALUE } from 'constants.ts'
import { RelayController } from 'controllers'
import { useAppSelector, useMuteLists } from 'hooks'
import {
useAppSelector,
useFilteredMods,
useMuteLists,
useNSFWList
} from 'hooks'
import { Filter, kinds } from 'nostr-tools'
import { Subscription } from 'nostr-tools/abstract-relay'
import React, {
Dispatch,
SetStateAction,
useEffect,
useMemo,
useRef,
useState
} from 'react'
import { useEffect, useRef, useState } from 'react'
import { useParams } from 'react-router-dom'
import { toast } from 'react-toastify'
import { ModDetails } from 'types'
import {
FilterOptions,
ModDetails,
ModeratedFilter,
NSFWFilter,
SortBy
} from 'types'
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 = () => {
const params = useParams()
const { name: gameName } = params
const muteLists = useMuteLists()
const nsfwList = useNSFWList()
const [filterOptions, setFilterOptions] = useState<FilterOptions>({
sort: SortByEnum.Latest,
moderated: ModeratedFilterEnum.Moderated
sort: SortBy.Latest,
nsfw: NSFWFilter.Hide_NSFW,
source: window.location.host,
moderated: ModeratedFilter.Moderated
})
const [mods, setMods] = useState<ModDetails[]>([])
@ -54,43 +44,13 @@ export const GamePage = () => {
const userState = useAppSelector((state) => state.user)
const filteredMods = useMemo(() => {
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
}, [
const filteredMods = useFilteredMods(
mods,
userState.user?.npub,
filterOptions.sort,
filterOptions.moderated,
userState,
filterOptions,
nsfwList,
muteLists
])
)
// Pagination logic
const totalGames = filteredMods.length
@ -172,7 +132,7 @@ export const GamePage = () => {
</div>
</div>
</div>
<Filters
<ModFilter
filterOptions={filterOptions}
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>
)
}
)

View File

@ -1,52 +1,30 @@
import { ModFilter } from 'components/ModsFilter'
import { Pagination } from 'components/Pagination'
import React, {
Dispatch,
SetStateAction,
useCallback,
useEffect,
useMemo,
useRef,
useState
} from 'react'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { createSearchParams, useNavigate } from 'react-router-dom'
import { LoadingSpinner } from '../components/LoadingSpinner'
import { ModCard } from '../components/ModCard'
import { MOD_FILTER_LIMIT } from '../constants'
import { useAppSelector, useMuteLists, useNSFWList } from '../hooks'
import {
useAppSelector,
useFilteredMods,
useMuteLists,
useNSFWList
} from '../hooks'
import { appRoutes } from '../routes'
import '../styles/filters.css'
import '../styles/pagination.css'
import '../styles/search.css'
import '../styles/styles.css'
import { ModDetails } from '../types'
import {
FilterOptions,
ModDetails,
ModeratedFilter,
NSFWFilter,
SortBy
} from '../types'
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 = () => {
const [isFetching, setIsFetching] = useState(false)
const [mods, setMods] = useState<ModDetails[]>([])
@ -111,61 +89,13 @@ export const ModsPage = () => {
})
}, [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,
const filteredModList = useFilteredMods(
mods,
muteLists,
nsfwList
])
userState,
filterOptions,
nsfwList,
muteLists
)
return (
<>
@ -174,7 +104,7 @@ export const ModsPage = () => {
<div className='ContainerMain'>
<div className='IBMSecMainGroup IBMSecMainGroupAlt'>
<PageTitleRow />
<Filters
<ModFilter
filterOptions={filterOptions}
setFilterOptions={setFilterOptions}
/>
@ -261,154 +191,3 @@ const PageTitleRow = React.memo(() => {
</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>
)
}
)

View File

@ -3,6 +3,7 @@ import { ErrorBoundary } from 'components/ErrorBoundary'
import { GameCard } from 'components/GameCard'
import { LoadingSpinner } from 'components/LoadingSpinner'
import { ModCard } from 'components/ModCard'
import { ModFilter } from 'components/ModsFilter'
import { Pagination } from 'components/Pagination'
import { Profile } from 'components/ProfileSection'
import {
@ -11,7 +12,13 @@ import {
T_TAG_VALUE
} from 'constants.ts'
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 { Subscription } from 'nostr-tools/abstract-relay'
import React, {
@ -24,48 +31,40 @@ import React, {
} from 'react'
import { useSearchParams } from 'react-router-dom'
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'
enum SortByEnum {
Latest = 'Latest',
Oldest = 'Oldest',
Best_Rated = 'Best Rated',
Worst_Rated = 'Worst Rated'
}
enum ModeratedFilterEnum {
Moderated = 'Moderated',
Unmoderated = 'Unmoderated',
Unmoderated_Fully = 'Unmoderated Fully'
}
enum SearchingFilterEnum {
enum SearchKindEnum {
Mods = 'Mods',
Games = 'Games',
Users = 'Users'
}
interface FilterOptions {
sort: SortByEnum
moderated: ModeratedFilterEnum
searching: SearchingFilterEnum
source: string
}
export const SearchPage = () => {
const [searchParams] = useSearchParams()
const muteLists = useMuteLists()
const nsfwList = useNSFWList()
const searchTermRef = useRef<HTMLInputElement>(null)
const [searchKind, setSearchKind] = useState(
(searchParams.get('searching') as SearchKindEnum) || SearchKindEnum.Mods
)
const [filterOptions, setFilterOptions] = useState<FilterOptions>({
sort: SortByEnum.Latest,
moderated: ModeratedFilterEnum.Moderated,
sort: SortBy.Latest,
nsfw: NSFWFilter.Hide_NSFW,
source: window.location.host,
searching:
(searchParams.get('searching') as SearchingFilterEnum) ||
SearchingFilterEnum.Mods
moderated: ModeratedFilter.Moderated
})
const [searchTerm, setSearchTerm] = useState(
searchParams.get('searchTerm') || ''
)
@ -129,22 +128,25 @@ export const SearchPage = () => {
<Filters
filterOptions={filterOptions}
setFilterOptions={setFilterOptions}
searchKind={searchKind}
setSearchKind={setSearchKind}
/>
{filterOptions.searching === SearchingFilterEnum.Mods && (
{searchKind === SearchKindEnum.Mods && (
<ModsResult
searchTerm={searchTerm}
filterOptions={filterOptions}
muteLists={muteLists}
nsfwList={nsfwList}
/>
)}
{filterOptions.searching === SearchingFilterEnum.Users && (
{searchKind === SearchKindEnum.Users && (
<UsersResult
searchTerm={searchTerm}
muteLists={muteLists}
moderationFilter={filterOptions.moderated}
/>
)}
{filterOptions.searching === SearchingFilterEnum.Games && (
{searchKind === SearchKindEnum.Games && (
<GamesResult searchTerm={searchTerm} />
)}
</div>
@ -156,49 +158,30 @@ export const SearchPage = () => {
type FiltersProps = {
filterOptions: FilterOptions
setFilterOptions: Dispatch<SetStateAction<FilterOptions>>
searchKind: SearchKindEnum
setSearchKind: Dispatch<SetStateAction<SearchKindEnum>>
}
const Filters = React.memo(
({ filterOptions, setFilterOptions }: FiltersProps) => {
({
filterOptions,
setFilterOptions,
searchKind,
setSearchKind
}: FiltersProps) => {
const userState = useAppSelector((state) => state.user)
return (
<div className='IBMSecMain'>
<div className='FiltersMain'>
{filterOptions.searching === SearchingFilterEnum.Mods && (
<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>
{searchKind === SearchKindEnum.Mods && (
<ModFilter
filterOptions={filterOptions}
setFilterOptions={setFilterOptions}
/>
)}
{(filterOptions.searching === SearchingFilterEnum.Mods ||
filterOptions.searching === SearchingFilterEnum.Users) && (
{searchKind === SearchKindEnum.Users && (
<div className='FiltersMainElement'>
<div className='dropdown dropdownMain'>
<button
@ -210,8 +193,8 @@ const Filters = React.memo(
{filterOptions.moderated}
</button>
<div className='dropdown-menu dropdownMainMenu'>
{Object.values(ModeratedFilterEnum).map((item, index) => {
if (item === ModeratedFilterEnum.Unmoderated_Fully) {
{Object.values(ModeratedFilter).map((item, index) => {
if (item === ModeratedFilter.Unmoderated_Fully) {
const isAdmin =
userState.user?.npub ===
import.meta.env.VITE_REPORTING_NPUB
@ -239,7 +222,6 @@ const Filters = React.memo(
</div>
)}
{filterOptions.searching === SearchingFilterEnum.Mods && (
<div className='FiltersMainElement'>
<div className='dropdown dropdownMain'>
<button
@ -248,59 +230,14 @@ const Filters = React.memo(
data-bs-toggle='dropdown'
type='button'
>
{filterOptions.source === window.location.host
? `Show From: ${filterOptions.source}`
: 'Show All'}
Searching: {searchKind}
</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 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) => (
{Object.values(SearchKindEnum).map((item, index) => (
<div
key={`searchingFilterItem-${index}`}
className='dropdown-item dropdownMainMenuItem'
onClick={() =>
setFilterOptions((prev) => ({
...prev,
searching: item
}))
}
onClick={() => setSearchKind(item)}
>
{item}
</div>
@ -321,12 +258,14 @@ type ModsResultProps = {
admin: MuteLists
user: MuteLists
}
nsfwList: string[]
}
const ModsResult = ({
filterOptions,
searchTerm,
muteLists
muteLists,
nsfwList
}: ModsResultProps) => {
const hasEffectRun = useRef(false)
const [isSubscribing, setIsSubscribing] = useState(false)
@ -400,49 +339,13 @@ const ModsResult = ({
return mods.filter(filterFn)
}, [mods, searchTerm])
const filteredModList = useMemo(() => {
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
}, [
const filteredModList = useFilteredMods(
filteredMods,
userState.user?.npub,
filterOptions.sort,
filterOptions.moderated,
filterOptions.source,
userState,
filterOptions,
nsfwList,
muteLists
])
)
const handleNext = () => {
setPage((prev) => prev + 1)
@ -478,7 +381,7 @@ const ModsResult = ({
type UsersResultProps = {
searchTerm: string
moderationFilter: ModeratedFilterEnum
moderationFilter: ModeratedFilter
muteLists: {
admin: MuteLists
user: MuteLists
@ -528,7 +431,7 @@ const UsersResult = ({
let filtered = [...profiles]
const isAdmin = userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB
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"
if (!(isAdmin && isUnmoderatedFully)) {
@ -537,7 +440,7 @@ const UsersResult = ({
)
}
if (moderationFilter === ModeratedFilterEnum.Moderated) {
if (moderationFilter === ModeratedFilter.Moderated) {
filtered = filtered.filter(
(profile) => !muteLists.user.authors.includes(profile.pubkey as string)
)

View File

@ -1,4 +1,5 @@
export * from './mod'
export * from './modsFilter'
export * from './nostr'
export * from './user'
export * from './zap'

25
src/types/modsFilter.ts Normal file
View 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
}