feat: implemented zap split UI
All checks were successful
Release to Staging / build_and_release (push) Successful in 48s
All checks were successful
Release to Staging / build_and_release (push) Successful in 48s
This commit is contained in:
parent
3ed7eada83
commit
bad1404e1a
@ -12,14 +12,16 @@ import { toast } from 'react-toastify'
|
|||||||
import { MetadataController, ZapController } from '../controllers'
|
import { MetadataController, ZapController } from '../controllers'
|
||||||
import { useAppSelector, useDidMount } from '../hooks'
|
import { useAppSelector, useDidMount } from '../hooks'
|
||||||
import '../styles/popup.css'
|
import '../styles/popup.css'
|
||||||
import { PaymentRequest } from '../types'
|
import { PaymentRequest, UserProfile } from '../types'
|
||||||
import {
|
import {
|
||||||
copyTextToClipboard,
|
copyTextToClipboard,
|
||||||
formatNumber,
|
formatNumber,
|
||||||
|
getTagValue,
|
||||||
getZapAmount,
|
getZapAmount,
|
||||||
unformatNumber
|
unformatNumber
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { LoadingSpinner } from './LoadingSpinner'
|
import { LoadingSpinner } from './LoadingSpinner'
|
||||||
|
import { FALLBACK_PROFILE_IMAGE } from 'constants.ts'
|
||||||
|
|
||||||
type PresetAmountProps = {
|
type PresetAmountProps = {
|
||||||
label: string
|
label: string
|
||||||
@ -460,3 +462,405 @@ export const ZapPopUp = ({
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ZapSplitProps = {
|
||||||
|
pubkey: string
|
||||||
|
eventId?: string
|
||||||
|
aTag?: string
|
||||||
|
setTotalZapAmount?: Dispatch<SetStateAction<number>>
|
||||||
|
setHasZapped?: Dispatch<SetStateAction<boolean>>
|
||||||
|
handleClose: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ZapSplit = ({
|
||||||
|
pubkey,
|
||||||
|
eventId,
|
||||||
|
aTag,
|
||||||
|
setTotalZapAmount,
|
||||||
|
setHasZapped,
|
||||||
|
handleClose
|
||||||
|
}: ZapSplitProps) => {
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
||||||
|
const [amount, setAmount] = useState<number>(0)
|
||||||
|
const [message, setMessage] = useState('')
|
||||||
|
const [authorPercentage, setAuthorPercentage] = useState(95)
|
||||||
|
const [adminPercentage, setAdminPercentage] = useState(5)
|
||||||
|
|
||||||
|
const [author, setAuthor] = useState<UserProfile>()
|
||||||
|
const [admin, setAdmin] = useState<UserProfile>()
|
||||||
|
|
||||||
|
const userState = useAppSelector((state) => state.user)
|
||||||
|
|
||||||
|
const [invoices, setInvoices] = useState<Map<string, PaymentRequest>>()
|
||||||
|
|
||||||
|
useDidMount(async () => {
|
||||||
|
const metadataController = await MetadataController.getInstance()
|
||||||
|
metadataController.findMetadata(pubkey).then((res) => {
|
||||||
|
setAuthor(res)
|
||||||
|
})
|
||||||
|
|
||||||
|
metadataController.findAdminMetadata().then((res) => {
|
||||||
|
setAdmin(res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleAuthorPercentageChange = (
|
||||||
|
e: React.ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
|
const newValue = parseInt(e.target.value)
|
||||||
|
setAuthorPercentage(newValue)
|
||||||
|
setAdminPercentage(100 - newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAdminPercentageChange = (
|
||||||
|
e: React.ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
|
const newValue = parseInt(e.target.value)
|
||||||
|
setAdminPercentage(newValue)
|
||||||
|
setAuthorPercentage(100 - newValue) // Update the other slider to maintain 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAmountChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const unformattedValue = unformatNumber(event.target.value)
|
||||||
|
setAmount(unformattedValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
const generatePaymentInvoices = async () => {
|
||||||
|
if (!amount) return 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
|
||||||
|
}
|
||||||
|
|
||||||
|
const adminShare = Math.floor((amount * adminPercentage) / 100)
|
||||||
|
const authorShare = amount - adminShare
|
||||||
|
|
||||||
|
const zapController = ZapController.getInstance()
|
||||||
|
|
||||||
|
const invoices = new Map<string, PaymentRequest>()
|
||||||
|
|
||||||
|
if (authorShare > 0 && author?.pubkey && author?.lud16) {
|
||||||
|
setLoadingSpinnerDesc('Generating invoice for author')
|
||||||
|
const invoice = await zapController
|
||||||
|
.getLightningPaymentRequest(
|
||||||
|
author.lud16,
|
||||||
|
authorShare,
|
||||||
|
author.pubkey as string,
|
||||||
|
userHexKey,
|
||||||
|
message,
|
||||||
|
eventId,
|
||||||
|
aTag
|
||||||
|
)
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error(err.message || err)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
if (invoice) {
|
||||||
|
invoices.set('author', invoice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (adminShare > 0 && admin?.pubkey && admin?.lud16) {
|
||||||
|
setLoadingSpinnerDesc('Generating invoice for site owner')
|
||||||
|
const invoice = await zapController
|
||||||
|
.getLightningPaymentRequest(
|
||||||
|
admin.lud16,
|
||||||
|
adminShare,
|
||||||
|
admin.pubkey as string,
|
||||||
|
userHexKey,
|
||||||
|
message,
|
||||||
|
eventId,
|
||||||
|
aTag
|
||||||
|
)
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error(err.message || err)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
if (invoice) {
|
||||||
|
invoices.set('admin', invoice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(false)
|
||||||
|
|
||||||
|
return invoices
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleGenerateQRCode = async () => {
|
||||||
|
const paymentInvoices = await generatePaymentInvoices()
|
||||||
|
|
||||||
|
if (!paymentInvoices) return
|
||||||
|
|
||||||
|
setInvoices(paymentInvoices)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSend = async () => {
|
||||||
|
const paymentInvoices = await generatePaymentInvoices()
|
||||||
|
|
||||||
|
if (!paymentInvoices) return
|
||||||
|
|
||||||
|
setIsLoading(true)
|
||||||
|
setLoadingSpinnerDesc('Sending payment!')
|
||||||
|
|
||||||
|
const zapController = ZapController.getInstance()
|
||||||
|
|
||||||
|
if (await zapController.isWeblnProviderExists()) {
|
||||||
|
const authorInvoice = paymentInvoices.get('author')
|
||||||
|
if (authorInvoice) {
|
||||||
|
setLoadingSpinnerDesc('Sending payment to author')
|
||||||
|
|
||||||
|
const sats = parseInt(getTagValue(authorInvoice, 'amount')![0]) / 1000
|
||||||
|
|
||||||
|
await zapController
|
||||||
|
.sendPayment(authorInvoice.pr)
|
||||||
|
.then(() => {
|
||||||
|
toast.success(`Successfully sent ${sats} sats to author!`)
|
||||||
|
if (setTotalZapAmount) {
|
||||||
|
setTotalZapAmount((prev) => prev + sats)
|
||||||
|
|
||||||
|
if (setHasZapped) setHasZapped(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error(err.message || err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const adminInvoice = paymentInvoices.get('admin')
|
||||||
|
if (adminInvoice) {
|
||||||
|
setLoadingSpinnerDesc('Sending payment to site owner')
|
||||||
|
|
||||||
|
const sats = parseInt(getTagValue(adminInvoice, 'amount')![0]) / 1000
|
||||||
|
|
||||||
|
await zapController
|
||||||
|
.sendPayment(adminInvoice.pr)
|
||||||
|
.then(() => {
|
||||||
|
toast.success(`Successfully sent ${sats} sats to site owner!`)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error(err.message || err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClose()
|
||||||
|
} else {
|
||||||
|
toast.warn('Webln is not present. Use QR code to send zap.')
|
||||||
|
setInvoices(paymentInvoices)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeInvoice = (key: string) => {
|
||||||
|
setInvoices((prev) => {
|
||||||
|
const newMap = new Map(prev)
|
||||||
|
newMap.delete(key)
|
||||||
|
return newMap
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayQR = () => {
|
||||||
|
if (!invoices) return null
|
||||||
|
|
||||||
|
const authorInvoice = invoices.get('author')
|
||||||
|
if (authorInvoice) {
|
||||||
|
return (
|
||||||
|
<ZapQR
|
||||||
|
key={authorInvoice.pr}
|
||||||
|
paymentRequest={authorInvoice}
|
||||||
|
handleClose={() => removeInvoice('author')}
|
||||||
|
handleQRExpiry={() => removeInvoice('author')}
|
||||||
|
setTotalZapAmount={setTotalZapAmount}
|
||||||
|
setHasZapped={setHasZapped}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const adminInvoice = invoices.get('admin')
|
||||||
|
if (adminInvoice) {
|
||||||
|
return (
|
||||||
|
<ZapQR
|
||||||
|
key={adminInvoice.pr}
|
||||||
|
paymentRequest={adminInvoice}
|
||||||
|
handleClose={() => {
|
||||||
|
removeInvoice('admin')
|
||||||
|
handleClose()
|
||||||
|
}}
|
||||||
|
handleQRExpiry={() => removeInvoice('admin')}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const authorName = author?.displayName || author?.name || '[name not set up]'
|
||||||
|
const adminName = admin?.displayName || admin?.name || '[name not set up]'
|
||||||
|
|
||||||
|
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>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>
|
||||||
|
<div className='inputLabelWrapperMain'>
|
||||||
|
<label className='form-label labelMain'>Tip Split</label>
|
||||||
|
<div className='ZapSplitUserBox'>
|
||||||
|
<div className='ZapSplitUserBoxUser'>
|
||||||
|
<div
|
||||||
|
className='ZapSplitUserBoxUserPic'
|
||||||
|
style={{
|
||||||
|
background: `url('${
|
||||||
|
author?.image || FALLBACK_PROFILE_IMAGE
|
||||||
|
}') center / cover no-repeat`
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
<div className='ZapSplitUserBoxUserDetails'>
|
||||||
|
<p className='ZapSplitUserBoxUserDetailsName'>
|
||||||
|
{authorName}
|
||||||
|
</p>
|
||||||
|
{author?.nip05 && (
|
||||||
|
<p className='ZapSplitUserBoxUserDetailsHandle'>
|
||||||
|
{author.nip05}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='ZapSplitUserBoxRange'>
|
||||||
|
<input
|
||||||
|
className='form-range inputRangeMain inputRangeMainZap'
|
||||||
|
type='range'
|
||||||
|
max='100'
|
||||||
|
min='0'
|
||||||
|
value={authorPercentage}
|
||||||
|
onChange={handleAuthorPercentageChange}
|
||||||
|
step='1'
|
||||||
|
required
|
||||||
|
name='ZapSplitName'
|
||||||
|
/>
|
||||||
|
<p className='ZapSplitUserBoxRangeText'>
|
||||||
|
{authorPercentage}%
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p className='ZapSplitUserBoxText'>
|
||||||
|
This goes to show your appreciation to the mod creator!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className='ZapSplitUserBox'>
|
||||||
|
<div className='ZapSplitUserBoxUser'>
|
||||||
|
<div
|
||||||
|
className='ZapSplitUserBoxUserPic'
|
||||||
|
style={{
|
||||||
|
background: `url('${
|
||||||
|
admin?.image || FALLBACK_PROFILE_IMAGE
|
||||||
|
}') center / cover no-repeat`
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
<div className='ZapSplitUserBoxUserDetails'>
|
||||||
|
<p className='ZapSplitUserBoxUserDetailsName'>
|
||||||
|
{adminName}
|
||||||
|
</p>
|
||||||
|
{admin?.nip05 && (
|
||||||
|
<p className='ZapSplitUserBoxUserDetailsHandle'></p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='ZapSplitUserBoxRange'>
|
||||||
|
<input
|
||||||
|
className='form-range inputRangeMain inputRangeMainZap'
|
||||||
|
type='range'
|
||||||
|
max='100'
|
||||||
|
min='0'
|
||||||
|
value={adminPercentage}
|
||||||
|
onChange={handleAdminPercentageChange}
|
||||||
|
step='1'
|
||||||
|
required
|
||||||
|
name='ZapSplitName'
|
||||||
|
/>
|
||||||
|
<p className='ZapSplitUserBoxRangeText'>
|
||||||
|
{adminPercentage}%
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p className='ZapSplitUserBoxText'>
|
||||||
|
Help with the development, maintenance, management, and
|
||||||
|
growth of DEG Mods.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ZapButtons
|
||||||
|
disabled={!amount}
|
||||||
|
handleGenerateQRCode={handleGenerateQRCode}
|
||||||
|
handleSend={handleSend}
|
||||||
|
/>
|
||||||
|
{displayQR()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { LoadingSpinner } from 'components/LoadingSpinner'
|
import { ZapSplit } from 'components/Zap'
|
||||||
import { ZapButtons, ZapPopUp, ZapPresets, ZapQR } from 'components/Zap'
|
import { RelayController } from 'controllers'
|
||||||
import { MetadataController, RelayController, ZapController } from 'controllers'
|
|
||||||
import { useAppSelector, useDidMount } from 'hooks'
|
import { useAppSelector, useDidMount } from 'hooks'
|
||||||
import { useCallback, useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { ModDetails, PaymentRequest } from 'types'
|
import { ModDetails } from 'types'
|
||||||
import { abbreviateNumber, formatNumber, unformatNumber } from 'utils'
|
import { abbreviateNumber } from 'utils'
|
||||||
|
|
||||||
type ZapProps = {
|
type ZapProps = {
|
||||||
modDetails: ModDetails
|
modDetails: ModDetails
|
||||||
@ -65,192 +64,15 @@ export const Zap = ({ modDetails }: ZapProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<ZapPopUp
|
<ZapSplit
|
||||||
title='Tip/Zap'
|
pubkey={modDetails.author}
|
||||||
receiver={modDetails.author}
|
|
||||||
eventId={modDetails.id}
|
eventId={modDetails.id}
|
||||||
aTag={modDetails.aTag}
|
aTag={modDetails.aTag}
|
||||||
handleClose={() => setIsOpen(false)}
|
|
||||||
lastNode={<ZapSite />}
|
|
||||||
notCloseAfterZap
|
|
||||||
setTotalZapAmount={setTotalZappedAmount}
|
setTotalZapAmount={setTotalZappedAmount}
|
||||||
setHasZapped={setHasZapped}
|
setHasZapped={setHasZapped}
|
||||||
|
handleClose={() => setIsOpen(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ZapSite = () => {
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
|
||||||
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
|
||||||
|
|
||||||
const [amount, setAmount] = useState(0)
|
|
||||||
|
|
||||||
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 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 (
|
|
||||||
<>
|
|
||||||
<div className='inputLabelWrapperMain'>
|
|
||||||
<label className='form-label labelMain'>
|
|
||||||
Tip DEG Mods too (Optional)
|
|
||||||
</label>
|
|
||||||
<div className='ZapSplitUserBox'>
|
|
||||||
<div className='ZapSplitUserBoxUser'>
|
|
||||||
<div
|
|
||||||
className='ZapSplitUserBoxUserPic'
|
|
||||||
style={{
|
|
||||||
background: `url('/assets/img/Logo%20with%20circle.png')
|
|
||||||
center / cover no-repeat`
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
<div className='ZapSplitUserBoxUserDetails'>
|
|
||||||
<p className='ZapSplitUserBoxUserDetailsName'>DEG Mods</p>
|
|
||||||
<p className='ZapSplitUserBoxUserDetailsHandle'>
|
|
||||||
degmods@degmods.com
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p className='ZapSplitUserBoxText'>
|
|
||||||
Help with the development, maintenance, management, and growth of
|
|
||||||
DEG Mods.
|
|
||||||
</p>
|
|
||||||
<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>
|
|
||||||
<ZapButtons
|
|
||||||
disabled={!amount}
|
|
||||||
handleGenerateQRCode={handleGenerateQRCode}
|
|
||||||
handleSend={handleSend}
|
|
||||||
/>
|
|
||||||
{paymentRequest && (
|
|
||||||
<ZapQR
|
|
||||||
paymentRequest={paymentRequest}
|
|
||||||
handleClose={handleClose}
|
|
||||||
handleQRExpiry={handleQRExpiry}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user