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 { useCallback, useState } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { toast } from 'react-toastify'
|
||||
import {
|
||||
MetadataController,
|
||||
RelayController,
|
||||
UserRelaysType,
|
||||
ZapController
|
||||
UserRelaysType
|
||||
} from '../controllers'
|
||||
import { useAppSelector, useDidMount } from '../hooks'
|
||||
import { getProfilePageRoute } from '../routes'
|
||||
import '../styles/author.css'
|
||||
import '../styles/innerPage.css'
|
||||
import '../styles/socialPosts.css'
|
||||
import { PaymentRequest, UserProfile } from '../types'
|
||||
import {
|
||||
copyTextToClipboard,
|
||||
formatNumber,
|
||||
log,
|
||||
LogType,
|
||||
unformatNumber,
|
||||
now
|
||||
} from '../utils'
|
||||
import { UserProfile } from '../types'
|
||||
import { copyTextToClipboard, log, LogType, now } from '../utils'
|
||||
import { LoadingSpinner } from './LoadingSpinner'
|
||||
import { ZapButtons, ZapPresets, ZapQR } from './Zap'
|
||||
import { getProfilePageRoute } from '../routes'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { ZapPopUp } from './Zap'
|
||||
|
||||
type Props = {
|
||||
pubkey: string
|
||||
@ -259,7 +251,7 @@ const QRButtonWithPopUp = ({ pubkey }: QRButtonWithPopUpProps) => {
|
||||
</div>
|
||||
|
||||
{isOpen && (
|
||||
<div id='PopUpMainQR' className='popUpMain'>
|
||||
<div className='popUpMain'>
|
||||
<div className='ContainerMain'>
|
||||
<div className='popUpMainCardWrapper'>
|
||||
<div className='popUpMainCard popUpMainCardQR'>
|
||||
@ -307,122 +299,6 @@ type ZapButtonWithPopUpProps = {
|
||||
|
||||
const ZapButtonWithPopUp = ({ pubkey }: ZapButtonWithPopUpProps) => {
|
||||
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 (
|
||||
<>
|
||||
@ -442,77 +318,12 @@ const ZapButtonWithPopUp = ({ pubkey }: ZapButtonWithPopUpProps) => {
|
||||
</svg>
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div id='PopUpMainZap' 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}>
|
||||
<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}
|
||||
<ZapPopUp
|
||||
title='Tip/Zap'
|
||||
receiver={pubkey}
|
||||
handleClose={() => setIsOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -1,12 +1,20 @@
|
||||
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 { toast } from 'react-toastify'
|
||||
import { ZapController } from '../controllers'
|
||||
import { useDidMount } from '../hooks'
|
||||
import { MetadataController, ZapController } from '../controllers'
|
||||
import { useAppSelector, useDidMount } from '../hooks'
|
||||
import '../styles/popup.css'
|
||||
import { PaymentRequest } from '../types'
|
||||
import { copyTextToClipboard } from '../utils'
|
||||
import { copyTextToClipboard, formatNumber, unformatNumber } from '../utils'
|
||||
import { LoadingSpinner } from './LoadingSpinner'
|
||||
|
||||
type PresetAmountProps = {
|
||||
label: string
|
||||
@ -194,3 +202,223 @@ const Timer = React.memo(({ onTimerExpired }: TimerProps) => {
|
||||
</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,
|
||||
launch as launchNostrLoginDialog
|
||||
} from 'nostr-login'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { toast } from 'react-toastify'
|
||||
import { Banner } from '../components/Banner'
|
||||
import { LoadingSpinner } from '../components/LoadingSpinner'
|
||||
import { ZapButtons, ZapPresets, ZapQR } from '../components/Zap'
|
||||
import { MetadataController, ZapController } from '../controllers'
|
||||
import { useAppDispatch, useAppSelector } from '../hooks'
|
||||
import { ZapPopUp } from '../components/Zap'
|
||||
import { MetadataController } from '../controllers'
|
||||
import { useAppDispatch, useAppSelector, useDidMount } from '../hooks'
|
||||
import { appRoutes } from '../routes'
|
||||
import { setAuth, setUser } from '../store/reducers/user'
|
||||
import mainStyles from '../styles//main.module.scss'
|
||||
import navStyles from '../styles/nav.module.scss'
|
||||
import '../styles/popup.css'
|
||||
import { PaymentRequest } from '../types'
|
||||
import { formatNumber, npubToHex, unformatNumber } from '../utils'
|
||||
import { npubToHex } from '../utils'
|
||||
|
||||
export const Header = () => {
|
||||
const dispatch = useAppDispatch()
|
||||
@ -173,7 +170,9 @@ export const Header = () => {
|
||||
<div className={navStyles.NavMainBottom}>
|
||||
<div className={mainStyles.ContainerMain}>
|
||||
<div className={navStyles.NavMainBottomInside}>
|
||||
<div className={`${navStyles.NavMainBottomInsideOther} ${navStyles.NavMainBottomInsideOtherLeft}`}></div>
|
||||
<div
|
||||
className={`${navStyles.NavMainBottomInsideOther} ${navStyles.NavMainBottomInsideOtherLeft}`}
|
||||
></div>
|
||||
<div className={navStyles.NavMainBottomInsideLinks}>
|
||||
<Link
|
||||
to={appRoutes.games}
|
||||
@ -200,11 +199,24 @@ export const Header = () => {
|
||||
Blog
|
||||
</Link>
|
||||
</div>
|
||||
<div className={`${navStyles.NavMainBottomInsideOther} ${navStyles.NavMainBottomInsideOtherRight}`}>
|
||||
<a className={navStyles.NavMainBottomInsideOtherLink} href="https://primal.net/p/npub17jl3ldd6305rnacvwvchx03snauqsg4nz8mruq0emj9thdpglr2sst825x" target="_blank">
|
||||
<img src="https://image.nostr.build/fb557f1b6d58c7bbcdf4d1edb1b48090c76ff1d1384b9d1aae13d652e7a3cfe4.gif" width="15px" />
|
||||
<div
|
||||
className={`${navStyles.NavMainBottomInsideOther} ${navStyles.NavMainBottomInsideOtherRight}`}
|
||||
>
|
||||
<a
|
||||
className={navStyles.NavMainBottomInsideOtherLink}
|
||||
href='https://primal.net/p/npub17jl3ldd6305rnacvwvchx03snauqsg4nz8mruq0emj9thdpglr2sst825x'
|
||||
target='_blank'
|
||||
>
|
||||
<img
|
||||
src='https://image.nostr.build/fb557f1b6d58c7bbcdf4d1edb1b48090c76ff1d1384b9d1aae13d652e7a3cfe4.gif'
|
||||
width='15px'
|
||||
/>
|
||||
</a>
|
||||
<a className={navStyles.NavMainBottomInsideOtherLink} href="https://x.com/DEGMods" target="_blank">
|
||||
<a
|
||||
className={navStyles.NavMainBottomInsideOtherLink}
|
||||
href='https://x.com/DEGMods'
|
||||
target='_blank'
|
||||
>
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
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>
|
||||
</svg>
|
||||
</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
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 512 512'
|
||||
@ -235,124 +251,13 @@ export const Header = () => {
|
||||
}
|
||||
|
||||
const TipButtonWithDialog = React.memo(() => {
|
||||
const [adminNpub, setAdminNpub] = useState<string | null>(null)
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
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 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')
|
||||
useDidMount(async () => {
|
||||
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,
|
||||
message
|
||||
)
|
||||
.catch((err) => {
|
||||
toast.error(err.message || err)
|
||||
return null
|
||||
setAdminNpub(metadataController.adminNpubs[0])
|
||||
})
|
||||
.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 (
|
||||
<>
|
||||
@ -371,91 +276,38 @@ const TipButtonWithDialog = React.memo(() => {
|
||||
</svg>
|
||||
Tip
|
||||
</a>
|
||||
{isOpen && (
|
||||
<div id='PopUpMainZap' className='popUpMain'>
|
||||
<div className='ContainerMain'>
|
||||
<div className='popUpMainCardWrapper'>
|
||||
<div className='popUpMainCard popUpMainCardQR'>
|
||||
<div className='popUpMainCardTop'>
|
||||
<div className='popUpMainCardTopInfo'>
|
||||
<h3>Tip/Zap DEG Mods</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'>
|
||||
<p
|
||||
className='labelDescriptionMain'
|
||||
style={{ textAlign: 'center' }}
|
||||
>
|
||||
If you don't want the development and maintenance of DEG
|
||||
Mods to stop, then a tip helps!
|
||||
{isOpen && adminNpub && (
|
||||
<ZapPopUp
|
||||
title='Tip/Zap DEG Mods'
|
||||
receiver={adminNpub}
|
||||
handleClose={() => setIsOpen(false)}
|
||||
labelDescriptionMain={
|
||||
<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>
|
||||
<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}
|
||||
/>
|
||||
)}
|
||||
}
|
||||
lastNode={
|
||||
<div className='BTCAddressPopZap'>
|
||||
<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 />
|
||||
<span className='BTCAddressPopZapTextSpan'>sp1qq205tj23sq3z6qjxt5ts5ps8gdwcrkwypej3h2z2hdclmaptl25xxqjfqhc2de4gaxprgm0yqwfr737swpvvmrph9ctkeyk60knz6xpjhqumafrd</span>
|
||||
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 />
|
||||
<span className='BTCAddressPopZapTextSpan'>
|
||||
sp1qq205tj23sq3z6qjxt5ts5ps8gdwcrkwypej3h2z2hdclmaptl25xxqjfqhc2de4gaxprgm0yqwfr737swpvvmrph9ctkeyk60knz6xpjhqumafrd
|
||||
</span>
|
||||
</p>
|
||||
</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 StarterKit from '@tiptap/starter-kit'
|
||||
import { formatDate } from 'date-fns'
|
||||
import FsLightbox from 'fslightbox-react'
|
||||
import { Filter, kinds, nip19, UnsignedEvent } from 'nostr-tools'
|
||||
import {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react'
|
||||
import { useCallback, useEffect, 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 { ZapButtons, ZapPresets, ZapQR } from '../components/Zap'
|
||||
import { ZapButtons, ZapPopUp, ZapPresets, ZapQR } from '../components/Zap'
|
||||
import {
|
||||
MetadataController,
|
||||
RelayController,
|
||||
@ -48,10 +42,9 @@ import {
|
||||
now,
|
||||
npubToHex,
|
||||
sendDMUsingRandomKey,
|
||||
unformatNumber,
|
||||
signAndPublish
|
||||
signAndPublish,
|
||||
unformatNumber
|
||||
} from '../utils'
|
||||
import FsLightbox from 'fslightbox-react'
|
||||
|
||||
export const ModPage = () => {
|
||||
const { naddr } = useParams()
|
||||
@ -1638,221 +1631,17 @@ const Zap = ({ modDetails }: ZapProps) => {
|
||||
<div className='IBMSMSMBSSCL_CAElementLoad'></div>
|
||||
</div>
|
||||
</div>
|
||||
{isOpen && <ZapModal modDetails={modDetails} handleClose={setIsOpen} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
type ZapModalProps = {
|
||||
modDetails: ModDetails
|
||||
handleClose: Dispatch<SetStateAction<boolean>>
|
||||
}
|
||||
|
||||
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}
|
||||
{isOpen && (
|
||||
<ZapPopUp
|
||||
title='Tip/Zap'
|
||||
receiver={modDetails.author}
|
||||
eventId={modDetails.id}
|
||||
aTag={modDetails.aTag}
|
||||
handleClose={() => setIsOpen(false)}
|
||||
lastNode={<ZapSite />}
|
||||
notCloseAfterZap
|
||||
/>
|
||||
)}
|
||||
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user