import { formatDate } from 'date-fns' import { Filter, nip19 } from 'nostr-tools' import { Dispatch, SetStateAction, useCallback, useRef, useState } from 'react' import { useNavigate, useParams } from 'react-router-dom' import { toast } from 'react-toastify' import { BlogCard } from '../components/BlogCard' import { LoadingSpinner } from '../components/LoadingSpinner' import { ProfileSection } from '../components/ProfileSection' import { MetadataController, RelayController, ZapController } from '../controllers' import { useAppSelector, useDidMount } from '../hooks' import '../styles/comments.css' import '../styles/downloads.css' import '../styles/innerPage.css' import '../styles/popup.css' import '../styles/post.css' import '../styles/reactions.css' import '../styles/styles.css' import '../styles/tabs.css' import '../styles/tags.css' import '../styles/write.css' import { DownloadUrl, ModDetails, PaymentRequest } from '../types' import { abbreviateNumber, copyTextToClipboard, downloadFile, extractModData, formatNumber, getFilenameFromUrl, log, LogType, sanitizeAndAddTargetBlank, unformatNumber } from '../utils' import { ZapButtons, ZapPresets, ZapQR } from '../components/Zap' import { getModsEditPageRoute } from '../routes' export const InnerModPage = () => { const { nevent } = useParams() const [modData, setModData] = useState() const [isFetching, setIsFetching] = useState(true) useDidMount(async () => { if (nevent) { const decoded = nip19.decode<'nevent'>(nevent as `nevent1${string}`) const eventId = decoded.data.id const kind = decoded.data.kind const author = decoded.data.author const relays = decoded.data.relays || [] const filter: Filter = { ids: [eventId] } if (kind) filter.kinds = [kind] if (author) filter.authors = [author] RelayController.getInstance() .fetchEvent(filter, relays) .then((event) => { if (event) { const extracted = extractModData(event) setModData(extracted) } }) .catch((err) => { log( true, LogType.Error, 'An error occurred in fetching mod details from relays', err ) toast.error('An error occurred in fetching mod details from relays') }) .finally(() => { setIsFetching(false) }) } }) const oldDownloadListRef = useRef(null) const handleViewOldLinks = () => { if (oldDownloadListRef.current) { // Toggle styles if (oldDownloadListRef.current.style.height === '0px') { // Enable styles oldDownloadListRef.current.style.padding = '' oldDownloadListRef.current.style.height = '' oldDownloadListRef.current.style.border = '' } else { // Disable styles oldDownloadListRef.current.style.padding = '0' oldDownloadListRef.current.style.height = '0' oldDownloadListRef.current.style.border = 'unset' } } } if (isFetching) return if (!modData) return null return (

Mod Download

{modData.downloadUrls.length > 0 && (
)} {modData.downloadUrls.length > 1 && ( <>
{modData.downloadUrls .slice(1) .map((download, index) => ( ))}
)}

Creator's Blog Posts (WIP)

) } type GameProps = { nevent: string game: string author: string } const Game = ({ nevent, game, author }: GameProps) => { const navigate = useNavigate() const userState = useAppSelector((state) => state.user) return (

Mod for:  {game}

) } type BodyProps = { featuredImageUrl: string title: string body: string screenshotsUrls: string[] tags: string[] nsfw: boolean } const Body = ({ featuredImageUrl, title, body, screenshotsUrls, tags, nsfw }: BodyProps) => { const postBodyRef = useRef(null) const viewFullPostBtnRef = useRef(null) const viewFullPost = () => { if (postBodyRef.current && viewFullPostBtnRef.current) { postBodyRef.current.style.maxHeight = 'unset' postBodyRef.current.style.padding = 'unset' viewFullPostBtnRef.current.style.display = 'none' } } return (

{title}

View

{screenshotsUrls.map((url, index) => ( {`ScreenShot-${index}`} ))}
{nsfw && (

NSFW

)} {tags.map((tag, index) => ( {tag} ))}
) } type InteractionsProps = { modDetails: ModDetails } const Interactions = ({ modDetails }: InteractionsProps) => { return (

420

4.2k

69

) } type PublishDetailsProps = { published_at: number edited_at: number site: string } const PublishDetails = ({ published_at, edited_at, site }: PublishDetailsProps) => { return (

{formatDate( (published_at !== -1 ? published_at : edited_at) * 1000, 'dd/MM/yyyy hh:mm:ss aa' )}

{formatDate(edited_at * 1000, 'dd/MM/yyyy hh:mm:ss aa')}

{site}

) } const Download = ({ url, hash, signatureKey, malwareScanLink, modVersion, customNote }: DownloadUrl) => { const [showAuthDetails, setShowAuthDetails] = useState(false) const handleDownload = () => { // Get the filename from the URL const filename = getFilenameFromUrl(url) downloadFile(url, filename) } return (

Ratings (WIP):

420

420

420

4,200

4,200

4,200

setShowAuthDetails((prev) => !prev)} > Authentication Details

{showAuthDetails && (

SHA-256 hash

{hash}

Signature from

{signatureKey}

Scan

{malwareScanLink}

Mod Version

{modVersion}

Note

{customNote}

)}
) } const Comments = () => { return (

Comments (WIP)

Example user comment

52

4

6

500K

12

Replies

Reply

) } type ZapProps = { modDetails: ModDetails } const Zap = ({ modDetails }: ZapProps) => { const [isOpen, setIsOpen] = useState(false) const [hasZapped, setHasZapped] = useState(false) const userState = useAppSelector((state) => state.user) const [totalZappedAmount, setTotalZappedAmount] = useState('0') useDidMount(() => { RelayController.getInstance() .getTotalZapAmount(modDetails, userState.user?.pubkey as string) .then((res) => { setTotalZappedAmount(abbreviateNumber(res.accumulatedZapAmount)) setHasZapped(res.hasZapped) }) .catch((err) => { toast.error(err.message || err) }) }) return ( <>
setIsOpen(true)} >

{totalZappedAmount}

{isOpen && } ) } type ZapModalProps = { modDetails: ModDetails handleClose: Dispatch> } const ZapModal = ({ modDetails, handleClose }: ZapModalProps) => { return (

Tip/Zap

handleClose(false)} >
) } type ZapModProps = { modDetails: ModDetails } const ZapMod = ({ modDetails }: ZapModProps) => { const [isLoading, setIsLoading] = useState(false) const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('') const [amount, setAmount] = useState(0) const [message, setMessage] = useState('') const [paymentRequest, setPaymentRequest] = useState() const userState = useAppSelector((state) => state.user) const handleAmountChange = (event: React.ChangeEvent) => { const unformattedValue = unformatNumber(event.target.value) setAmount(unformattedValue) } const handleClose = useCallback(() => { setPaymentRequest(undefined) setIsLoading(false) }, []) const handleQRExpiry = useCallback(() => { setPaymentRequest(undefined) }, []) const generatePaymentRequest = useCallback(async (): Promise => { 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) { setIsLoading(false) toast.error('Could not get pubkey') return null } setLoadingSpinnerDesc('Getting admin metadata') const metadataController = await MetadataController.getInstance() const authorMetadata = await metadataController.findMetadata( modDetails.author ) if (!authorMetadata?.lud16) { setIsLoading(false) toast.error('Lighting address (lud16) is missing in author metadata!') return null } if (!authorMetadata?.pubkey) { setIsLoading(false) toast.error('pubkey is missing in author metadata!') return null } const zapController = ZapController.getInstance() setLoadingSpinnerDesc('Creating zap request') return await zapController .getLightningPaymentRequest( authorMetadata.lud16, amount, authorMetadata.pubkey as string, userHexKey, message, modDetails.id, modDetails.aTag ) .catch((err) => { toast.error(err.message || err) return null }) .finally(() => { setIsLoading(false) }) }, [amount, message, userState, modDetails]) const handleSend = useCallback(async () => { const pr = await generatePaymentRequest() if (!pr) return setIsLoading(true) setLoadingSpinnerDesc('Sending payment!') const zapController = ZapController.getInstance() if (await zapController.isWeblnProviderExists()) { await zapController .sendPayment(pr.pr) .then(() => { toast.success(`Successfully sent ${amount} sats!`) handleClose() }) .catch((err) => { toast.error(err.message || err) }) } else { toast.warn('Webln is not present. Use QR code to send zap.') setPaymentRequest(pr) } setIsLoading(false) }, [amount, handleClose, generatePaymentRequest]) const handleGenerateQRCode = async () => { const pr = await generatePaymentRequest() if (!pr) return setPaymentRequest(pr) } return ( <>
setMessage(e.target.value)} />
{paymentRequest && ( )} {isLoading && } ) } const ZapSite = () => { const [isLoading, setIsLoading] = useState(false) const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('') const [amount, setAmount] = useState(0) const [paymentRequest, setPaymentRequest] = useState() const userState = useAppSelector((state) => state.user) const handleAmountChange = (event: React.ChangeEvent) => { const unformattedValue = unformatNumber(event.target.value) setAmount(unformattedValue) } const handleClose = useCallback(() => { setPaymentRequest(undefined) setIsLoading(false) }, []) const handleQRExpiry = useCallback(() => { setPaymentRequest(undefined) }, []) const generatePaymentRequest = useCallback(async (): Promise => { 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) { setIsLoading(false) toast.error('Could not get pubkey') return null } setLoadingSpinnerDesc('Getting admin metadata') const metadataController = await MetadataController.getInstance() const adminMetadata = await metadataController.findAdminMetadata() if (!adminMetadata?.lud16) { setIsLoading(false) toast.error('Lighting address (lud16) is missing in admin metadata!') return null } if (!adminMetadata?.pubkey) { setIsLoading(false) toast.error('pubkey is missing in admin metadata!') return null } const zapController = ZapController.getInstance() setLoadingSpinnerDesc('Creating zap request') return await zapController .getLightningPaymentRequest( adminMetadata.lud16, amount, adminMetadata.pubkey as string, userHexKey ) .catch((err) => { toast.error(err.message || err) return null }) .finally(() => { setIsLoading(false) }) }, [amount, userState]) const handleSend = useCallback(async () => { const pr = await generatePaymentRequest() if (!pr) return setIsLoading(true) setLoadingSpinnerDesc('Sending payment!') const zapController = ZapController.getInstance() if (await zapController.isWeblnProviderExists()) { await zapController .sendPayment(pr.pr) .then(() => { toast.success(`Successfully sent ${amount} sats!`) handleClose() }) .catch((err) => { toast.error(err.message || err) }) } else { toast.warn('Webln is not present. Use QR code to send zap.') setPaymentRequest(pr) } setIsLoading(false) }, [amount, handleClose, generatePaymentRequest]) const handleGenerateQRCode = async () => { const pr = await generatePaymentRequest() if (!pr) return setPaymentRequest(pr) } return ( <>

DEG Mods

degmods@degmods.com

Help with the development, maintenance, management, and growth of DEG Mods.

{paymentRequest && ( )}
{isLoading && } ) }