Compare commits
No commits in common. "12b2fc1627632c8a8633cf4c5f49e8fcbb7a9966" and "e8833e72dbcc29c8df649c7d74599f5f42724bfd" have entirely different histories.
12b2fc1627
...
e8833e72db
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "degmods.com",
|
"name": "degmods.com",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0-alpha",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
@ -7909,7 +7909,7 @@ Lamborghini R6 125,,
|
|||||||
Farming Simulator 2013 Ursus,,
|
Farming Simulator 2013 Ursus,,
|
||||||
Cubemen Soundtrack,,
|
Cubemen Soundtrack,,
|
||||||
Nancy Drew: The Deadly Device,,
|
Nancy Drew: The Deadly Device,,
|
||||||
DmC Devil May Cry,,https://image.nostr.build/917da143fafe8a003865ec4dbbb872dcb04020fad2ca8d3c6cee00f2ac141bde.jpg
|
DmC Devil May Cry,,
|
||||||
Cargo Commander,,
|
Cargo Commander,,
|
||||||
Fairy Bloom Freesia Demo,,
|
Fairy Bloom Freesia Demo,,
|
||||||
Football Manager 2013 Russian Demo,,
|
Football Manager 2013 Russian Demo,,
|
||||||
@ -9057,7 +9057,7 @@ Hacker Evolution Duality Hardcore Package 1,,
|
|||||||
MC6T - Cakewalk Expansion Pack - Modern Strings,,
|
MC6T - Cakewalk Expansion Pack - Modern Strings,,
|
||||||
MC6T - Cakewalk Expansion Pack - Guitars,,
|
MC6T - Cakewalk Expansion Pack - Guitars,,
|
||||||
Sorcerer King,,
|
Sorcerer King,,
|
||||||
Assassin's Creed IV Black Flag,,https://image.nostr.build/6e468d37073f922b9442e03a9428e10425032dbae19a920316ebc32520c10713.jpg
|
Assassin's Creed IV Black Flag,,
|
||||||
Joe Danger 2: The Movie,,
|
Joe Danger 2: The Movie,,
|
||||||
Joe Danger 2: Undead Movie Pack,,
|
Joe Danger 2: Undead Movie Pack,,
|
||||||
Vector Thrust,,
|
Vector Thrust,,
|
||||||
@ -39626,7 +39626,7 @@ Red Bull 360: Get the ultimate 360 video experience of drifting,,
|
|||||||
8Doors: Arum's Afterlife Adventure,,
|
8Doors: Arum's Afterlife Adventure,,
|
||||||
Thomaz,,
|
Thomaz,,
|
||||||
Jack & the Cat,,
|
Jack & the Cat,,
|
||||||
Atomic Heart,,https://image.nostr.build/19cf6181c271a6e2d56dd275600a29b797569f3e054a162bae63ca46c255e772.jpg
|
Atomic Heart,,
|
||||||
Omen Exitio: Plague,,
|
Omen Exitio: Plague,,
|
||||||
Pixelum,,
|
Pixelum,,
|
||||||
Wars of Seignior,,
|
Wars of Seignior,,
|
||||||
|
Can't render this file because it is too large.
|
@ -1,20 +1,16 @@
|
|||||||
import { useAppSelector, useLocalStorage } from 'hooks'
|
import { useAppSelector } from 'hooks'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { Dispatch, SetStateAction } from 'react'
|
||||||
import { FilterOptions, ModeratedFilter, NSFWFilter, SortBy } from 'types'
|
import { FilterOptions, ModeratedFilter, NSFWFilter, SortBy } from 'types'
|
||||||
import { DEFAULT_FILTER_OPTIONS } from 'utils'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
author?: string | undefined
|
filterOptions: FilterOptions
|
||||||
filterKey?: string | undefined
|
setFilterOptions: Dispatch<SetStateAction<FilterOptions>>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ModFilter = React.memo(
|
export const ModFilter = React.memo(
|
||||||
({ author, filterKey = 'filter' }: Props) => {
|
({ filterOptions, setFilterOptions }: Props) => {
|
||||||
const userState = useAppSelector((state) => state.user)
|
const userState = useAppSelector((state) => state.user)
|
||||||
const [filterOptions, setFilterOptions] = useLocalStorage<FilterOptions>(
|
|
||||||
filterKey,
|
|
||||||
DEFAULT_FILTER_OPTIONS
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='IBMSecMain'>
|
<div className='IBMSecMain'>
|
||||||
@ -66,9 +62,9 @@ export const ModFilter = React.memo(
|
|||||||
import.meta.env.VITE_REPORTING_NPUB
|
import.meta.env.VITE_REPORTING_NPUB
|
||||||
|
|
||||||
const isOwnProfile =
|
const isOwnProfile =
|
||||||
author &&
|
filterOptions.author &&
|
||||||
userState.auth &&
|
userState.auth &&
|
||||||
userState.user?.pubkey === author
|
userState.user?.pubkey === filterOptions.author
|
||||||
|
|
||||||
if (!(isAdmin || isOwnProfile)) return null
|
if (!(isAdmin || isOwnProfile)) return null
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ import { appRoutes, getProfilePageRoute } from '../routes'
|
|||||||
import '../styles/author.css'
|
import '../styles/author.css'
|
||||||
import '../styles/innerPage.css'
|
import '../styles/innerPage.css'
|
||||||
import '../styles/socialPosts.css'
|
import '../styles/socialPosts.css'
|
||||||
import { UserRelaysType } from '../types'
|
import { UserProfile, UserRelaysType } from '../types'
|
||||||
import {
|
import {
|
||||||
copyTextToClipboard,
|
copyTextToClipboard,
|
||||||
hexToNpub,
|
hexToNpub,
|
||||||
@ -27,18 +27,37 @@ import { LoadingSpinner } from './LoadingSpinner'
|
|||||||
import { ZapPopUp } from './Zap'
|
import { ZapPopUp } from './Zap'
|
||||||
import placeholder from '../assets/img/DEGMods Placeholder Img.png'
|
import placeholder from '../assets/img/DEGMods Placeholder Img.png'
|
||||||
import { NDKEvent } from '@nostr-dev-kit/ndk'
|
import { NDKEvent } from '@nostr-dev-kit/ndk'
|
||||||
import { useProfile } from 'hooks/useProfile'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
pubkey: string
|
pubkey: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProfileSection = ({ pubkey }: Props) => {
|
export const ProfileSection = ({ pubkey }: Props) => {
|
||||||
|
const { findMetadata } = useNDKContext()
|
||||||
|
const [profile, setProfile] = useState<UserProfile>()
|
||||||
|
|
||||||
|
useDidMount(() => {
|
||||||
|
findMetadata(pubkey).then((res) => {
|
||||||
|
setProfile(res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const displayName =
|
||||||
|
profile?.displayName || profile?.name || '[name not set up]'
|
||||||
|
const about = profile?.bio || profile?.about || '[bio not set up]'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='IBMSMSplitMainSmallSide'>
|
<div className='IBMSMSplitMainSmallSide'>
|
||||||
<div className='IBMSMSplitMainSmallSideSecWrapper'>
|
<div className='IBMSMSplitMainSmallSideSecWrapper'>
|
||||||
<div className='IBMSMSplitMainSmallSideSec'>
|
<div className='IBMSMSplitMainSmallSideSec'>
|
||||||
<Profile pubkey={pubkey} />
|
<Profile
|
||||||
|
pubkey={pubkey}
|
||||||
|
displayName={displayName}
|
||||||
|
about={about}
|
||||||
|
image={profile?.image}
|
||||||
|
nip05={profile?.nip05}
|
||||||
|
lud16={profile?.lud16}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='IBMSMSplitMainSmallSideSec'>
|
<div className='IBMSMSplitMainSmallSideSec'>
|
||||||
<div className='IBMSMSMSSS_ShortPosts'>
|
<div className='IBMSMSMSSS_ShortPosts'>
|
||||||
@ -90,18 +109,21 @@ export const ProfileSection = ({ pubkey }: Props) => {
|
|||||||
|
|
||||||
type ProfileProps = {
|
type ProfileProps = {
|
||||||
pubkey: string
|
pubkey: string
|
||||||
|
displayName: string
|
||||||
|
about: string
|
||||||
|
image?: string
|
||||||
|
nip05?: string
|
||||||
|
lud16?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Profile = ({ pubkey }: ProfileProps) => {
|
export const Profile = ({
|
||||||
const profile = useProfile(pubkey)
|
pubkey,
|
||||||
|
displayName,
|
||||||
const displayName =
|
about,
|
||||||
profile?.displayName || profile?.name || '[name not set up]'
|
image,
|
||||||
const about = profile?.bio || profile?.about || '[bio not set up]'
|
nip05,
|
||||||
const image = profile?.image || FALLBACK_PROFILE_IMAGE
|
lud16
|
||||||
const nip05 = profile?.nip05
|
}: ProfileProps) => {
|
||||||
const lud16 = profile?.lud16
|
|
||||||
|
|
||||||
const npub = hexToNpub(pubkey)
|
const npub = hexToNpub(pubkey)
|
||||||
|
|
||||||
const handleCopy = async () => {
|
const handleCopy = async () => {
|
||||||
@ -116,20 +138,14 @@ export const Profile = ({ pubkey }: ProfileProps) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to encode
|
|
||||||
let profileRoute = appRoutes.home
|
let profileRoute = appRoutes.home
|
||||||
let nprofile: string | undefined
|
const hexPubkey = npubToHex(pubkey)
|
||||||
try {
|
if (hexPubkey) {
|
||||||
const hexPubkey = npubToHex(pubkey)
|
profileRoute = getProfilePageRoute(
|
||||||
nprofile = hexPubkey
|
nip19.nprofileEncode({
|
||||||
? nip19.nprofileEncode({
|
pubkey: hexPubkey
|
||||||
pubkey: hexPubkey
|
})
|
||||||
})
|
)
|
||||||
: undefined
|
|
||||||
profileRoute = nprofile ? getProfilePageRoute(nprofile) : appRoutes.home
|
|
||||||
} catch (error) {
|
|
||||||
// Silently ignore and redirect to home
|
|
||||||
log(true, LogType.Error, 'Failed to encode profile.', error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -146,7 +162,9 @@ export const Profile = ({ pubkey }: ProfileProps) => {
|
|||||||
<div
|
<div
|
||||||
className='IBMSMSMSSS_Author_Top_PP'
|
className='IBMSMSMSSS_Author_Top_PP'
|
||||||
style={{
|
style={{
|
||||||
background: `url('${image}') center / cover no-repeat`
|
background: `url('${
|
||||||
|
image || FALLBACK_PROFILE_IMAGE
|
||||||
|
}') center / cover no-repeat`
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
@ -154,8 +172,7 @@ export const Profile = ({ pubkey }: ProfileProps) => {
|
|||||||
<div className='IBMSMSMSSS_Author_Top_Left_InsideDetails'>
|
<div className='IBMSMSMSSS_Author_Top_Left_InsideDetails'>
|
||||||
<div className='IBMSMSMSSS_Author_TopWrapper'>
|
<div className='IBMSMSMSSS_Author_TopWrapper'>
|
||||||
<p className='IBMSMSMSSS_Author_Top_Name'>{displayName}</p>
|
<p className='IBMSMSMSSS_Author_Top_Name'>{displayName}</p>
|
||||||
{/* Nip05 can sometimes be an empty object '{}' which causes the error */}
|
{nip05 && (
|
||||||
{typeof nip05 === 'string' && (
|
|
||||||
<p className='IBMSMSMSSS_Author_Top_Handle'>{nip05}</p>
|
<p className='IBMSMSMSSS_Author_Top_Handle'>{nip05}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -188,12 +205,8 @@ export const Profile = ({ pubkey }: ProfileProps) => {
|
|||||||
<path d='M384 96L384 0h-112c-26.51 0-48 21.49-48 48v288c0 26.51 21.49 48 48 48H464c26.51 0 48-21.49 48-48V128h-95.1C398.4 128 384 113.6 384 96zM416 0v96h96L416 0zM192 352V128h-144c-26.51 0-48 21.49-48 48v288c0 26.51 21.49 48 48 48h192c26.51 0 48-21.49 48-48L288 416h-32C220.7 416 192 387.3 192 352z'></path>
|
<path d='M384 96L384 0h-112c-26.51 0-48 21.49-48 48v288c0 26.51 21.49 48 48 48H464c26.51 0 48-21.49 48-48V128h-95.1C398.4 128 384 113.6 384 96zM416 0v96h96L416 0zM192 352V128h-144c-26.51 0-48 21.49-48 48v288c0 26.51 21.49 48 48 48h192c26.51 0 48-21.49 48-48L288 416h-32C220.7 416 192 387.3 192 352z'></path>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
{typeof nprofile !== 'undefined' && (
|
<ProfileQRButtonWithPopUp pubkey={pubkey} />
|
||||||
<ProfileQRButtonWithPopUp nprofile={nprofile} />
|
{lud16 && <ZapButtonWithPopUp pubkey={pubkey} />}
|
||||||
)}
|
|
||||||
{typeof lud16 !== 'undefined' && (
|
|
||||||
<ZapButtonWithPopUp pubkey={pubkey} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -238,16 +251,20 @@ const posts: Post[] = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
type QRButtonWithPopUpProps = {
|
type QRButtonWithPopUpProps = {
|
||||||
nprofile: string
|
pubkey: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProfileQRButtonWithPopUp = ({
|
export const ProfileQRButtonWithPopUp = ({
|
||||||
nprofile
|
pubkey
|
||||||
}: QRButtonWithPopUpProps) => {
|
}: QRButtonWithPopUpProps) => {
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
|
||||||
useBodyScrollDisable(isOpen)
|
useBodyScrollDisable(isOpen)
|
||||||
|
|
||||||
|
const nprofile = nip19.nprofileEncode({
|
||||||
|
pubkey
|
||||||
|
})
|
||||||
|
|
||||||
const onQrCodeClicked = async () => {
|
const onQrCodeClicked = async () => {
|
||||||
const href = `https://njump.me/${nprofile}`
|
const href = `https://njump.me/${nprofile}`
|
||||||
const a = document.createElement('a')
|
const a = document.createElement('a')
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
import { forwardRef } from 'react'
|
|
||||||
|
|
||||||
interface SearchInputProps {
|
|
||||||
handleKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => void
|
|
||||||
handleSearch: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>(
|
|
||||||
({ handleKeyDown, handleSearch }, ref) => (
|
|
||||||
<div className='SearchMain'>
|
|
||||||
<div className='SearchMainInside'>
|
|
||||||
<div className='SearchMainInsideWrapper'>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='SMIWInput'
|
|
||||||
ref={ref}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
placeholder='Enter search term'
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className='btn btnMain SMIWButton'
|
|
||||||
type='button'
|
|
||||||
onClick={handleSearch}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
|
||||||
viewBox='0 0 512 512'
|
|
||||||
width='1em'
|
|
||||||
height='1em'
|
|
||||||
fill='currentColor'
|
|
||||||
>
|
|
||||||
<path d='M500.3 443.7l-119.7-119.7c27.22-40.41 40.65-90.9 33.46-144.7C401.8 87.79 326.8 13.32 235.2 1.723C99.01-15.51-15.51 99.01 1.724 235.2c11.6 91.64 86.08 166.7 177.6 178.9c53.8 7.189 104.3-6.236 144.7-33.46l119.7 119.7c15.62 15.62 40.95 15.62 56.57 0C515.9 484.7 515.9 459.3 500.3 443.7zM79.1 208c0-70.58 57.42-128 128-128s128 57.42 128 128c0 70.58-57.42 128-128 128S79.1 278.6 79.1 208z'></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
)
|
|
@ -7,4 +7,3 @@ export * from './useNSFWList'
|
|||||||
export * from './useReactions'
|
export * from './useReactions'
|
||||||
export * from './useNDKContext'
|
export * from './useNDKContext'
|
||||||
export * from './useScrollDisable'
|
export * from './useScrollDisable'
|
||||||
export * from './useLocalStorage'
|
|
||||||
|
@ -18,20 +18,10 @@ export const useFilteredMods = (
|
|||||||
muteLists: {
|
muteLists: {
|
||||||
admin: MuteLists
|
admin: MuteLists
|
||||||
user: MuteLists
|
user: MuteLists
|
||||||
},
|
}
|
||||||
author?: string | undefined
|
|
||||||
) => {
|
) => {
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const nsfwFilter = (mods: ModDetails[]) => {
|
const nsfwFilter = (mods: ModDetails[]) => {
|
||||||
// Add nsfw tag to mods included in nsfwList
|
|
||||||
if (filterOptions.nsfw !== NSFWFilter.Hide_NSFW) {
|
|
||||||
mods = mods.map((mod) => {
|
|
||||||
return !mod.nsfw && nsfwList.includes(mod.aTag)
|
|
||||||
? { ...mod, nsfw: true }
|
|
||||||
: mod
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the filtering logic based on the NSFW filter option
|
// Determine the filtering logic based on the NSFW filter option
|
||||||
switch (filterOptions.nsfw) {
|
switch (filterOptions.nsfw) {
|
||||||
case NSFWFilter.Hide_NSFW:
|
case NSFWFilter.Hide_NSFW:
|
||||||
@ -51,7 +41,7 @@ export const useFilteredMods = (
|
|||||||
const isAdmin = userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB
|
const isAdmin = userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB
|
||||||
const isOwner =
|
const isOwner =
|
||||||
userState.user?.npub &&
|
userState.user?.npub &&
|
||||||
npubToHex(userState.user.npub as string) === author
|
npubToHex(userState.user.npub as string) === filterOptions.author
|
||||||
const isUnmoderatedFully =
|
const isUnmoderatedFully =
|
||||||
filterOptions.moderated === ModeratedFilter.Unmoderated_Fully
|
filterOptions.moderated === ModeratedFilter.Unmoderated_Fully
|
||||||
|
|
||||||
@ -85,7 +75,7 @@ export const useFilteredMods = (
|
|||||||
filterOptions.sort,
|
filterOptions.sort,
|
||||||
filterOptions.moderated,
|
filterOptions.moderated,
|
||||||
filterOptions.nsfw,
|
filterOptions.nsfw,
|
||||||
author,
|
filterOptions.author,
|
||||||
mods,
|
mods,
|
||||||
muteLists,
|
muteLists,
|
||||||
nsfwList
|
nsfwList
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import {
|
|
||||||
getLocalStorageItem,
|
|
||||||
removeLocalStorageItem,
|
|
||||||
setLocalStorageItem
|
|
||||||
} from 'utils'
|
|
||||||
|
|
||||||
const useLocalStorageSubscribe = (callback: () => void) => {
|
|
||||||
window.addEventListener('storage', callback)
|
|
||||||
return () => window.removeEventListener('storage', callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useLocalStorage<T>(
|
|
||||||
key: string,
|
|
||||||
initialValue: T
|
|
||||||
): [T, React.Dispatch<React.SetStateAction<T>>] {
|
|
||||||
const getSnapshot = () => getLocalStorageItem(key, initialValue)
|
|
||||||
|
|
||||||
const data = React.useSyncExternalStore(useLocalStorageSubscribe, getSnapshot)
|
|
||||||
|
|
||||||
const setState: React.Dispatch<React.SetStateAction<T>> = React.useCallback(
|
|
||||||
(v: React.SetStateAction<T>) => {
|
|
||||||
try {
|
|
||||||
const nextState =
|
|
||||||
typeof v === 'function'
|
|
||||||
? (v as (prevState: T) => T)(JSON.parse(data))
|
|
||||||
: v
|
|
||||||
|
|
||||||
if (nextState === undefined || nextState === null) {
|
|
||||||
removeLocalStorageItem(key)
|
|
||||||
} else {
|
|
||||||
setLocalStorageItem(key, JSON.stringify(nextState))
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(e)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[key, data]
|
|
||||||
)
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
// Set local storage only when it's empty
|
|
||||||
const data = window.localStorage.getItem(key)
|
|
||||||
if (data === null) {
|
|
||||||
setLocalStorageItem(key, JSON.stringify(initialValue))
|
|
||||||
}
|
|
||||||
}, [key, initialValue])
|
|
||||||
|
|
||||||
return [JSON.parse(data) as T, setState]
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
import { useNDKContext } from 'hooks'
|
|
||||||
import { useState, useEffect } from 'react'
|
|
||||||
import { UserProfile } from 'types'
|
|
||||||
|
|
||||||
export const useProfile = (pubkey?: string) => {
|
|
||||||
const { findMetadata } = useNDKContext()
|
|
||||||
const [profile, setProfile] = useState<UserProfile>()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (pubkey) {
|
|
||||||
findMetadata(pubkey).then((res) => {
|
|
||||||
setProfile(res)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [findMetadata, pubkey])
|
|
||||||
|
|
||||||
return profile
|
|
||||||
}
|
|
@ -1,28 +1,13 @@
|
|||||||
import { useAppSelector } from 'hooks'
|
import { useAppSelector } from 'hooks'
|
||||||
import { nip19 } from 'nostr-tools'
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { NavLink, NavLinkProps } from 'react-router-dom'
|
import { NavLink, NavLinkProps } from 'react-router-dom'
|
||||||
import { appRoutes, getProfilePageRoute } from 'routes'
|
import { appRoutes, getProfilePageRoute } from 'routes'
|
||||||
import 'styles/socialNav.css'
|
import 'styles/socialNav.css'
|
||||||
import { npubToHex } from 'utils'
|
|
||||||
|
|
||||||
export const SocialNav = () => {
|
export const SocialNav = () => {
|
||||||
const [isCollapsed, setIsCollapsed] = useState<boolean>(false)
|
const [isCollapsed, setIsCollapsed] = useState<boolean>(false)
|
||||||
const userState = useAppSelector((state) => state.user)
|
const userState = useAppSelector((state) => state.user)
|
||||||
|
|
||||||
let profileRoute = ''
|
|
||||||
if (userState.auth && userState.user) {
|
|
||||||
// Redirect to user's profile is no profile is linked
|
|
||||||
const userHexKey = npubToHex(userState.user.npub as string)
|
|
||||||
|
|
||||||
if (userHexKey) {
|
|
||||||
profileRoute = getProfilePageRoute(
|
|
||||||
nip19.nprofileEncode({
|
|
||||||
pubkey: userHexKey
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const toggleNav = () => {
|
const toggleNav = () => {
|
||||||
setIsCollapsed(!isCollapsed)
|
setIsCollapsed(!isCollapsed)
|
||||||
}
|
}
|
||||||
@ -57,7 +42,7 @@ export const SocialNav = () => {
|
|||||||
/>
|
/>
|
||||||
{!!userState.auth && (
|
{!!userState.auth && (
|
||||||
<NavButton
|
<NavButton
|
||||||
to={profileRoute}
|
to={getProfilePageRoute('')}
|
||||||
svgPath='M256 288c79.53 0 144-64.47 144-144s-64.47-144-144-144c-79.52 0-144 64.47-144 144S176.5 288 256 288zM351.1 320H160c-88.36 0-160 71.63-160 160c0 17.67 14.33 32 31.1 32H480c17.67 0 31.1-14.33 31.1-32C512 391.6 440.4 320 351.1 320z'
|
svgPath='M256 288c79.53 0 144-64.47 144-144s-64.47-144-144-144c-79.52 0-144 64.47-144 144S176.5 288 256 288zM351.1 320H160c-88.36 0-160 71.63-160 160c0 17.67 14.33 32 31.1 32H480c17.67 0 31.1-14.33 31.1-32C512 391.6 440.4 320 351.1 320z'
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -6,25 +6,24 @@ import {
|
|||||||
import { ModCard } from 'components/ModCard'
|
import { ModCard } from 'components/ModCard'
|
||||||
import { ModFilter } from 'components/ModsFilter'
|
import { ModFilter } from 'components/ModsFilter'
|
||||||
import { PaginationWithPageNumbers } from 'components/Pagination'
|
import { PaginationWithPageNumbers } from 'components/Pagination'
|
||||||
import { SearchInput } from 'components/SearchInput'
|
|
||||||
import { MAX_MODS_PER_PAGE, T_TAG_VALUE } from 'constants.ts'
|
import { MAX_MODS_PER_PAGE, T_TAG_VALUE } from 'constants.ts'
|
||||||
import {
|
import {
|
||||||
useAppSelector,
|
useAppSelector,
|
||||||
useFilteredMods,
|
useFilteredMods,
|
||||||
useLocalStorage,
|
|
||||||
useMuteLists,
|
useMuteLists,
|
||||||
useNDKContext,
|
useNDKContext,
|
||||||
useNSFWList
|
useNSFWList
|
||||||
} from 'hooks'
|
} from 'hooks'
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { useParams, useSearchParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import { FilterOptions, ModDetails } from 'types'
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_FILTER_OPTIONS,
|
FilterOptions,
|
||||||
extractModData,
|
ModDetails,
|
||||||
isModDataComplete,
|
ModeratedFilter,
|
||||||
scrollIntoView
|
NSFWFilter,
|
||||||
} from 'utils'
|
SortBy
|
||||||
|
} from 'types'
|
||||||
|
import { extractModData, isModDataComplete, scrollIntoView } from 'utils'
|
||||||
|
|
||||||
export const GamePage = () => {
|
export const GamePage = () => {
|
||||||
const scrollTargetRef = useRef<HTMLDivElement>(null)
|
const scrollTargetRef = useRef<HTMLDivElement>(null)
|
||||||
@ -34,70 +33,20 @@ export const GamePage = () => {
|
|||||||
const muteLists = useMuteLists()
|
const muteLists = useMuteLists()
|
||||||
const nsfwList = useNSFWList()
|
const nsfwList = useNSFWList()
|
||||||
|
|
||||||
const [filterOptions] = useLocalStorage<FilterOptions>(
|
const [filterOptions, setFilterOptions] = useState<FilterOptions>({
|
||||||
'filter',
|
sort: SortBy.Latest,
|
||||||
DEFAULT_FILTER_OPTIONS
|
nsfw: NSFWFilter.Hide_NSFW,
|
||||||
)
|
source: window.location.host,
|
||||||
|
moderated: ModeratedFilter.Moderated
|
||||||
|
})
|
||||||
const [mods, setMods] = useState<ModDetails[]>([])
|
const [mods, setMods] = useState<ModDetails[]>([])
|
||||||
|
|
||||||
const [currentPage, setCurrentPage] = useState(1)
|
const [currentPage, setCurrentPage] = useState(1)
|
||||||
|
|
||||||
const userState = useAppSelector((state) => state.user)
|
const userState = useAppSelector((state) => state.user)
|
||||||
|
|
||||||
// Search
|
const filteredMods = useFilteredMods(
|
||||||
const searchTermRef = useRef<HTMLInputElement>(null)
|
mods,
|
||||||
const [searchParams, setSearchParams] = useSearchParams()
|
|
||||||
const [searchTerm, setSearchTerm] = useState(searchParams.get('q') || '')
|
|
||||||
|
|
||||||
const handleSearch = () => {
|
|
||||||
const value = searchTermRef.current?.value || '' // Access the input value from the ref
|
|
||||||
setSearchTerm(value)
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
searchParams.set('q', value)
|
|
||||||
} else {
|
|
||||||
searchParams.delete('q')
|
|
||||||
}
|
|
||||||
|
|
||||||
setSearchParams(searchParams, {
|
|
||||||
replace: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle "Enter" key press inside the input
|
|
||||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
handleSearch()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const filteredMods = useMemo(() => {
|
|
||||||
const filterSourceFn = (mod: ModDetails) => {
|
|
||||||
if (filterOptions.source === window.location.host) {
|
|
||||||
return mod.rTag === filterOptions.source
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// If search term is missing, only filter by sources
|
|
||||||
if (searchTerm === '') return mods.filter(filterSourceFn)
|
|
||||||
|
|
||||||
const lowerCaseSearchTerm = searchTerm.toLowerCase()
|
|
||||||
|
|
||||||
const filterFn = (mod: ModDetails) =>
|
|
||||||
mod.title.toLowerCase().includes(lowerCaseSearchTerm) ||
|
|
||||||
mod.game.toLowerCase().includes(lowerCaseSearchTerm) ||
|
|
||||||
mod.summary.toLowerCase().includes(lowerCaseSearchTerm) ||
|
|
||||||
mod.body.toLowerCase().includes(lowerCaseSearchTerm) ||
|
|
||||||
mod.tags.findIndex((tag) =>
|
|
||||||
tag.toLowerCase().includes(lowerCaseSearchTerm)
|
|
||||||
) > -1
|
|
||||||
|
|
||||||
return mods.filter(filterFn).filter(filterSourceFn)
|
|
||||||
}, [filterOptions.source, mods, searchTerm])
|
|
||||||
|
|
||||||
const filteredModList = useFilteredMods(
|
|
||||||
filteredMods,
|
|
||||||
userState,
|
userState,
|
||||||
filterOptions,
|
filterOptions,
|
||||||
nsfwList,
|
nsfwList,
|
||||||
@ -105,11 +54,11 @@ export const GamePage = () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Pagination logic
|
// Pagination logic
|
||||||
const totalGames = filteredModList.length
|
const totalGames = filteredMods.length
|
||||||
const totalPages = Math.ceil(totalGames / MAX_MODS_PER_PAGE)
|
const totalPages = Math.ceil(totalGames / MAX_MODS_PER_PAGE)
|
||||||
const startIndex = (currentPage - 1) * MAX_MODS_PER_PAGE
|
const startIndex = (currentPage - 1) * MAX_MODS_PER_PAGE
|
||||||
const endIndex = startIndex + MAX_MODS_PER_PAGE
|
const endIndex = startIndex + MAX_MODS_PER_PAGE
|
||||||
const currentMods = filteredModList.slice(startIndex, endIndex)
|
const currentMods = filteredMods.slice(startIndex, endIndex)
|
||||||
|
|
||||||
const handlePageChange = (page: number) => {
|
const handlePageChange = (page: number) => {
|
||||||
if (page >= 1 && page <= totalPages) {
|
if (page >= 1 && page <= totalPages) {
|
||||||
@ -167,24 +116,14 @@ export const GamePage = () => {
|
|||||||
<span className='IBMSMTitleMainHeadingSpan'>
|
<span className='IBMSMTitleMainHeadingSpan'>
|
||||||
{gameName}
|
{gameName}
|
||||||
</span>
|
</span>
|
||||||
{searchTerm !== '' && (
|
|
||||||
<>
|
|
||||||
—
|
|
||||||
<span className='IBMSMTitleMainHeadingSpan'>
|
|
||||||
{searchTerm}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<SearchInput
|
|
||||||
handleKeyDown={handleKeyDown}
|
|
||||||
handleSearch={handleSearch}
|
|
||||||
ref={searchTermRef}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ModFilter />
|
<ModFilter
|
||||||
|
filterOptions={filterOptions}
|
||||||
|
setFilterOptions={setFilterOptions}
|
||||||
|
/>
|
||||||
<div className='IBMSecMain IBMSMListWrapper'>
|
<div className='IBMSecMain IBMSMListWrapper'>
|
||||||
<div className='IBMSMList'>
|
<div className='IBMSMList'>
|
||||||
{currentMods.map((mod) => (
|
{currentMods.map((mod) => (
|
||||||
|
@ -9,7 +9,6 @@ import '../styles/styles.css'
|
|||||||
import { createSearchParams, useNavigate } from 'react-router-dom'
|
import { createSearchParams, useNavigate } from 'react-router-dom'
|
||||||
import { appRoutes } from 'routes'
|
import { appRoutes } from 'routes'
|
||||||
import { scrollIntoView } from 'utils'
|
import { scrollIntoView } from 'utils'
|
||||||
import { SearchInput } from 'components/SearchInput'
|
|
||||||
|
|
||||||
export const GamesPage = () => {
|
export const GamesPage = () => {
|
||||||
const scrollTargetRef = useRef<HTMLDivElement>(null)
|
const scrollTargetRef = useRef<HTMLDivElement>(null)
|
||||||
@ -75,8 +74,8 @@ export const GamesPage = () => {
|
|||||||
const value = searchTermRef.current?.value || '' // Access the input value from the ref
|
const value = searchTermRef.current?.value || '' // Access the input value from the ref
|
||||||
if (value !== '') {
|
if (value !== '') {
|
||||||
const searchParams = createSearchParams({
|
const searchParams = createSearchParams({
|
||||||
q: value,
|
searchTerm: value,
|
||||||
kind: 'Games'
|
searching: 'Games'
|
||||||
})
|
})
|
||||||
navigate({ pathname: appRoutes.search, search: `?${searchParams}` })
|
navigate({ pathname: appRoutes.search, search: `?${searchParams}` })
|
||||||
}
|
}
|
||||||
@ -101,11 +100,34 @@ export const GamesPage = () => {
|
|||||||
<div className='IBMSMTitleMain'>
|
<div className='IBMSMTitleMain'>
|
||||||
<h2 className='IBMSMTitleMainHeading'>Games</h2>
|
<h2 className='IBMSMTitleMainHeading'>Games</h2>
|
||||||
</div>
|
</div>
|
||||||
<SearchInput
|
<div className='SearchMain'>
|
||||||
ref={searchTermRef}
|
<div className='SearchMainInside'>
|
||||||
handleKeyDown={handleKeyDown}
|
<div className='SearchMainInsideWrapper'>
|
||||||
handleSearch={handleSearch}
|
<input
|
||||||
/>
|
type='text'
|
||||||
|
className='SMIWInput'
|
||||||
|
ref={searchTermRef}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
placeholder='Enter search term'
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className='btn btnMain SMIWButton'
|
||||||
|
type='button'
|
||||||
|
onClick={handleSearch}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
viewBox='0 0 512 512'
|
||||||
|
width='1em'
|
||||||
|
height='1em'
|
||||||
|
fill='currentColor'
|
||||||
|
>
|
||||||
|
<path d='M500.3 443.7l-119.7-119.7c27.22-40.41 40.65-90.9 33.46-144.7C401.8 87.79 326.8 13.32 235.2 1.723C99.01-15.51-15.51 99.01 1.724 235.2c11.6 91.64 86.08 166.7 177.6 178.9c53.8 7.189 104.3-6.236 144.7-33.46l119.7 119.7c15.62 15.62 40.95 15.62 56.57 0C515.9 484.7 515.9 459.3 500.3 443.7zM79.1 208c0-70.58 57.42-128 128-128s128 57.42 128 128c0 70.58-57.42 128-128 128S79.1 278.6 79.1 208z'></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='IBMSecMain IBMSMListWrapper'>
|
<div className='IBMSecMain IBMSMListWrapper'>
|
||||||
|
@ -15,8 +15,7 @@ import {
|
|||||||
useAppSelector,
|
useAppSelector,
|
||||||
useBodyScrollDisable,
|
useBodyScrollDisable,
|
||||||
useDidMount,
|
useDidMount,
|
||||||
useNDKContext,
|
useNDKContext
|
||||||
useNSFWList
|
|
||||||
} from '../../hooks'
|
} from '../../hooks'
|
||||||
import { getGamePageRoute, getModsEditPageRoute } from '../../routes'
|
import { getGamePageRoute, getModsEditPageRoute } from '../../routes'
|
||||||
import '../../styles/comments.css'
|
import '../../styles/comments.css'
|
||||||
@ -52,18 +51,10 @@ import placeholder from '../../assets/img/DEGMods Placeholder Img.png'
|
|||||||
export const ModPage = () => {
|
export const ModPage = () => {
|
||||||
const { naddr } = useParams()
|
const { naddr } = useParams()
|
||||||
const { fetchEvent } = useNDKContext()
|
const { fetchEvent } = useNDKContext()
|
||||||
const [mod, setMod] = useState<ModDetails>()
|
const [modData, setModData] = useState<ModDetails>()
|
||||||
const [isFetching, setIsFetching] = useState(true)
|
const [isFetching, setIsFetching] = useState(true)
|
||||||
const [commentCount, setCommentCount] = useState(0)
|
const [commentCount, setCommentCount] = useState(0)
|
||||||
|
|
||||||
// Make sure to mark non-nsfw mods as NSFW if found in nsfwList
|
|
||||||
const nsfwList = useNSFWList()
|
|
||||||
const isMissingNsfwTag =
|
|
||||||
!mod?.nsfw && mod?.aTag && nsfwList && nsfwList.includes(mod.aTag)
|
|
||||||
const modData = isMissingNsfwTag
|
|
||||||
? ({ ...mod, nsfw: true } as ModDetails)
|
|
||||||
: mod
|
|
||||||
|
|
||||||
useDidMount(async () => {
|
useDidMount(async () => {
|
||||||
if (naddr) {
|
if (naddr) {
|
||||||
const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`)
|
const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`)
|
||||||
@ -79,7 +70,7 @@ export const ModPage = () => {
|
|||||||
.then((event) => {
|
.then((event) => {
|
||||||
if (event) {
|
if (event) {
|
||||||
const extracted = extractModData(event)
|
const extracted = extractModData(event)
|
||||||
setMod(extracted)
|
setModData(extracted)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
@ -8,7 +8,6 @@ import { MOD_FILTER_LIMIT } from '../constants'
|
|||||||
import {
|
import {
|
||||||
useAppSelector,
|
useAppSelector,
|
||||||
useFilteredMods,
|
useFilteredMods,
|
||||||
useLocalStorage,
|
|
||||||
useMuteLists,
|
useMuteLists,
|
||||||
useNDKContext,
|
useNDKContext,
|
||||||
useNSFWList
|
useNSFWList
|
||||||
@ -18,20 +17,26 @@ 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 { FilterOptions, ModDetails } from '../types'
|
import {
|
||||||
import { DEFAULT_FILTER_OPTIONS, scrollIntoView } from 'utils'
|
FilterOptions,
|
||||||
import { SearchInput } from 'components/SearchInput'
|
ModDetails,
|
||||||
|
ModeratedFilter,
|
||||||
|
NSFWFilter,
|
||||||
|
SortBy
|
||||||
|
} from '../types'
|
||||||
|
import { scrollIntoView } from 'utils'
|
||||||
|
|
||||||
export const ModsPage = () => {
|
export const ModsPage = () => {
|
||||||
const scrollTargetRef = useRef<HTMLDivElement>(null)
|
const scrollTargetRef = useRef<HTMLDivElement>(null)
|
||||||
const { fetchMods } = useNDKContext()
|
const { fetchMods } = useNDKContext()
|
||||||
const [isFetching, setIsFetching] = useState(false)
|
const [isFetching, setIsFetching] = useState(false)
|
||||||
const [mods, setMods] = useState<ModDetails[]>([])
|
const [mods, setMods] = useState<ModDetails[]>([])
|
||||||
|
const [filterOptions, setFilterOptions] = useState<FilterOptions>({
|
||||||
const [filterOptions] = useLocalStorage<FilterOptions>(
|
sort: SortBy.Latest,
|
||||||
'filter',
|
nsfw: NSFWFilter.Hide_NSFW,
|
||||||
DEFAULT_FILTER_OPTIONS
|
source: window.location.host,
|
||||||
)
|
moderated: ModeratedFilter.Moderated
|
||||||
|
})
|
||||||
const muteLists = useMuteLists()
|
const muteLists = useMuteLists()
|
||||||
const nsfwList = useNSFWList()
|
const nsfwList = useNSFWList()
|
||||||
|
|
||||||
@ -107,7 +112,10 @@ export const ModsPage = () => {
|
|||||||
ref={scrollTargetRef}
|
ref={scrollTargetRef}
|
||||||
>
|
>
|
||||||
<PageTitleRow />
|
<PageTitleRow />
|
||||||
<ModFilter />
|
<ModFilter
|
||||||
|
filterOptions={filterOptions}
|
||||||
|
setFilterOptions={setFilterOptions}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className='IBMSecMain IBMSMListWrapper'>
|
<div className='IBMSecMain IBMSMListWrapper'>
|
||||||
<div className='IBMSMList'>
|
<div className='IBMSMList'>
|
||||||
@ -138,8 +146,8 @@ const PageTitleRow = React.memo(() => {
|
|||||||
const value = searchTermRef.current?.value || '' // Access the input value from the ref
|
const value = searchTermRef.current?.value || '' // Access the input value from the ref
|
||||||
if (value !== '') {
|
if (value !== '') {
|
||||||
const searchParams = createSearchParams({
|
const searchParams = createSearchParams({
|
||||||
q: value,
|
searchTerm: value,
|
||||||
kind: 'Mods'
|
searching: 'Mods'
|
||||||
})
|
})
|
||||||
navigate({ pathname: appRoutes.search, search: `?${searchParams}` })
|
navigate({ pathname: appRoutes.search, search: `?${searchParams}` })
|
||||||
}
|
}
|
||||||
@ -158,11 +166,35 @@ const PageTitleRow = React.memo(() => {
|
|||||||
<div className='IBMSMTitleMain'>
|
<div className='IBMSMTitleMain'>
|
||||||
<h2 className='IBMSMTitleMainHeading'>Mods</h2>
|
<h2 className='IBMSMTitleMainHeading'>Mods</h2>
|
||||||
</div>
|
</div>
|
||||||
<SearchInput
|
<div className='SearchMain'>
|
||||||
ref={searchTermRef}
|
<div className='SearchMainInside'>
|
||||||
handleKeyDown={handleKeyDown}
|
<div className='SearchMainInsideWrapper'>
|
||||||
handleSearch={handleSearch}
|
<input
|
||||||
/>
|
type='text'
|
||||||
|
className='SMIWInput'
|
||||||
|
ref={searchTermRef}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
placeholder='Enter search term'
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className='btn btnMain SMIWButton'
|
||||||
|
type='button'
|
||||||
|
onClick={handleSearch}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
viewBox='0 0 512 512'
|
||||||
|
width='1em'
|
||||||
|
height='1em'
|
||||||
|
fill='currentColor'
|
||||||
|
>
|
||||||
|
<path d='M500.3 443.7l-119.7-119.7c27.22-40.41 40.65-90.9 33.46-144.7C401.8 87.79 326.8 13.32 235.2 1.723C99.01-15.51-15.51 99.01 1.724 235.2c11.6 91.64 86.08 166.7 177.6 178.9c53.8 7.189 104.3-6.236 144.7-33.46l119.7 119.7c15.62 15.62 40.95 15.62 56.57 0C515.9 484.7 515.9 459.3 500.3 443.7zM79.1 208c0-70.58 57.42-128 128-128s128 57.42 128 128c0 70.58-57.42 128-128 128S79.1 278.6 79.1 208z'></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -8,8 +8,8 @@ import { Tabs } from 'components/Tabs'
|
|||||||
import { MOD_FILTER_LIMIT } from '../constants'
|
import { MOD_FILTER_LIMIT } from '../constants'
|
||||||
import {
|
import {
|
||||||
useAppSelector,
|
useAppSelector,
|
||||||
|
useDidMount,
|
||||||
useFilteredMods,
|
useFilteredMods,
|
||||||
useLocalStorage,
|
|
||||||
useMuteLists,
|
useMuteLists,
|
||||||
useNDKContext,
|
useNDKContext,
|
||||||
useNSFWList
|
useNSFWList
|
||||||
@ -19,12 +19,17 @@ import { useCallback, useEffect, useRef, useState } from 'react'
|
|||||||
import { useParams, Navigate, Link } from 'react-router-dom'
|
import { useParams, Navigate, Link } from 'react-router-dom'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { appRoutes, getProfilePageRoute } from 'routes'
|
import { appRoutes, getProfilePageRoute } from 'routes'
|
||||||
import { FilterOptions, ModDetails, UserRelaysType } from 'types'
|
import {
|
||||||
|
FilterOptions,
|
||||||
|
ModDetails,
|
||||||
|
ModeratedFilter,
|
||||||
|
NSFWFilter,
|
||||||
|
SortBy,
|
||||||
|
UserProfile,
|
||||||
|
UserRelaysType
|
||||||
|
} from 'types'
|
||||||
import {
|
import {
|
||||||
copyTextToClipboard,
|
copyTextToClipboard,
|
||||||
DEFAULT_FILTER_OPTIONS,
|
|
||||||
log,
|
|
||||||
LogType,
|
|
||||||
now,
|
now,
|
||||||
npubToHex,
|
npubToHex,
|
||||||
scrollIntoView,
|
scrollIntoView,
|
||||||
@ -32,7 +37,6 @@ import {
|
|||||||
signAndPublish
|
signAndPublish
|
||||||
} from 'utils'
|
} from 'utils'
|
||||||
import { CheckboxField } from 'components/Inputs'
|
import { CheckboxField } from 'components/Inputs'
|
||||||
import { useProfile } from 'hooks/useProfile'
|
|
||||||
|
|
||||||
export const ProfilePage = () => {
|
export const ProfilePage = () => {
|
||||||
// Try to decode nprofile parameter
|
// Try to decode nprofile parameter
|
||||||
@ -44,19 +48,27 @@ export const ProfilePage = () => {
|
|||||||
: undefined
|
: undefined
|
||||||
profilePubkey = value?.data.pubkey
|
profilePubkey = value?.data.pubkey
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// Failed to decode the nprofile
|
||||||
// Silently ignore and redirect to home or logged in user
|
// Silently ignore and redirect to home or logged in user
|
||||||
log(true, LogType.Error, 'Failed to decode nprofile.', error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const scrollTargetRef = useRef<HTMLDivElement>(null)
|
const scrollTargetRef = useRef<HTMLDivElement>(null)
|
||||||
const { ndk, publish, fetchEventFromUserRelays, fetchMods } = useNDKContext()
|
const { ndk, publish, findMetadata, fetchEventFromUserRelays, fetchMods } =
|
||||||
|
useNDKContext()
|
||||||
|
const [profile, setProfile] = useState<UserProfile>()
|
||||||
const userState = useAppSelector((state) => state.user)
|
const userState = useAppSelector((state) => state.user)
|
||||||
const isOwnProfile =
|
const isOwnProfile =
|
||||||
userState.auth && userState.user?.pubkey === profilePubkey
|
userState.auth && userState.user?.pubkey === profilePubkey
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
||||||
|
|
||||||
const profile = useProfile(profilePubkey)
|
useDidMount(() => {
|
||||||
|
if (profilePubkey) {
|
||||||
|
findMetadata(profilePubkey).then((res) => {
|
||||||
|
setProfile(res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const displayName =
|
const displayName =
|
||||||
profile?.displayName || profile?.name || '[name not set up]'
|
profile?.displayName || profile?.name || '[name not set up]'
|
||||||
@ -209,7 +221,7 @@ export const ProfilePage = () => {
|
|||||||
kind: NDKKind.MuteList,
|
kind: NDKKind.MuteList,
|
||||||
content: muteListEvent.content,
|
content: muteListEvent.content,
|
||||||
created_at: now(),
|
created_at: now(),
|
||||||
tags: tags.filter((item) => item[0] !== 'p' || item[1] !== profilePubkey)
|
tags: tags.filter((item) => item[0] !== 'a' || item[1] !== profilePubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Updating mute list event')
|
setLoadingSpinnerDesc('Updating mute list event')
|
||||||
@ -227,9 +239,12 @@ export const ProfilePage = () => {
|
|||||||
|
|
||||||
// Mods
|
// Mods
|
||||||
const [mods, setMods] = useState<ModDetails[]>([])
|
const [mods, setMods] = useState<ModDetails[]>([])
|
||||||
const filterKey = 'filter-profile'
|
const [filterOptions, setFilterOptions] = useState<FilterOptions>({
|
||||||
const [filterOptions] = useLocalStorage<FilterOptions>(filterKey, {
|
sort: SortBy.Latest,
|
||||||
...DEFAULT_FILTER_OPTIONS
|
nsfw: NSFWFilter.Hide_NSFW,
|
||||||
|
source: window.location.host,
|
||||||
|
moderated: ModeratedFilter.Moderated,
|
||||||
|
author: profilePubkey
|
||||||
})
|
})
|
||||||
const muteLists = useMuteLists()
|
const muteLists = useMuteLists()
|
||||||
const nsfwList = useNSFWList()
|
const nsfwList = useNSFWList()
|
||||||
@ -298,8 +313,7 @@ export const ProfilePage = () => {
|
|||||||
userState,
|
userState,
|
||||||
filterOptions,
|
filterOptions,
|
||||||
nsfwList,
|
nsfwList,
|
||||||
muteLists,
|
muteLists
|
||||||
profilePubkey
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Redirect route
|
// Redirect route
|
||||||
@ -465,7 +479,10 @@ export const ProfilePage = () => {
|
|||||||
{/* Tabs Content */}
|
{/* Tabs Content */}
|
||||||
{tab === 0 && (
|
{tab === 0 && (
|
||||||
<>
|
<>
|
||||||
<ModFilter filterKey={filterKey} author={profilePubkey} />
|
<ModFilter
|
||||||
|
filterOptions={filterOptions}
|
||||||
|
setFilterOptions={setFilterOptions}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className='IBMSMList IBMSMListAlt'>
|
<div className='IBMSMList IBMSMListAlt'>
|
||||||
{filteredModList.map((mod) => (
|
{filteredModList.map((mod) => (
|
||||||
|
@ -4,16 +4,15 @@ import {
|
|||||||
NDKKind,
|
NDKKind,
|
||||||
NDKSubscriptionCacheUsage,
|
NDKSubscriptionCacheUsage,
|
||||||
NDKUserProfile,
|
NDKUserProfile,
|
||||||
NostrEvent,
|
|
||||||
profileFromEvent
|
profileFromEvent
|
||||||
} from '@nostr-dev-kit/ndk'
|
} from '@nostr-dev-kit/ndk'
|
||||||
import { ErrorBoundary } from 'components/ErrorBoundary'
|
import { ErrorBoundary } from 'components/ErrorBoundary'
|
||||||
import { GameCard } from 'components/GameCard'
|
import { GameCard } from 'components/GameCard'
|
||||||
|
import { LoadingSpinner } from 'components/LoadingSpinner'
|
||||||
import { ModCard } from 'components/ModCard'
|
import { ModCard } from 'components/ModCard'
|
||||||
import { ModFilter } from 'components/ModsFilter'
|
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 { SearchInput } from 'components/SearchInput'
|
|
||||||
import {
|
import {
|
||||||
MAX_GAMES_PER_PAGE,
|
MAX_GAMES_PER_PAGE,
|
||||||
MAX_MODS_PER_PAGE,
|
MAX_MODS_PER_PAGE,
|
||||||
@ -23,18 +22,32 @@ import {
|
|||||||
useAppSelector,
|
useAppSelector,
|
||||||
useFilteredMods,
|
useFilteredMods,
|
||||||
useGames,
|
useGames,
|
||||||
useLocalStorage,
|
|
||||||
useMuteLists,
|
useMuteLists,
|
||||||
useNDKContext,
|
useNDKContext,
|
||||||
useNSFWList
|
useNSFWList
|
||||||
} from 'hooks'
|
} from 'hooks'
|
||||||
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
import React, {
|
||||||
|
Dispatch,
|
||||||
|
SetStateAction,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState
|
||||||
|
} from 'react'
|
||||||
import { useSearchParams } from 'react-router-dom'
|
import { useSearchParams } from 'react-router-dom'
|
||||||
import { FilterOptions, ModDetails, ModeratedFilter, MuteLists } from 'types'
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_FILTER_OPTIONS,
|
FilterOptions,
|
||||||
|
ModDetails,
|
||||||
|
ModeratedFilter,
|
||||||
|
MuteLists,
|
||||||
|
NSFWFilter,
|
||||||
|
SortBy
|
||||||
|
} from 'types'
|
||||||
|
import {
|
||||||
extractModData,
|
extractModData,
|
||||||
isModDataComplete,
|
isModDataComplete,
|
||||||
|
log,
|
||||||
|
LogType,
|
||||||
scrollIntoView
|
scrollIntoView
|
||||||
} from 'utils'
|
} from 'utils'
|
||||||
|
|
||||||
@ -46,35 +59,30 @@ enum SearchKindEnum {
|
|||||||
|
|
||||||
export const SearchPage = () => {
|
export const SearchPage = () => {
|
||||||
const scrollTargetRef = useRef<HTMLDivElement>(null)
|
const scrollTargetRef = useRef<HTMLDivElement>(null)
|
||||||
const [searchParams, setSearchParams] = useSearchParams()
|
const [searchParams] = useSearchParams()
|
||||||
|
|
||||||
const muteLists = useMuteLists()
|
const muteLists = useMuteLists()
|
||||||
const nsfwList = useNSFWList()
|
const nsfwList = useNSFWList()
|
||||||
const searchTermRef = useRef<HTMLInputElement>(null)
|
const searchTermRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
const searchKind =
|
const [searchKind, setSearchKind] = useState(
|
||||||
(searchParams.get('kind') as SearchKindEnum) || SearchKindEnum.Mods
|
(searchParams.get('searching') as SearchKindEnum) || SearchKindEnum.Mods
|
||||||
|
|
||||||
const [filterOptions] = useLocalStorage<FilterOptions>(
|
|
||||||
'filter',
|
|
||||||
DEFAULT_FILTER_OPTIONS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const [searchTerm, setSearchTerm] = useState(searchParams.get('q') || '')
|
const [filterOptions, setFilterOptions] = useState<FilterOptions>({
|
||||||
|
sort: SortBy.Latest,
|
||||||
|
nsfw: NSFWFilter.Hide_NSFW,
|
||||||
|
source: window.location.host,
|
||||||
|
moderated: ModeratedFilter.Moderated
|
||||||
|
})
|
||||||
|
|
||||||
|
const [searchTerm, setSearchTerm] = useState(
|
||||||
|
searchParams.get('searchTerm') || ''
|
||||||
|
)
|
||||||
|
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
const value = searchTermRef.current?.value || '' // Access the input value from the ref
|
const value = searchTermRef.current?.value || '' // Access the input value from the ref
|
||||||
setSearchTerm(value)
|
setSearchTerm(value)
|
||||||
|
|
||||||
if (value) {
|
|
||||||
searchParams.set('q', value)
|
|
||||||
} else {
|
|
||||||
searchParams.delete('q')
|
|
||||||
}
|
|
||||||
|
|
||||||
setSearchParams(searchParams, {
|
|
||||||
replace: true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle "Enter" key press inside the input
|
// Handle "Enter" key press inside the input
|
||||||
@ -101,14 +109,42 @@ export const SearchPage = () => {
|
|||||||
</span>
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<SearchInput
|
<div className='SearchMain'>
|
||||||
handleKeyDown={handleKeyDown}
|
<div className='SearchMainInside'>
|
||||||
handleSearch={handleSearch}
|
<div className='SearchMainInsideWrapper'>
|
||||||
ref={searchTermRef}
|
<input
|
||||||
/>
|
type='text'
|
||||||
|
className='SMIWInput'
|
||||||
|
ref={searchTermRef}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
placeholder='Enter search term'
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className='btn btnMain SMIWButton'
|
||||||
|
type='button'
|
||||||
|
onClick={handleSearch}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
viewBox='0 0 512 512'
|
||||||
|
width='1em'
|
||||||
|
height='1em'
|
||||||
|
fill='currentColor'
|
||||||
|
>
|
||||||
|
<path d='M500.3 443.7l-119.7-119.7c27.22-40.41 40.65-90.9 33.46-144.7C401.8 87.79 326.8 13.32 235.2 1.723C99.01-15.51-15.51 99.01 1.724 235.2c11.6 91.64 86.08 166.7 177.6 178.9c53.8 7.189 104.3-6.236 144.7-33.46l119.7 119.7c15.62 15.62 40.95 15.62 56.57 0C515.9 484.7 515.9 459.3 500.3 443.7zM79.1 208c0-70.58 57.42-128 128-128s128 57.42 128 128c0 70.58-57.42 128-128 128S79.1 278.6 79.1 208z'></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Filters />
|
<Filters
|
||||||
|
filterOptions={filterOptions}
|
||||||
|
setFilterOptions={setFilterOptions}
|
||||||
|
searchKind={searchKind}
|
||||||
|
setSearchKind={setSearchKind}
|
||||||
|
/>
|
||||||
{searchKind === SearchKindEnum.Mods && (
|
{searchKind === SearchKindEnum.Mods && (
|
||||||
<ModsResult
|
<ModsResult
|
||||||
searchTerm={searchTerm}
|
searchTerm={searchTerm}
|
||||||
@ -134,29 +170,73 @@ export const SearchPage = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Filters = React.memo(() => {
|
type FiltersProps = {
|
||||||
const [filterOptions, setFilterOptions] = useLocalStorage<FilterOptions>(
|
filterOptions: FilterOptions
|
||||||
'filter',
|
setFilterOptions: Dispatch<SetStateAction<FilterOptions>>
|
||||||
DEFAULT_FILTER_OPTIONS
|
searchKind: SearchKindEnum
|
||||||
)
|
setSearchKind: Dispatch<SetStateAction<SearchKindEnum>>
|
||||||
|
}
|
||||||
|
|
||||||
const userState = useAppSelector((state) => state.user)
|
const Filters = React.memo(
|
||||||
const [searchParams, setSearchParams] = useSearchParams()
|
({
|
||||||
const searchKind =
|
filterOptions,
|
||||||
(searchParams.get('kind') as SearchKindEnum) || SearchKindEnum.Mods
|
setFilterOptions,
|
||||||
const handleChangeSearchKind = (kind: SearchKindEnum) => {
|
searchKind,
|
||||||
searchParams.set('kind', kind)
|
setSearchKind
|
||||||
setSearchParams(searchParams, {
|
}: FiltersProps) => {
|
||||||
replace: true
|
const userState = useAppSelector((state) => state.user)
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='IBMSecMain'>
|
<div className='IBMSecMain'>
|
||||||
<div className='FiltersMain'>
|
<div className='FiltersMain'>
|
||||||
{searchKind === SearchKindEnum.Mods && <ModFilter />}
|
{searchKind === SearchKindEnum.Mods && (
|
||||||
|
<ModFilter
|
||||||
|
filterOptions={filterOptions}
|
||||||
|
setFilterOptions={setFilterOptions}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{searchKind === SearchKindEnum.Users && (
|
||||||
|
<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>
|
||||||
|
)}
|
||||||
|
|
||||||
{searchKind === SearchKindEnum.Users && (
|
|
||||||
<div className='FiltersMainElement'>
|
<div className='FiltersMainElement'>
|
||||||
<div className='dropdown dropdownMain'>
|
<div className='dropdown dropdownMain'>
|
||||||
<button
|
<button
|
||||||
@ -165,65 +245,26 @@ const Filters = React.memo(() => {
|
|||||||
data-bs-toggle='dropdown'
|
data-bs-toggle='dropdown'
|
||||||
type='button'
|
type='button'
|
||||||
>
|
>
|
||||||
{filterOptions.moderated}
|
Searching: {searchKind}
|
||||||
</button>
|
</button>
|
||||||
<div className='dropdown-menu dropdownMainMenu'>
|
<div className='dropdown-menu dropdownMainMenu'>
|
||||||
{Object.values(ModeratedFilter).map((item, index) => {
|
{Object.values(SearchKindEnum).map((item, index) => (
|
||||||
if (item === ModeratedFilter.Unmoderated_Fully) {
|
<div
|
||||||
const isAdmin =
|
key={`searchingFilterItem-${index}`}
|
||||||
userState.user?.npub ===
|
className='dropdown-item dropdownMainMenuItem'
|
||||||
import.meta.env.VITE_REPORTING_NPUB
|
onClick={() => setSearchKind(item)}
|
||||||
|
>
|
||||||
if (!isAdmin) return null
|
{item}
|
||||||
}
|
</div>
|
||||||
|
))}
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={`moderatedFilterItem-${index}`}
|
|
||||||
className='dropdown-item dropdownMainMenuItem'
|
|
||||||
onClick={() =>
|
|
||||||
setFilterOptions((prev) => ({
|
|
||||||
...prev,
|
|
||||||
moderated: item
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{item}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</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: {searchKind}
|
|
||||||
</button>
|
|
||||||
<div className='dropdown-menu dropdownMainMenu'>
|
|
||||||
{Object.values(SearchKindEnum).map((item, index) => (
|
|
||||||
<div
|
|
||||||
key={`searchingFilterItem-${index}`}
|
|
||||||
className='dropdown-item dropdownMainMenuItem'
|
|
||||||
onClick={() => handleChangeSearchKind(item)}
|
|
||||||
>
|
|
||||||
{item}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)
|
||||||
)
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
type ModsResultProps = {
|
type ModsResultProps = {
|
||||||
filterOptions: FilterOptions
|
filterOptions: FilterOptions
|
||||||
@ -283,7 +324,6 @@ const ModsResult = ({
|
|||||||
}, [searchTerm])
|
}, [searchTerm])
|
||||||
|
|
||||||
const filteredMods = useMemo(() => {
|
const filteredMods = useMemo(() => {
|
||||||
// Search page requires search term
|
|
||||||
if (searchTerm === '') return []
|
if (searchTerm === '') return []
|
||||||
|
|
||||||
const lowerCaseSearchTerm = searchTerm.toLowerCase()
|
const lowerCaseSearchTerm = searchTerm.toLowerCase()
|
||||||
@ -297,16 +337,8 @@ const ModsResult = ({
|
|||||||
tag.toLowerCase().includes(lowerCaseSearchTerm)
|
tag.toLowerCase().includes(lowerCaseSearchTerm)
|
||||||
) > -1
|
) > -1
|
||||||
|
|
||||||
const filterSourceFn = (mod: ModDetails) => {
|
return mods.filter(filterFn)
|
||||||
// Filter by source if selected
|
}, [mods, searchTerm])
|
||||||
if (filterOptions.source === window.location.host) {
|
|
||||||
return mod.rTag === filterOptions.source
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return mods.filter(filterFn).filter(filterSourceFn)
|
|
||||||
}, [filterOptions.source, mods, searchTerm])
|
|
||||||
|
|
||||||
const filteredModList = useFilteredMods(
|
const filteredModList = useFilteredMods(
|
||||||
filteredMods,
|
filteredMods,
|
||||||
@ -361,70 +393,39 @@ const UsersResult = ({
|
|||||||
moderationFilter,
|
moderationFilter,
|
||||||
muteLists
|
muteLists
|
||||||
}: UsersResultProps) => {
|
}: UsersResultProps) => {
|
||||||
const { ndk } = useNDKContext()
|
const { fetchEvents } = useNDKContext()
|
||||||
|
const [isFetching, setIsFetching] = useState(false)
|
||||||
const [profiles, setProfiles] = useState<NDKUserProfile[]>([])
|
const [profiles, setProfiles] = useState<NDKUserProfile[]>([])
|
||||||
|
|
||||||
const userState = useAppSelector((state) => state.user)
|
const userState = useAppSelector((state) => state.user)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (searchTerm === '') {
|
if (searchTerm === '') {
|
||||||
setProfiles([])
|
setProfiles([])
|
||||||
} else {
|
} else {
|
||||||
const sub = ndk.subscribe(
|
const filter: NDKFilter = {
|
||||||
{
|
kinds: [NDKKind.Metadata],
|
||||||
kinds: [NDKKind.Metadata],
|
search: searchTerm
|
||||||
search: searchTerm
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
closeOnEose: true,
|
|
||||||
cacheUsage: NDKSubscriptionCacheUsage.PARALLEL
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
|
|
||||||
// Stop the sub after 10 seconds if we are still searching the same term as before
|
setIsFetching(true)
|
||||||
window.setTimeout(() => {
|
fetchEvents(filter)
|
||||||
if (sub.filter.search === searchTerm) {
|
.then((events) => {
|
||||||
sub.stop()
|
const results = events.map((event) => {
|
||||||
}
|
const ndkEvent = new NDKEvent(undefined, event)
|
||||||
}, 10000)
|
const profile = profileFromEvent(ndkEvent)
|
||||||
|
return profile
|
||||||
const onEvent = (event: NostrEvent | NDKEvent) => {
|
})
|
||||||
if (!(event instanceof NDKEvent)) event = new NDKEvent(undefined, event)
|
setProfiles(results)
|
||||||
const dedupKey = event.deduplicationKey()
|
})
|
||||||
const existingEvent = events.get(dedupKey)
|
.catch((err) => {
|
||||||
if (existingEvent) {
|
log(true, LogType.Error, 'An error occurred in fetching users', err)
|
||||||
event = dedup(existingEvent, event)
|
})
|
||||||
}
|
.finally(() => {
|
||||||
event.ndk = this
|
setIsFetching(false)
|
||||||
events.set(dedupKey, event)
|
|
||||||
|
|
||||||
// We can't rely on the 'eose' to arrive
|
|
||||||
// Instead we repeat and sort results on each event
|
|
||||||
const ndkEvents = Array.from(events.values())
|
|
||||||
const profiles: NDKUserProfile[] = []
|
|
||||||
ndkEvents.forEach((event) => {
|
|
||||||
try {
|
|
||||||
const profile = profileFromEvent(event)
|
|
||||||
profiles.push(profile)
|
|
||||||
} catch (error) {
|
|
||||||
// If we are unable to parse silently skip over the errors
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
setProfiles(profiles)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear previous results
|
|
||||||
const events = new Map<string, NDKEvent>()
|
|
||||||
|
|
||||||
// Bind handler and start the sub
|
|
||||||
sub.on('event', onEvent)
|
|
||||||
sub.start()
|
|
||||||
return () => {
|
|
||||||
sub.stop()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [ndk, searchTerm])
|
}, [searchTerm, fetchEvents])
|
||||||
|
|
||||||
const filteredProfiles = useMemo(() => {
|
const filteredProfiles = useMemo(() => {
|
||||||
let filtered = [...profiles]
|
let filtered = [...profiles]
|
||||||
@ -449,13 +450,25 @@ const UsersResult = ({
|
|||||||
}, [userState.user?.npub, moderationFilter, profiles, muteLists])
|
}, [userState.user?.npub, moderationFilter, profiles, muteLists])
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{isFetching && <LoadingSpinner desc='Fetching Profiles' />}
|
||||||
<div className='IBMSecMain IBMSMListWrapper'>
|
<div className='IBMSecMain IBMSMListWrapper'>
|
||||||
<div className='IBMSMList'>
|
<div className='IBMSMList'>
|
||||||
{filteredProfiles.map((profile) => {
|
{filteredProfiles.map((profile) => {
|
||||||
if (profile.pubkey) {
|
if (profile.pubkey) {
|
||||||
|
const displayName =
|
||||||
|
profile?.displayName || profile?.name || '[name not set up]'
|
||||||
|
const about = profile?.bio || profile?.about || '[bio not set up]'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary key={profile.pubkey}>
|
<ErrorBoundary key={profile.pubkey}>
|
||||||
<Profile pubkey={profile.pubkey as string} />
|
<Profile
|
||||||
|
pubkey={profile.pubkey as string}
|
||||||
|
displayName={displayName}
|
||||||
|
about={about}
|
||||||
|
image={profile?.image}
|
||||||
|
nip05={profile?.nip05}
|
||||||
|
lud16={profile?.lud16}
|
||||||
|
/>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -523,11 +536,3 @@ const GamesResult = ({ searchTerm }: GamesResultProps) => {
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
function dedup(event1: NDKEvent, event2: NDKEvent) {
|
|
||||||
// return the newest of the two
|
|
||||||
if (event1.created_at! > event2.created_at!) {
|
|
||||||
return event1
|
|
||||||
}
|
|
||||||
|
|
||||||
return event2
|
|
||||||
}
|
|
||||||
|
@ -98,15 +98,15 @@ export const ProfileSettings = () => {
|
|||||||
|
|
||||||
// In case user is not logged in clicking on profile link will navigate to homepage
|
// In case user is not logged in clicking on profile link will navigate to homepage
|
||||||
let profileRoute = appRoutes.home
|
let profileRoute = appRoutes.home
|
||||||
let nprofile: string | undefined
|
|
||||||
if (userState.auth && userState.user) {
|
if (userState.auth && userState.user) {
|
||||||
const hexPubkey = npubToHex(userState.user.npub as string)
|
const hexPubkey = npubToHex(userState.user.npub as string)
|
||||||
|
|
||||||
if (hexPubkey) {
|
if (hexPubkey) {
|
||||||
nprofile = nip19.nprofileEncode({
|
profileRoute = getProfilePageRoute(
|
||||||
pubkey: hexPubkey
|
nip19.nprofileEncode({
|
||||||
})
|
pubkey: hexPubkey
|
||||||
profileRoute = getProfilePageRoute(nprofile)
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,8 +247,10 @@ export const ProfileSettings = () => {
|
|||||||
<path d='M384 96L384 0h-112c-26.51 0-48 21.49-48 48v288c0 26.51 21.49 48 48 48H464c26.51 0 48-21.49 48-48V128h-95.1C398.4 128 384 113.6 384 96zM416 0v96h96L416 0zM192 352V128h-144c-26.51 0-48 21.49-48 48v288c0 26.51 21.49 48 48 48h192c26.51 0 48-21.49 48-48L288 416h-32C220.7 416 192 387.3 192 352z'></path>
|
<path d='M384 96L384 0h-112c-26.51 0-48 21.49-48 48v288c0 26.51 21.49 48 48 48H464c26.51 0 48-21.49 48-48V128h-95.1C398.4 128 384 113.6 384 96zM416 0v96h96L416 0zM192 352V128h-144c-26.51 0-48 21.49-48 48v288c0 26.51 21.49 48 48 48h192c26.51 0 48-21.49 48-48L288 416h-32C220.7 416 192 387.3 192 352z'></path>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
{typeof nprofile !== 'undefined' && (
|
{typeof userState.user?.pubkey === 'string' && (
|
||||||
<ProfileQRButtonWithPopUp nprofile={nprofile} />
|
<ProfileQRButtonWithPopUp
|
||||||
|
pubkey={userState.user.pubkey}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -155,12 +155,3 @@
|
|||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
background: rgba(35, 35, 35, 0.85);
|
background: rgba(35, 35, 35, 0.85);
|
||||||
}
|
}
|
||||||
|
|
||||||
.IBMSMSMBSSTagsTag.IBMSMSMBSSTagsTagRepost.IBMSMSMBSSTagsTagRepostCard {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 10px;
|
|
||||||
left: 10px;
|
|
||||||
-webkit-backdrop-filter: blur(10px);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
background: #232323d9;
|
|
||||||
}
|
|
||||||
|
@ -44,13 +44,3 @@
|
|||||||
cursor: default;
|
cursor: default;
|
||||||
box-shadow: unset;
|
box-shadow: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.IBMSMSMBSSTagsTag.IBMSMSMBSSTagsTagRepost {
|
|
||||||
background: #ffffff1a;
|
|
||||||
color: #ffffff59;
|
|
||||||
font-weight: 700;
|
|
||||||
border: unset;
|
|
||||||
font-size: 14px;
|
|
||||||
cursor: default;
|
|
||||||
box-shadow: unset;
|
|
||||||
}
|
|
@ -22,4 +22,5 @@ export interface FilterOptions {
|
|||||||
nsfw: NSFWFilter
|
nsfw: NSFWFilter
|
||||||
source: string
|
source: string
|
||||||
moderated: ModeratedFilter
|
moderated: ModeratedFilter
|
||||||
|
author?: string
|
||||||
}
|
}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
import { FilterOptions, SortBy, NSFWFilter, ModeratedFilter } from 'types'
|
|
||||||
|
|
||||||
export const DEFAULT_FILTER_OPTIONS: FilterOptions = {
|
|
||||||
sort: SortBy.Latest,
|
|
||||||
nsfw: NSFWFilter.Hide_NSFW,
|
|
||||||
source: window.location.host,
|
|
||||||
moderated: ModeratedFilter.Moderated
|
|
||||||
}
|
|
@ -3,5 +3,3 @@ export * from './nostr'
|
|||||||
export * from './url'
|
export * from './url'
|
||||||
export * from './utils'
|
export * from './utils'
|
||||||
export * from './zap'
|
export * from './zap'
|
||||||
export * from './localStorage'
|
|
||||||
export * from './consts'
|
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
export function getLocalStorageItem<T>(key: string, defaultValue: T): string {
|
|
||||||
try {
|
|
||||||
const data = window.localStorage.getItem(key)
|
|
||||||
if (data === null) return JSON.stringify(defaultValue)
|
|
||||||
return data
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`Error while fetching local storage value: `, err)
|
|
||||||
return JSON.stringify(defaultValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setLocalStorageItem(key: string, value: string) {
|
|
||||||
try {
|
|
||||||
window.localStorage.setItem(key, value)
|
|
||||||
dispatchLocalStorageEvent(key, value)
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`Error while saving local storage value: `, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeLocalStorageItem(key: string) {
|
|
||||||
try {
|
|
||||||
window.localStorage.removeItem(key)
|
|
||||||
dispatchLocalStorageEvent(key, null)
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`Error while deleting local storage value: `, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function dispatchLocalStorageEvent(key: string, newValue: string | null) {
|
|
||||||
window.dispatchEvent(new StorageEvent('storage', { key, newValue }))
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user