chore(refactor): reduce code duplication in zap
This commit is contained in:
parent
b12887cdf5
commit
a85314f0a7
@ -1,30 +1,22 @@
|
|||||||
import { Filter, kinds, nip19, UnsignedEvent, Event } from 'nostr-tools'
|
import { Event, Filter, kinds, nip19, UnsignedEvent } from 'nostr-tools'
|
||||||
import { QRCodeSVG } from 'qrcode.react'
|
import { QRCodeSVG } from 'qrcode.react'
|
||||||
import { useCallback, useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import {
|
import {
|
||||||
MetadataController,
|
MetadataController,
|
||||||
RelayController,
|
RelayController,
|
||||||
UserRelaysType,
|
UserRelaysType
|
||||||
ZapController
|
|
||||||
} from '../controllers'
|
} from '../controllers'
|
||||||
import { useAppSelector, useDidMount } from '../hooks'
|
import { useAppSelector, useDidMount } from '../hooks'
|
||||||
|
import { 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 { PaymentRequest, UserProfile } from '../types'
|
import { UserProfile } from '../types'
|
||||||
import {
|
import { copyTextToClipboard, log, LogType, now } from '../utils'
|
||||||
copyTextToClipboard,
|
|
||||||
formatNumber,
|
|
||||||
log,
|
|
||||||
LogType,
|
|
||||||
unformatNumber,
|
|
||||||
now
|
|
||||||
} from '../utils'
|
|
||||||
import { LoadingSpinner } from './LoadingSpinner'
|
import { LoadingSpinner } from './LoadingSpinner'
|
||||||
import { ZapButtons, ZapPresets, ZapQR } from './Zap'
|
import { ZapPopUp } from './Zap'
|
||||||
import { getProfilePageRoute } from '../routes'
|
|
||||||
import { useNavigate } from 'react-router-dom'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
pubkey: string
|
pubkey: string
|
||||||
@ -259,7 +251,7 @@ const QRButtonWithPopUp = ({ pubkey }: QRButtonWithPopUpProps) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div id='PopUpMainQR' className='popUpMain'>
|
<div className='popUpMain'>
|
||||||
<div className='ContainerMain'>
|
<div className='ContainerMain'>
|
||||||
<div className='popUpMainCardWrapper'>
|
<div className='popUpMainCardWrapper'>
|
||||||
<div className='popUpMainCard popUpMainCardQR'>
|
<div className='popUpMainCard popUpMainCardQR'>
|
||||||
@ -307,122 +299,6 @@ type ZapButtonWithPopUpProps = {
|
|||||||
|
|
||||||
const ZapButtonWithPopUp = ({ pubkey }: ZapButtonWithPopUpProps) => {
|
const ZapButtonWithPopUp = ({ pubkey }: ZapButtonWithPopUpProps) => {
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
const [amount, setAmount] = useState<number>(0)
|
|
||||||
const [message, setMessage] = useState('')
|
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
|
||||||
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
|
||||||
|
|
||||||
const [paymentRequest, setPaymentRequest] = useState<PaymentRequest>()
|
|
||||||
|
|
||||||
const userState = useAppSelector((state) => state.user)
|
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
|
||||||
setPaymentRequest(undefined)
|
|
||||||
setIsLoading(false)
|
|
||||||
setIsOpen(false)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleQRExpiry = useCallback(() => {
|
|
||||||
setPaymentRequest(undefined)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleAmountChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const unformattedValue = unformatNumber(event.target.value)
|
|
||||||
setAmount(unformattedValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
const generatePaymentRequest =
|
|
||||||
useCallback(async (): Promise<PaymentRequest | null> => {
|
|
||||||
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(pubkey)
|
|
||||||
|
|
||||||
if (!authorMetadata?.lud16) {
|
|
||||||
setIsLoading(false)
|
|
||||||
toast.error('Lighting address (lud16) is missing in admin metadata!')
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!authorMetadata?.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(
|
|
||||||
authorMetadata.lud16,
|
|
||||||
amount,
|
|
||||||
authorMetadata.pubkey as string,
|
|
||||||
userHexKey,
|
|
||||||
message
|
|
||||||
)
|
|
||||||
.catch((err) => {
|
|
||||||
toast.error(err.message || err)
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsLoading(false)
|
|
||||||
})
|
|
||||||
}, [amount, message, userState, pubkey])
|
|
||||||
|
|
||||||
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -442,77 +318,12 @@ const ZapButtonWithPopUp = ({ pubkey }: ZapButtonWithPopUpProps) => {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div id='PopUpMainZap' className='popUpMain'>
|
<ZapPopUp
|
||||||
<div className='ContainerMain'>
|
title='Tip/Zap'
|
||||||
<div className='popUpMainCardWrapper'>
|
receiver={pubkey}
|
||||||
<div className='popUpMainCard popUpMainCardQR'>
|
handleClose={() => setIsOpen(false)}
|
||||||
<div className='popUpMainCardTop'>
|
|
||||||
<div className='popUpMainCardTopInfo'>
|
|
||||||
<h3>Tip/Zap</h3>
|
|
||||||
</div>
|
|
||||||
<div className='popUpMainCardTopClose' onClick={handleClose}>
|
|
||||||
<svg
|
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
|
||||||
viewBox='-96 0 512 512'
|
|
||||||
width='1em'
|
|
||||||
height='1em'
|
|
||||||
fill='currentColor'
|
|
||||||
style={{ zIndex: 1 }}
|
|
||||||
>
|
|
||||||
<path d='M310.6 361.4c12.5 12.5 12.5 32.75 0 45.25C304.4 412.9 296.2 416 288 416s-16.38-3.125-22.62-9.375L160 301.3L54.63 406.6C48.38 412.9 40.19 416 32 416S15.63 412.9 9.375 406.6c-12.5-12.5-12.5-32.75 0-45.25l105.4-105.4L9.375 150.6c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L160 210.8l105.4-105.4c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25l-105.4 105.4L310.6 361.4z' />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='pUMCB_Zaps'>
|
|
||||||
<div className='pUMCB_ZapsInside'>
|
|
||||||
<div className='pUMCB_ZapsInsideAmount'>
|
|
||||||
<div className='inputLabelWrapperMain'>
|
|
||||||
<label className='form-label labelMain'>
|
|
||||||
Amount (Satoshis)
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
className='inputMain'
|
|
||||||
type='text'
|
|
||||||
inputMode='numeric'
|
|
||||||
value={amount ? formatNumber(amount) : ''}
|
|
||||||
onChange={handleAmountChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='pUMCB_ZapsInsideAmountOptions'>
|
|
||||||
<ZapPresets setAmount={setAmount} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='inputLabelWrapperMain'>
|
|
||||||
<label className='form-label labelMain'>
|
|
||||||
Message (optional)
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='inputMain'
|
|
||||||
value={message}
|
|
||||||
onChange={(e) => setMessage(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<ZapButtons
|
|
||||||
disabled={!amount}
|
|
||||||
handleGenerateQRCode={handleGenerateQRCode}
|
|
||||||
handleSend={handleSend}
|
|
||||||
/>
|
|
||||||
{paymentRequest && (
|
|
||||||
<ZapQR
|
|
||||||
paymentRequest={paymentRequest}
|
|
||||||
handleClose={handleClose}
|
|
||||||
handleQRExpiry={handleQRExpiry}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,20 @@
|
|||||||
import { QRCodeSVG } from 'qrcode.react'
|
import { QRCodeSVG } from 'qrcode.react'
|
||||||
import React, { Dispatch, SetStateAction, useMemo } from 'react'
|
import React, {
|
||||||
|
Dispatch,
|
||||||
|
ReactNode,
|
||||||
|
SetStateAction,
|
||||||
|
useCallback,
|
||||||
|
useMemo,
|
||||||
|
useState
|
||||||
|
} from 'react'
|
||||||
import Countdown, { CountdownRenderProps } from 'react-countdown'
|
import Countdown, { CountdownRenderProps } from 'react-countdown'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { ZapController } from '../controllers'
|
import { MetadataController, ZapController } from '../controllers'
|
||||||
import { useDidMount } from '../hooks'
|
import { useAppSelector, useDidMount } from '../hooks'
|
||||||
import '../styles/popup.css'
|
import '../styles/popup.css'
|
||||||
import { PaymentRequest } from '../types'
|
import { PaymentRequest } from '../types'
|
||||||
import { copyTextToClipboard } from '../utils'
|
import { copyTextToClipboard, formatNumber, unformatNumber } from '../utils'
|
||||||
|
import { LoadingSpinner } from './LoadingSpinner'
|
||||||
|
|
||||||
type PresetAmountProps = {
|
type PresetAmountProps = {
|
||||||
label: string
|
label: string
|
||||||
@ -194,3 +202,223 @@ const Timer = React.memo(({ onTimerExpired }: TimerProps) => {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
type ZapPopUpProps = {
|
||||||
|
title: string
|
||||||
|
labelDescriptionMain?: ReactNode
|
||||||
|
receiver: string
|
||||||
|
eventId?: string
|
||||||
|
aTag?: string
|
||||||
|
notCloseAfterZap?: boolean
|
||||||
|
lastNode?: ReactNode
|
||||||
|
handleClose: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ZapPopUp = ({
|
||||||
|
title,
|
||||||
|
labelDescriptionMain,
|
||||||
|
receiver,
|
||||||
|
eventId,
|
||||||
|
aTag,
|
||||||
|
lastNode,
|
||||||
|
notCloseAfterZap,
|
||||||
|
handleClose
|
||||||
|
}: ZapPopUpProps) => {
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
||||||
|
const [amount, setAmount] = useState<number>(0)
|
||||||
|
const [message, setMessage] = useState('')
|
||||||
|
const [paymentRequest, setPaymentRequest] = useState<PaymentRequest>()
|
||||||
|
|
||||||
|
const userState = useAppSelector((state) => state.user)
|
||||||
|
|
||||||
|
const handleAmountChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const unformattedValue = unformatNumber(event.target.value)
|
||||||
|
setAmount(unformattedValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
const generatePaymentRequest =
|
||||||
|
useCallback(async (): Promise<PaymentRequest | null> => {
|
||||||
|
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('finding receiver metadata')
|
||||||
|
const metadataController = await MetadataController.getInstance()
|
||||||
|
|
||||||
|
const receiverMetadata = await metadataController.findMetadata(receiver)
|
||||||
|
|
||||||
|
if (!receiverMetadata?.lud16) {
|
||||||
|
setIsLoading(false)
|
||||||
|
toast.error('Lighting address (lud16) is missing in receiver metadata!')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!receiverMetadata?.pubkey) {
|
||||||
|
setIsLoading(false)
|
||||||
|
toast.error('pubkey is missing in receiver metadata!')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const zapController = ZapController.getInstance()
|
||||||
|
|
||||||
|
setLoadingSpinnerDesc('Creating zap request')
|
||||||
|
return await zapController
|
||||||
|
.getLightningPaymentRequest(
|
||||||
|
receiverMetadata.lud16,
|
||||||
|
amount,
|
||||||
|
receiverMetadata.pubkey as string,
|
||||||
|
userHexKey,
|
||||||
|
message,
|
||||||
|
eventId,
|
||||||
|
aTag
|
||||||
|
)
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error(err.message || err)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setIsLoading(false)
|
||||||
|
})
|
||||||
|
}, [amount, message, userState, receiver, eventId, aTag])
|
||||||
|
|
||||||
|
const handleGenerateQRCode = async () => {
|
||||||
|
const pr = await generatePaymentRequest()
|
||||||
|
|
||||||
|
if (!pr) return
|
||||||
|
|
||||||
|
setPaymentRequest(pr)
|
||||||
|
}
|
||||||
|
|
||||||
|
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!`)
|
||||||
|
if (!notCloseAfterZap) {
|
||||||
|
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, notCloseAfterZap, handleClose, generatePaymentRequest])
|
||||||
|
|
||||||
|
const handleQRExpiry = useCallback(() => {
|
||||||
|
setPaymentRequest(undefined)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleQRClose = useCallback(() => {
|
||||||
|
setPaymentRequest(undefined)
|
||||||
|
setIsLoading(false)
|
||||||
|
if (!notCloseAfterZap) {
|
||||||
|
handleClose()
|
||||||
|
}
|
||||||
|
}, [notCloseAfterZap, handleClose])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
||||||
|
<div className='popUpMain'>
|
||||||
|
<div className='ContainerMain'>
|
||||||
|
<div className='popUpMainCardWrapper'>
|
||||||
|
<div className='popUpMainCard popUpMainCardQR'>
|
||||||
|
<div className='popUpMainCardTop'>
|
||||||
|
<div className='popUpMainCardTopInfo'>
|
||||||
|
<h3>{title}</h3>
|
||||||
|
</div>
|
||||||
|
<div className='popUpMainCardTopClose' onClick={handleClose}>
|
||||||
|
<svg
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
viewBox='-96 0 512 512'
|
||||||
|
width='1em'
|
||||||
|
height='1em'
|
||||||
|
fill='currentColor'
|
||||||
|
style={{ zIndex: 1 }}
|
||||||
|
>
|
||||||
|
<path d='M310.6 361.4c12.5 12.5 12.5 32.75 0 45.25C304.4 412.9 296.2 416 288 416s-16.38-3.125-22.62-9.375L160 301.3L54.63 406.6C48.38 412.9 40.19 416 32 416S15.63 412.9 9.375 406.6c-12.5-12.5-12.5-32.75 0-45.25l105.4-105.4L9.375 150.6c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L160 210.8l105.4-105.4c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25l-105.4 105.4L310.6 361.4z' />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='pUMCB_Zaps'>
|
||||||
|
<div className='pUMCB_ZapsInside'>
|
||||||
|
<div className='pUMCB_ZapsInsideAmount'>
|
||||||
|
<div className='inputLabelWrapperMain'>
|
||||||
|
{labelDescriptionMain}
|
||||||
|
<label className='form-label labelMain'>
|
||||||
|
Amount (Satoshis)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className='inputMain'
|
||||||
|
type='text'
|
||||||
|
inputMode='numeric'
|
||||||
|
value={amount ? formatNumber(amount) : ''}
|
||||||
|
onChange={handleAmountChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='pUMCB_ZapsInsideAmountOptions'>
|
||||||
|
<ZapPresets setAmount={setAmount} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='inputLabelWrapperMain'>
|
||||||
|
<label className='form-label labelMain'>
|
||||||
|
Message (optional)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
className='inputMain'
|
||||||
|
value={message}
|
||||||
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ZapButtons
|
||||||
|
disabled={!amount}
|
||||||
|
handleGenerateQRCode={handleGenerateQRCode}
|
||||||
|
handleSend={handleSend}
|
||||||
|
/>
|
||||||
|
{paymentRequest && (
|
||||||
|
<ZapQR
|
||||||
|
paymentRequest={paymentRequest}
|
||||||
|
handleClose={handleQRClose}
|
||||||
|
handleQRExpiry={handleQRExpiry}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{lastNode}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -2,21 +2,18 @@ import {
|
|||||||
init as initNostrLogin,
|
init as initNostrLogin,
|
||||||
launch as launchNostrLoginDialog
|
launch as launchNostrLoginDialog
|
||||||
} from 'nostr-login'
|
} from 'nostr-login'
|
||||||
import React, { useCallback, useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { toast } from 'react-toastify'
|
|
||||||
import { Banner } from '../components/Banner'
|
import { Banner } from '../components/Banner'
|
||||||
import { LoadingSpinner } from '../components/LoadingSpinner'
|
import { ZapPopUp } from '../components/Zap'
|
||||||
import { ZapButtons, ZapPresets, ZapQR } from '../components/Zap'
|
import { MetadataController } from '../controllers'
|
||||||
import { MetadataController, ZapController } from '../controllers'
|
import { useAppDispatch, useAppSelector, useDidMount } from '../hooks'
|
||||||
import { useAppDispatch, useAppSelector } from '../hooks'
|
|
||||||
import { appRoutes } from '../routes'
|
import { appRoutes } from '../routes'
|
||||||
import { setAuth, setUser } from '../store/reducers/user'
|
import { setAuth, setUser } from '../store/reducers/user'
|
||||||
import mainStyles from '../styles//main.module.scss'
|
import mainStyles from '../styles//main.module.scss'
|
||||||
import navStyles from '../styles/nav.module.scss'
|
import navStyles from '../styles/nav.module.scss'
|
||||||
import '../styles/popup.css'
|
import '../styles/popup.css'
|
||||||
import { PaymentRequest } from '../types'
|
import { npubToHex } from '../utils'
|
||||||
import { formatNumber, npubToHex, unformatNumber } from '../utils'
|
|
||||||
|
|
||||||
export const Header = () => {
|
export const Header = () => {
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
@ -173,7 +170,9 @@ export const Header = () => {
|
|||||||
<div className={navStyles.NavMainBottom}>
|
<div className={navStyles.NavMainBottom}>
|
||||||
<div className={mainStyles.ContainerMain}>
|
<div className={mainStyles.ContainerMain}>
|
||||||
<div className={navStyles.NavMainBottomInside}>
|
<div className={navStyles.NavMainBottomInside}>
|
||||||
<div className={`${navStyles.NavMainBottomInsideOther} ${navStyles.NavMainBottomInsideOtherLeft}`}></div>
|
<div
|
||||||
|
className={`${navStyles.NavMainBottomInsideOther} ${navStyles.NavMainBottomInsideOtherLeft}`}
|
||||||
|
></div>
|
||||||
<div className={navStyles.NavMainBottomInsideLinks}>
|
<div className={navStyles.NavMainBottomInsideLinks}>
|
||||||
<Link
|
<Link
|
||||||
to={appRoutes.games}
|
to={appRoutes.games}
|
||||||
@ -200,11 +199,24 @@ export const Header = () => {
|
|||||||
Blog
|
Blog
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className={`${navStyles.NavMainBottomInsideOther} ${navStyles.NavMainBottomInsideOtherRight}`}>
|
<div
|
||||||
<a className={navStyles.NavMainBottomInsideOtherLink} href="https://primal.net/p/npub17jl3ldd6305rnacvwvchx03snauqsg4nz8mruq0emj9thdpglr2sst825x" target="_blank">
|
className={`${navStyles.NavMainBottomInsideOther} ${navStyles.NavMainBottomInsideOtherRight}`}
|
||||||
<img src="https://image.nostr.build/fb557f1b6d58c7bbcdf4d1edb1b48090c76ff1d1384b9d1aae13d652e7a3cfe4.gif" width="15px" />
|
>
|
||||||
|
<a
|
||||||
|
className={navStyles.NavMainBottomInsideOtherLink}
|
||||||
|
href='https://primal.net/p/npub17jl3ldd6305rnacvwvchx03snauqsg4nz8mruq0emj9thdpglr2sst825x'
|
||||||
|
target='_blank'
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src='https://image.nostr.build/fb557f1b6d58c7bbcdf4d1edb1b48090c76ff1d1384b9d1aae13d652e7a3cfe4.gif'
|
||||||
|
width='15px'
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
<a className={navStyles.NavMainBottomInsideOtherLink} href="https://x.com/DEGMods" target="_blank">
|
<a
|
||||||
|
className={navStyles.NavMainBottomInsideOtherLink}
|
||||||
|
href='https://x.com/DEGMods'
|
||||||
|
target='_blank'
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
viewBox='0 0 512 512'
|
viewBox='0 0 512 512'
|
||||||
@ -215,7 +227,11 @@ export const Header = () => {
|
|||||||
<path d='M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z'></path>
|
<path d='M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z'></path>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<a className={navStyles.NavMainBottomInsideOtherLink} href="https://www.youtube.com/@DEGModsDotCom" target="_blank">
|
<a
|
||||||
|
className={navStyles.NavMainBottomInsideOtherLink}
|
||||||
|
href='https://www.youtube.com/@DEGModsDotCom'
|
||||||
|
target='_blank'
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
viewBox='0 0 512 512'
|
viewBox='0 0 512 512'
|
||||||
@ -235,124 +251,13 @@ export const Header = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TipButtonWithDialog = React.memo(() => {
|
const TipButtonWithDialog = React.memo(() => {
|
||||||
|
const [adminNpub, setAdminNpub] = useState<string | null>(null)
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
useDidMount(async () => {
|
||||||
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
|
||||||
|
|
||||||
const [amount, setAmount] = useState<number>(0)
|
|
||||||
const [message, setMessage] = useState('')
|
|
||||||
|
|
||||||
const [paymentRequest, setPaymentRequest] = useState<PaymentRequest>()
|
|
||||||
|
|
||||||
const userState = useAppSelector((state) => state.user)
|
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
|
||||||
setPaymentRequest(undefined)
|
|
||||||
setIsLoading(false)
|
|
||||||
setIsOpen(false)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleQRExpiry = useCallback(() => {
|
|
||||||
setPaymentRequest(undefined)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleAmountChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const unformattedValue = unformatNumber(event.target.value)
|
|
||||||
setAmount(unformattedValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
const generatePaymentRequest =
|
|
||||||
useCallback(async (): Promise<PaymentRequest | null> => {
|
|
||||||
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 metadataController = await MetadataController.getInstance()
|
||||||
|
setAdminNpub(metadataController.adminNpubs[0])
|
||||||
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,
|
|
||||||
message
|
|
||||||
)
|
|
||||||
.catch((err) => {
|
|
||||||
toast.error(err.message || err)
|
|
||||||
return null
|
|
||||||
})
|
})
|
||||||
.finally(() => {
|
|
||||||
setIsLoading(false)
|
|
||||||
})
|
|
||||||
}, [amount, message, 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -371,91 +276,38 @@ const TipButtonWithDialog = React.memo(() => {
|
|||||||
</svg>
|
</svg>
|
||||||
Tip
|
Tip
|
||||||
</a>
|
</a>
|
||||||
{isOpen && (
|
{isOpen && adminNpub && (
|
||||||
<div id='PopUpMainZap' className='popUpMain'>
|
<ZapPopUp
|
||||||
<div className='ContainerMain'>
|
title='Tip/Zap DEG Mods'
|
||||||
<div className='popUpMainCardWrapper'>
|
receiver={adminNpub}
|
||||||
<div className='popUpMainCard popUpMainCardQR'>
|
handleClose={() => setIsOpen(false)}
|
||||||
<div className='popUpMainCardTop'>
|
labelDescriptionMain={
|
||||||
<div className='popUpMainCardTopInfo'>
|
<p className='labelDescriptionMain' style={{ textAlign: 'center' }}>
|
||||||
<h3>Tip/Zap DEG Mods</h3>
|
If you don't want the development and maintenance of DEG Mods to
|
||||||
</div>
|
stop, then a tip helps!
|
||||||
<div className='popUpMainCardTopClose' onClick={handleClose}>
|
|
||||||
<svg
|
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
|
||||||
viewBox='-96 0 512 512'
|
|
||||||
width='1em'
|
|
||||||
height='1em'
|
|
||||||
fill='currentColor'
|
|
||||||
style={{ zIndex: 1 }}
|
|
||||||
>
|
|
||||||
<path d='M310.6 361.4c12.5 12.5 12.5 32.75 0 45.25C304.4 412.9 296.2 416 288 416s-16.38-3.125-22.62-9.375L160 301.3L54.63 406.6C48.38 412.9 40.19 416 32 416S15.63 412.9 9.375 406.6c-12.5-12.5-12.5-32.75 0-45.25l105.4-105.4L9.375 150.6c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L160 210.8l105.4-105.4c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25l-105.4 105.4L310.6 361.4z' />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='pUMCB_Zaps'>
|
|
||||||
<div className='pUMCB_ZapsInside'>
|
|
||||||
<div className='pUMCB_ZapsInsideAmount'>
|
|
||||||
<div className='inputLabelWrapperMain'>
|
|
||||||
<p
|
|
||||||
className='labelDescriptionMain'
|
|
||||||
style={{ textAlign: 'center' }}
|
|
||||||
>
|
|
||||||
If you don't want the development and maintenance of DEG
|
|
||||||
Mods to stop, then a tip helps!
|
|
||||||
</p>
|
</p>
|
||||||
<label className='form-label labelMain'>
|
}
|
||||||
Amount (Satoshis)
|
lastNode={
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
className='inputMain'
|
|
||||||
type='text'
|
|
||||||
inputMode='numeric'
|
|
||||||
value={amount ? formatNumber(amount) : ''}
|
|
||||||
onChange={handleAmountChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='pUMCB_ZapsInsideAmountOptions'>
|
|
||||||
<ZapPresets setAmount={setAmount} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='inputLabelWrapperMain'>
|
|
||||||
<label className='form-label labelMain'>
|
|
||||||
Message (optional)
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='inputMain'
|
|
||||||
value={message}
|
|
||||||
onChange={(e) => setMessage(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<ZapButtons
|
|
||||||
disabled={!amount}
|
|
||||||
handleGenerateQRCode={handleGenerateQRCode}
|
|
||||||
handleSend={handleSend}
|
|
||||||
/>
|
|
||||||
{paymentRequest && (
|
|
||||||
<ZapQR
|
|
||||||
paymentRequest={paymentRequest}
|
|
||||||
handleClose={handleClose}
|
|
||||||
handleQRExpiry={handleQRExpiry}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className='BTCAddressPopZap'>
|
<div className='BTCAddressPopZap'>
|
||||||
<p>
|
<p>
|
||||||
DEG Mod's Silent Payment Bitcoin Address (Be careful. <a href='https://youtu.be/payDPlHzp58?t=215' className='linkMain' target='_blank'>Learn more</a>):<br />
|
DEG Mod's Silent Payment Bitcoin Address (Be careful.{' '}
|
||||||
<span className='BTCAddressPopZapTextSpan'>sp1qq205tj23sq3z6qjxt5ts5ps8gdwcrkwypej3h2z2hdclmaptl25xxqjfqhc2de4gaxprgm0yqwfr737swpvvmrph9ctkeyk60knz6xpjhqumafrd</span>
|
<a
|
||||||
|
href='https://youtu.be/payDPlHzp58?t=215'
|
||||||
|
className='linkMain'
|
||||||
|
target='_blank'
|
||||||
|
>
|
||||||
|
Learn more
|
||||||
|
</a>
|
||||||
|
):
|
||||||
|
<br />
|
||||||
|
<span className='BTCAddressPopZapTextSpan'>
|
||||||
|
sp1qq205tj23sq3z6qjxt5ts5ps8gdwcrkwypej3h2z2hdclmaptl25xxqjfqhc2de4gaxprgm0yqwfr737swpvvmrph9ctkeyk60knz6xpjhqumafrd
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
</div>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -2,21 +2,15 @@ import Link from '@tiptap/extension-link'
|
|||||||
import { EditorContent, useEditor } from '@tiptap/react'
|
import { EditorContent, useEditor } from '@tiptap/react'
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
import { formatDate } from 'date-fns'
|
import { formatDate } from 'date-fns'
|
||||||
|
import FsLightbox from 'fslightbox-react'
|
||||||
import { Filter, kinds, nip19, UnsignedEvent } from 'nostr-tools'
|
import { Filter, kinds, nip19, UnsignedEvent } from 'nostr-tools'
|
||||||
import {
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
Dispatch,
|
|
||||||
SetStateAction,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState
|
|
||||||
} from 'react'
|
|
||||||
import { useNavigate, useParams } from 'react-router-dom'
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { BlogCard } from '../components/BlogCard'
|
import { BlogCard } from '../components/BlogCard'
|
||||||
import { LoadingSpinner } from '../components/LoadingSpinner'
|
import { LoadingSpinner } from '../components/LoadingSpinner'
|
||||||
import { ProfileSection } from '../components/ProfileSection'
|
import { ProfileSection } from '../components/ProfileSection'
|
||||||
import { ZapButtons, ZapPresets, ZapQR } from '../components/Zap'
|
import { ZapButtons, ZapPopUp, ZapPresets, ZapQR } from '../components/Zap'
|
||||||
import {
|
import {
|
||||||
MetadataController,
|
MetadataController,
|
||||||
RelayController,
|
RelayController,
|
||||||
@ -48,10 +42,9 @@ import {
|
|||||||
now,
|
now,
|
||||||
npubToHex,
|
npubToHex,
|
||||||
sendDMUsingRandomKey,
|
sendDMUsingRandomKey,
|
||||||
unformatNumber,
|
signAndPublish,
|
||||||
signAndPublish
|
unformatNumber
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import FsLightbox from 'fslightbox-react'
|
|
||||||
|
|
||||||
export const ModPage = () => {
|
export const ModPage = () => {
|
||||||
const { naddr } = useParams()
|
const { naddr } = useParams()
|
||||||
@ -1638,221 +1631,17 @@ const Zap = ({ modDetails }: ZapProps) => {
|
|||||||
<div className='IBMSMSMBSSCL_CAElementLoad'></div>
|
<div className='IBMSMSMBSSCL_CAElementLoad'></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isOpen && <ZapModal modDetails={modDetails} handleClose={setIsOpen} />}
|
{isOpen && (
|
||||||
</>
|
<ZapPopUp
|
||||||
)
|
title='Tip/Zap'
|
||||||
}
|
receiver={modDetails.author}
|
||||||
|
eventId={modDetails.id}
|
||||||
type ZapModalProps = {
|
aTag={modDetails.aTag}
|
||||||
modDetails: ModDetails
|
handleClose={() => setIsOpen(false)}
|
||||||
handleClose: Dispatch<SetStateAction<boolean>>
|
lastNode={<ZapSite />}
|
||||||
}
|
notCloseAfterZap
|
||||||
|
|
||||||
const ZapModal = ({ modDetails, handleClose }: ZapModalProps) => {
|
|
||||||
return (
|
|
||||||
<div id='PopUpMainZapSplitAlt' className='popUpMain'>
|
|
||||||
<div className='ContainerMain'>
|
|
||||||
<div className='popUpMainCardWrapper'>
|
|
||||||
<div className='popUpMainCard popUpMainCardQR'>
|
|
||||||
<div className='popUpMainCardTop'>
|
|
||||||
<div className='popUpMainCardTopInfo'>
|
|
||||||
<h3>Tip/Zap</h3>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className='popUpMainCardTopClose'
|
|
||||||
onClick={() => handleClose(false)}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
|
||||||
viewBox='-96 0 512 512'
|
|
||||||
width='1em'
|
|
||||||
height='1em'
|
|
||||||
fill='currentColor'
|
|
||||||
style={{ zIndex: 1 }}
|
|
||||||
>
|
|
||||||
<path d='M310.6 361.4c12.5 12.5 12.5 32.75 0 45.25C304.4 412.9 296.2 416 288 416s-16.38-3.125-22.62-9.375L160 301.3L54.63 406.6C48.38 412.9 40.19 416 32 416S15.63 412.9 9.375 406.6c-12.5-12.5-12.5-32.75 0-45.25l105.4-105.4L9.375 150.6c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L160 210.8l105.4-105.4c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25l-105.4 105.4L310.6 361.4z'></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='pUMCB_Zaps'>
|
|
||||||
<div className='pUMCB_ZapsInside'>
|
|
||||||
<ZapMod modDetails={modDetails} />
|
|
||||||
<ZapSite />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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<PaymentRequest>()
|
|
||||||
|
|
||||||
const userState = useAppSelector((state) => state.user)
|
|
||||||
|
|
||||||
const handleAmountChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
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<PaymentRequest | null> => {
|
|
||||||
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 (
|
|
||||||
<>
|
|
||||||
<div className='pUMCB_ZapsInsideAmount'>
|
|
||||||
<div className='inputLabelWrapperMain'>
|
|
||||||
<label className='form-label labelMain'>Amount (Satoshis)</label>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='inputMain'
|
|
||||||
inputMode='numeric'
|
|
||||||
placeholder='69 or 420? or 69,420?'
|
|
||||||
value={amount ? formatNumber(amount) : ''}
|
|
||||||
onChange={handleAmountChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='pUMCB_ZapsInsideAmountOptions'>
|
|
||||||
<ZapPresets setAmount={setAmount} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='inputLabelWrapperMain'>
|
|
||||||
<label className='form-label labelMain'>Message (optional)</label>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='inputMain'
|
|
||||||
placeholder='This is awesome!'
|
|
||||||
value={message}
|
|
||||||
onChange={(e) => setMessage(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<ZapButtons
|
|
||||||
disabled={!amount}
|
|
||||||
handleGenerateQRCode={handleGenerateQRCode}
|
|
||||||
handleSend={handleSend}
|
|
||||||
/>
|
|
||||||
{paymentRequest && (
|
|
||||||
<ZapQR
|
|
||||||
paymentRequest={paymentRequest}
|
|
||||||
handleClose={handleClose}
|
|
||||||
handleQRExpiry={handleQRExpiry}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user