import { NDKFilter, NDKKind } from '@nostr-dev-kit/ndk' import { LoadingSpinner } from 'components/LoadingSpinner' import { ModCard } from 'components/ModCard' import { ModFilter } from 'components/ModsFilter' import { Pagination } from 'components/Pagination' import { ProfileSection } from 'components/ProfileSection' import { Tabs } from 'components/Tabs' import { MOD_FILTER_LIMIT } from '../constants' import { useAppSelector, useFilteredMods, useLocalStorage, useMuteLists, useNDKContext, useNSFWList } from 'hooks' import { nip19, UnsignedEvent } from 'nostr-tools' import { useCallback, useEffect, useRef, useState } from 'react' import { useParams, Navigate, Link } from 'react-router-dom' import { toast } from 'react-toastify' import { appRoutes, getProfilePageRoute } from 'routes' import { FilterOptions, ModDetails, UserRelaysType } from 'types' import { copyTextToClipboard, DEFAULT_FILTER_OPTIONS, now, npubToHex, scrollIntoView, sendDMUsingRandomKey, signAndPublish } from 'utils' import { CheckboxField } from 'components/Inputs' import { useProfile } from 'hooks/useProfile' export const ProfilePage = () => { // Try to decode nprofile parameter const { nprofile } = useParams() let profilePubkey: string | undefined try { const value = nprofile ? nip19.decode(nprofile as `nprofile1${string}`) : undefined profilePubkey = value?.data.pubkey } catch (error) { // Failed to decode the nprofile // Silently ignore and redirect to home or logged in user } const scrollTargetRef = useRef(null) const { ndk, publish, fetchEventFromUserRelays, fetchMods } = useNDKContext() const userState = useAppSelector((state) => state.user) const isOwnProfile = userState.auth && userState.user?.pubkey === profilePubkey const [isLoading, setIsLoading] = useState(false) const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('') const profile = useProfile(profilePubkey) const displayName = profile?.displayName || profile?.name || '[name not set up]' const [showReportPopUp, setShowReportPopUp] = useState(false) const [isBlocked, setIsBlocked] = useState(false) useEffect(() => { if (userState.auth && userState.user?.pubkey) { const userHexKey = userState.user.pubkey as string const muteListFilter: NDKFilter = { kinds: [NDKKind.MuteList], authors: [userHexKey] } fetchEventFromUserRelays( muteListFilter, userHexKey, UserRelaysType.Write ).then((event) => { if (event) { // get a list of tags const tags = event.tags const blocked = tags.findIndex( (item) => item[0] === 'p' && item[1] === profilePubkey ) !== -1 setIsBlocked(blocked) } }) } }, [userState, profilePubkey, fetchEventFromUserRelays]) const handleBlock = async () => { if (!profilePubkey) { toast.error(`Something went wrong. Unable to find reported user's pubkey`) return } let userHexKey: string setIsLoading(true) setLoadingSpinnerDesc('Getting user pubkey') if (userState.auth && userState.user?.pubkey) { userHexKey = userState.user.pubkey as string } else { userHexKey = (await window.nostr?.getPublicKey()) as string } if (!userHexKey) { toast.error('Could not get pubkey for updating mute list') setIsLoading(false) return } setLoadingSpinnerDesc(`Finding user's mute list`) // Define the event filter to search for the user's mute list events. // We look for events of a specific kind (Mutelist) authored by the given hexPubkey. const filter: NDKFilter = { kinds: [NDKKind.MuteList], authors: [userHexKey] } // Fetch the mute list event from the relays. This returns the event containing the user's mute list. const muteListEvent = await fetchEventFromUserRelays( filter, userHexKey, UserRelaysType.Write ) let unsignedEvent: UnsignedEvent if (muteListEvent) { // get a list of tags const tags = muteListEvent.tags const alreadyExists = tags.findIndex( (item) => item[0] === 'p' && item[1] === profilePubkey ) !== -1 if (alreadyExists) { setIsLoading(false) setIsBlocked(true) return toast.warn(`User is already in the mute list`) } tags.push(['p', profilePubkey]) unsignedEvent = { pubkey: muteListEvent.pubkey, kind: NDKKind.MuteList, content: muteListEvent.content, created_at: now(), tags: [...tags] } } else { unsignedEvent = { pubkey: userHexKey, kind: NDKKind.MuteList, content: '', created_at: now(), tags: [['p', profilePubkey]] } } setLoadingSpinnerDesc('Updating mute list event') const isUpdated = await signAndPublish(unsignedEvent, ndk, publish) if (isUpdated) { setIsBlocked(true) } setIsLoading(false) } const handleUnblock = async () => { if (!profilePubkey) { toast.error(`Something went wrong. Unable to find reported user's pubkey`) return } const userHexKey = userState.user?.pubkey as string const filter: NDKFilter = { kinds: [NDKKind.MuteList], authors: [userHexKey] } setIsLoading(true) setLoadingSpinnerDesc(`Finding user's mute list`) // Fetch the mute list event from the relays. This returns the event containing the user's mute list. const muteListEvent = await fetchEventFromUserRelays( filter, userHexKey, UserRelaysType.Write ) if (!muteListEvent) { toast.error(`Couldn't get user's mute list event from relays`) return } const tags = muteListEvent.tags const unsignedEvent: UnsignedEvent = { pubkey: muteListEvent.pubkey, kind: NDKKind.MuteList, content: muteListEvent.content, created_at: now(), tags: tags.filter((item) => item[0] !== 'p' || item[1] !== profilePubkey) } setLoadingSpinnerDesc('Updating mute list event') const isUpdated = await signAndPublish(unsignedEvent, ndk, publish) if (isUpdated) { setIsBlocked(false) } setIsLoading(false) } // Tabs const [tab, setTab] = useState(0) const [page, setPage] = useState(1) // Mods const [mods, setMods] = useState([]) const filterKey = 'filter-profile' const [filterOptions] = useLocalStorage(filterKey, { ...DEFAULT_FILTER_OPTIONS }) const muteLists = useMuteLists() const nsfwList = useNSFWList() const handleNext = useCallback(() => { setIsLoading(true) const until = mods.length > 0 ? mods[mods.length - 1].published_at - 1 : undefined fetchMods({ source: filterOptions.source, until, author: profilePubkey }) .then((res) => { setMods(res) setPage((prev) => prev + 1) scrollIntoView(scrollTargetRef.current) }) .finally(() => { setIsLoading(false) }) }, [mods, fetchMods, filterOptions.source, profilePubkey]) const handlePrev = useCallback(() => { setIsLoading(true) const since = mods.length > 0 ? mods[0].published_at + 1 : undefined fetchMods({ source: filterOptions.source, since, author: profilePubkey }) .then((res) => { setMods(res) setPage((prev) => prev - 1) scrollIntoView(scrollTargetRef.current) }) .finally(() => { setIsLoading(false) }) }, [mods, fetchMods, filterOptions.source, profilePubkey]) useEffect(() => { setIsLoading(true) switch (tab) { case 0: fetchMods({ source: filterOptions.source, author: profilePubkey }) .then((res) => { setMods(res) }) .finally(() => { setIsLoading(false) }) break default: setIsLoading(false) break } }, [filterOptions.source, tab, fetchMods, profilePubkey]) const filteredModList = useFilteredMods( mods, userState, filterOptions, nsfwList, muteLists, profilePubkey ) // Redirect route let profileRoute = appRoutes.home if (!nprofile && 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 }) ) } } if (!profilePubkey) return return (
{/* Tabs Content */} {tab === 0 && ( <>
{filteredModList.map((mod) => ( ))}
)} {tab === 1 && <>WIP} {tab === 2 && <>WIP}
{showReportPopUp && ( setShowReportPopUp(false)} /> )}
) } type ReportUserPopupProps = { reportedPubkey: string handleClose: () => void } const USER_REPORT_REASONS = [ { label: `User posts actual CP`, key: 'user_actuallyCP' }, { label: `User is a spammer`, key: 'user_spam' }, { label: `User is a scammer`, key: 'user_scam' }, { label: `User posts malware`, key: 'user_malware' }, { label: `User posts non-mods`, key: 'user_notAGameMod' }, { label: `User doesn't tag NSFW`, key: 'user_wasntTaggedNSFW' }, { label: `Other (user)`, key: 'user_otherReason' } ] const ReportUserPopup = ({ reportedPubkey, handleClose }: ReportUserPopupProps) => { const { ndk, fetchEventFromUserRelays, publish } = useNDKContext() const userState = useAppSelector((state) => state.user) const [selectedOptions, setSelectedOptions] = useState( USER_REPORT_REASONS.reduce((acc: { [key: string]: boolean }, cur) => { acc[cur.key] = false return acc }, {}) ) const [isLoading, setIsLoading] = useState(false) const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('') const handleCheckboxChange = (option: keyof typeof selectedOptions) => { setSelectedOptions((prevState) => ({ ...prevState, [option]: !prevState[option] })) } const handleSubmit = async () => { const selectedOptionsCount = Object.values(selectedOptions).filter( (isSelected) => isSelected ).length if (selectedOptionsCount === 0) { toast.error('At least one option should be checked!') return } setIsLoading(true) setLoadingSpinnerDesc('Getting user pubkey') let userHexKey: string if (userState.auth && userState.user?.pubkey) { userHexKey = userState.user.pubkey as string } else { userHexKey = (await window.nostr?.getPublicKey()) as string } if (!userHexKey) { toast.error('Could not get pubkey for reporting user!') setIsLoading(false) return } const reportingNpub = import.meta.env.VITE_REPORTING_NPUB const reportingPubkey = npubToHex(reportingNpub) if (reportingPubkey === userHexKey) { setLoadingSpinnerDesc(`Finding user's mute list`) // Define the event filter to search for the user's mute list events. // We look for events of a specific kind (Mutelist) authored by the given hexPubkey. const filter: NDKFilter = { kinds: [NDKKind.MuteList], authors: [userHexKey] } // Fetch the mute list event from the relays. This returns the event containing the user's mute list. const muteListEvent = await fetchEventFromUserRelays( filter, userHexKey, UserRelaysType.Write ) let unsignedEvent: UnsignedEvent if (muteListEvent) { // get a list of tags const tags = muteListEvent.tags const alreadyExists = tags.findIndex( (item) => item[0] === 'p' && item[1] === reportedPubkey ) !== -1 if (alreadyExists) { setIsLoading(false) return toast.warn( `Reporter user's pubkey is already in the mute list` ) } tags.push(['p', reportedPubkey]) unsignedEvent = { pubkey: muteListEvent.pubkey, kind: NDKKind.MuteList, content: muteListEvent.content, created_at: now(), tags: [...tags] } } else { unsignedEvent = { pubkey: userHexKey, kind: NDKKind.MuteList, content: '', created_at: now(), tags: [['p', reportedPubkey]] } } setLoadingSpinnerDesc('Updating mute list event') const isUpdated = await signAndPublish(unsignedEvent, ndk, publish) if (isUpdated) handleClose() } else { const href = window.location.href let message = `I'd like to report ${href} due to following reasons:\n` Object.entries(selectedOptions).forEach(([key, value]) => { if (value) { message += `* ${key}\n` } }) setLoadingSpinnerDesc('Sending report') const isSent = await sendDMUsingRandomKey( message, reportingPubkey!, ndk, publish ) if (isSent) handleClose() } setIsLoading(false) } return ( <> {isLoading && }

Report Post

{USER_REPORT_REASONS.map((r) => ( handleCheckboxChange(r.key)} /> ))}
) }