256 lines
7.7 KiB
TypeScript
256 lines
7.7 KiB
TypeScript
|
import { LoadingSpinner } from 'components/LoadingSpinner'
|
||
|
import { ZapButtons, ZapPopUp, ZapPresets, ZapQR } from 'components/Zap'
|
||
|
import { MetadataController, RelayController, ZapController } from 'controllers'
|
||
|
import { useAppSelector, useDidMount } from 'hooks'
|
||
|
import { useCallback, useState } from 'react'
|
||
|
import { toast } from 'react-toastify'
|
||
|
import { ModDetails, PaymentRequest } from 'types'
|
||
|
import { abbreviateNumber, formatNumber, unformatNumber } from 'utils'
|
||
|
|
||
|
type ZapProps = {
|
||
|
modDetails: ModDetails
|
||
|
}
|
||
|
|
||
|
export const Zap = ({ modDetails }: ZapProps) => {
|
||
|
const [isOpen, setIsOpen] = useState(false)
|
||
|
const [hasZapped, setHasZapped] = useState(false)
|
||
|
|
||
|
const userState = useAppSelector((state) => state.user)
|
||
|
|
||
|
const [totalZappedAmount, setTotalZappedAmount] = useState(0)
|
||
|
|
||
|
useDidMount(() => {
|
||
|
RelayController.getInstance()
|
||
|
.getTotalZapAmount(
|
||
|
modDetails.author,
|
||
|
modDetails.id,
|
||
|
modDetails.aTag,
|
||
|
userState.user?.pubkey as string
|
||
|
)
|
||
|
.then((res) => {
|
||
|
setTotalZappedAmount(res.accumulatedZapAmount)
|
||
|
setHasZapped(res.hasZapped)
|
||
|
})
|
||
|
.catch((err) => {
|
||
|
toast.error(err.message || err)
|
||
|
})
|
||
|
})
|
||
|
|
||
|
return (
|
||
|
<>
|
||
|
<div
|
||
|
id='reactBolt'
|
||
|
className={`IBMSMSMBSS_Details_Card IBMSMSMBSS_D_CBolt ${
|
||
|
hasZapped ? 'IBMSMSMBSS_D_CBActive' : ''
|
||
|
}`}
|
||
|
onClick={() => setIsOpen(true)}
|
||
|
>
|
||
|
<div className='IBMSMSMBSS_Details_CardVisual'>
|
||
|
<svg
|
||
|
xmlns='http://www.w3.org/2000/svg'
|
||
|
viewBox='-64 0 512 512'
|
||
|
width='1em'
|
||
|
height='1em'
|
||
|
fill='currentColor'
|
||
|
className='IBMSMSMBSS_Details_CardVisualIcon'
|
||
|
>
|
||
|
<path d='M240.5 224H352C365.3 224 377.3 232.3 381.1 244.7C386.6 257.2 383.1 271.3 373.1 280.1L117.1 504.1C105.8 513.9 89.27 514.7 77.19 505.9C65.1 497.1 60.7 481.1 66.59 467.4L143.5 288H31.1C18.67 288 6.733 279.7 2.044 267.3C-2.645 254.8 .8944 240.7 10.93 231.9L266.9 7.918C278.2-1.92 294.7-2.669 306.8 6.114C318.9 14.9 323.3 30.87 317.4 44.61L240.5 224z'></path>
|
||
|
</svg>
|
||
|
</div>
|
||
|
<p className='IBMSMSMBSS_Details_CardText'>
|
||
|
{abbreviateNumber(totalZappedAmount)}
|
||
|
</p>
|
||
|
<div className='IBMSMSMBSSCL_CAElementLoadWrapper'>
|
||
|
<div className='IBMSMSMBSSCL_CAElementLoad'></div>
|
||
|
</div>
|
||
|
</div>
|
||
|
{isOpen && (
|
||
|
<ZapPopUp
|
||
|
title='Tip/Zap'
|
||
|
receiver={modDetails.author}
|
||
|
eventId={modDetails.id}
|
||
|
aTag={modDetails.aTag}
|
||
|
handleClose={() => setIsOpen(false)}
|
||
|
lastNode={<ZapSite />}
|
||
|
notCloseAfterZap
|
||
|
setTotalZapAmount={setTotalZappedAmount}
|
||
|
/>
|
||
|
)}
|
||
|
</>
|
||
|
)
|
||
|
}
|
||
|
|
||
|
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} />}
|
||
|
</>
|
||
|
)
|
||
|
}
|