chore(refactor): code refactored for zapping app account
All checks were successful
Release to Staging / build_and_release (push) Successful in 1m8s
All checks were successful
Release to Staging / build_and_release (push) Successful in 1m8s
This commit is contained in:
parent
5796bb18e3
commit
d451eb129d
196
src/components/Zap.tsx
Normal file
196
src/components/Zap.tsx
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
import { QRCodeSVG } from 'qrcode.react'
|
||||||
|
import React, { Dispatch, SetStateAction, useMemo } from 'react'
|
||||||
|
import Countdown, { CountdownRenderProps } from 'react-countdown'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
import { ZapController } from '../controllers'
|
||||||
|
import { useDidMount } from '../hooks'
|
||||||
|
import '../styles/popup.css'
|
||||||
|
import { PaymentRequest } from '../types'
|
||||||
|
import { copyTextToClipboard } from '../utils'
|
||||||
|
|
||||||
|
type PresetAmountProps = {
|
||||||
|
label: string
|
||||||
|
value: number
|
||||||
|
setAmount: Dispatch<SetStateAction<number>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PresetAmount = React.memo(
|
||||||
|
({ label, value, setAmount }: PresetAmountProps) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className='btn btnMain pUMCB_ZapsInsideAmountOptionsBtn'
|
||||||
|
type='button'
|
||||||
|
onClick={() => setAmount(value)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
viewBox='-64 0 512 512'
|
||||||
|
width='1em'
|
||||||
|
height='1em'
|
||||||
|
fill='currentColor'
|
||||||
|
>
|
||||||
|
<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' />
|
||||||
|
</svg>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type ZapPresetsProps = {
|
||||||
|
setAmount: Dispatch<SetStateAction<number>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ZapPresets = React.memo(({ setAmount }: ZapPresetsProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PresetAmount label='1K' value={1000} setAmount={setAmount} />
|
||||||
|
<PresetAmount label='5K' value={5000} setAmount={setAmount} />
|
||||||
|
<PresetAmount label='10K' value={10000} setAmount={setAmount} />
|
||||||
|
<PresetAmount label='25K' value={25000} setAmount={setAmount} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
type ZapButtonsProps = {
|
||||||
|
disabled: boolean
|
||||||
|
handleGenerateQRCode: () => void
|
||||||
|
handleSend: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ZapButtons = ({
|
||||||
|
disabled,
|
||||||
|
handleGenerateQRCode,
|
||||||
|
handleSend
|
||||||
|
}: ZapButtonsProps) => {
|
||||||
|
return (
|
||||||
|
<div className='pUMCB_ZapsInsideBtns'>
|
||||||
|
<button
|
||||||
|
className='btn btnMain pUMCB_ZapsInsideElementBtn'
|
||||||
|
type='button'
|
||||||
|
onClick={handleGenerateQRCode}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
viewBox='-32 0 512 512'
|
||||||
|
width='1em'
|
||||||
|
height='1em'
|
||||||
|
fill='currentColor'
|
||||||
|
>
|
||||||
|
<path d='M144 32C170.5 32 192 53.49 192 80V176C192 202.5 170.5 224 144 224H48C21.49 224 0 202.5 0 176V80C0 53.49 21.49 32 48 32H144zM128 96H64V160H128V96zM144 288C170.5 288 192 309.5 192 336V432C192 458.5 170.5 480 144 480H48C21.49 480 0 458.5 0 432V336C0 309.5 21.49 288 48 288H144zM128 352H64V416H128V352zM256 80C256 53.49 277.5 32 304 32H400C426.5 32 448 53.49 448 80V176C448 202.5 426.5 224 400 224H304C277.5 224 256 202.5 256 176V80zM320 160H384V96H320V160zM352 448H384V480H352V448zM448 480H416V448H448V480zM416 288H448V416H352V384H320V480H256V288H352V320H416V288z'></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className='btn btnMain pUMCB_ZapsInsideElementBtn'
|
||||||
|
type='button'
|
||||||
|
onClick={handleSend}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
viewBox='-64 0 512 512'
|
||||||
|
width='1em'
|
||||||
|
height='1em'
|
||||||
|
fill='currentColor'
|
||||||
|
>
|
||||||
|
<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' />
|
||||||
|
</svg>
|
||||||
|
Send
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ZapQRProps = {
|
||||||
|
paymentRequest: PaymentRequest
|
||||||
|
handleClose: () => void
|
||||||
|
handleQRExpiry: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ZapQR = React.memo(
|
||||||
|
({ paymentRequest, handleClose, handleQRExpiry }: ZapQRProps) => {
|
||||||
|
useDidMount(() => {
|
||||||
|
ZapController.getInstance()
|
||||||
|
.pollZapReceipt(paymentRequest)
|
||||||
|
.then(() => {
|
||||||
|
toast.success(`Successfully sent sats!`)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error(err.message || err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
handleClose()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const onQrCodeClicked = async () => {
|
||||||
|
if (!paymentRequest) return
|
||||||
|
|
||||||
|
const zapController = ZapController.getInstance()
|
||||||
|
|
||||||
|
if (await zapController.isWeblnProviderExists()) {
|
||||||
|
zapController.sendPayment(paymentRequest.pr)
|
||||||
|
} else {
|
||||||
|
console.warn('Webln provider not present')
|
||||||
|
|
||||||
|
const href = `lightning:${paymentRequest.pr}`
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = href
|
||||||
|
a.click()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='inputLabelWrapperMain' style={{ alignItems: 'center' }}>
|
||||||
|
<QRCodeSVG
|
||||||
|
className='popUpMainCardBottomQR'
|
||||||
|
onClick={onQrCodeClicked}
|
||||||
|
value={paymentRequest.pr}
|
||||||
|
height={235}
|
||||||
|
width={235}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
className='popUpMainCardBottomLnurl'
|
||||||
|
onClick={() => {
|
||||||
|
copyTextToClipboard(paymentRequest.pr).then((isCopied) => {
|
||||||
|
if (isCopied) toast.success('Lnurl copied to clipboard!')
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{paymentRequest.pr}
|
||||||
|
</label>
|
||||||
|
<Timer onTimerExpired={handleQRExpiry} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const MAX_POLLING_TIME = 2 * 60 * 1000 // 2 minutes in milliseconds
|
||||||
|
|
||||||
|
const renderer = ({ minutes, seconds }: CountdownRenderProps) => (
|
||||||
|
<span>
|
||||||
|
{minutes}:{seconds}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
|
||||||
|
type TimerProps = {
|
||||||
|
onTimerExpired: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const Timer = React.memo(({ onTimerExpired }: TimerProps) => {
|
||||||
|
const expiryTime = useMemo(() => {
|
||||||
|
return Date.now() + MAX_POLLING_TIME
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<i className='fas fa-clock'></i>
|
||||||
|
<Countdown
|
||||||
|
date={expiryTime}
|
||||||
|
renderer={renderer}
|
||||||
|
onComplete={onTimerExpired}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
@ -2,34 +2,21 @@ import {
|
|||||||
init as initNostrLogin,
|
init as initNostrLogin,
|
||||||
launch as launchNostrLoginDialog
|
launch as launchNostrLoginDialog
|
||||||
} from 'nostr-login'
|
} from 'nostr-login'
|
||||||
import React, {
|
import React, { useCallback, useEffect, useState } from 'react'
|
||||||
Dispatch,
|
|
||||||
SetStateAction,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
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 { ZapButtons, ZapPresets, ZapQR } from '../components/Zap'
|
||||||
import { MetadataController, ZapController } 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 { setIsAuth, setUser } from '../store/reducers/user'
|
import { setIsAuth, 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 {
|
|
||||||
copyTextToClipboard,
|
|
||||||
formatNumber,
|
|
||||||
npubToHex,
|
|
||||||
unformatNumber
|
|
||||||
} from '../utils'
|
|
||||||
import { toast } from 'react-toastify'
|
|
||||||
import { PaymentRequest } from '../types'
|
import { PaymentRequest } from '../types'
|
||||||
import { LoadingSpinner } from '../components/LoadingSpinner'
|
import { formatNumber, npubToHex, unformatNumber } from '../utils'
|
||||||
import { QRCodeSVG } from 'qrcode.react'
|
|
||||||
import Countdown, { CountdownRenderProps } from 'react-countdown'
|
|
||||||
|
|
||||||
export const Header = () => {
|
export const Header = () => {
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
@ -395,26 +382,7 @@ const TipButtonWithDialog = React.memo(() => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='pUMCB_ZapsInsideAmountOptions'>
|
<div className='pUMCB_ZapsInsideAmountOptions'>
|
||||||
<PresetAmount
|
<ZapPresets setAmount={setAmount} />
|
||||||
label='1K'
|
|
||||||
value={1000}
|
|
||||||
setAmount={setAmount}
|
|
||||||
/>
|
|
||||||
<PresetAmount
|
|
||||||
label='5K'
|
|
||||||
value={5000}
|
|
||||||
setAmount={setAmount}
|
|
||||||
/>
|
|
||||||
<PresetAmount
|
|
||||||
label='10K'
|
|
||||||
value={10000}
|
|
||||||
setAmount={setAmount}
|
|
||||||
/>
|
|
||||||
<PresetAmount
|
|
||||||
label='25K'
|
|
||||||
value={25000}
|
|
||||||
setAmount={setAmount}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='inputLabelWrapperMain'>
|
<div className='inputLabelWrapperMain'>
|
||||||
@ -428,41 +396,11 @@ const TipButtonWithDialog = React.memo(() => {
|
|||||||
onChange={(e) => setMessage(e.target.value)}
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='pUMCB_ZapsInsideBtns'>
|
<ZapButtons
|
||||||
<button
|
|
||||||
className='btn btnMain pUMCB_ZapsInsideElementBtn'
|
|
||||||
type='button'
|
|
||||||
onClick={handleGenerateQRCode}
|
|
||||||
disabled={!amount}
|
disabled={!amount}
|
||||||
>
|
handleGenerateQRCode={handleGenerateQRCode}
|
||||||
<svg
|
handleSend={handleSend}
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
/>
|
||||||
viewBox='-32 0 512 512'
|
|
||||||
width='1em'
|
|
||||||
height='1em'
|
|
||||||
fill='currentColor'
|
|
||||||
>
|
|
||||||
<path d='M144 32C170.5 32 192 53.49 192 80V176C192 202.5 170.5 224 144 224H48C21.49 224 0 202.5 0 176V80C0 53.49 21.49 32 48 32H144zM128 96H64V160H128V96zM144 288C170.5 288 192 309.5 192 336V432C192 458.5 170.5 480 144 480H48C21.49 480 0 458.5 0 432V336C0 309.5 21.49 288 48 288H144zM128 352H64V416H128V352zM256 80C256 53.49 277.5 32 304 32H400C426.5 32 448 53.49 448 80V176C448 202.5 426.5 224 400 224H304C277.5 224 256 202.5 256 176V80zM320 160H384V96H320V160zM352 448H384V480H352V448zM448 480H416V448H448V480zM416 288H448V416H352V384H320V480H256V288H352V320H416V288z'></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className='btn btnMain pUMCB_ZapsInsideElementBtn'
|
|
||||||
type='button'
|
|
||||||
onClick={handleSend}
|
|
||||||
disabled={!amount}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
|
||||||
viewBox='-64 0 512 512'
|
|
||||||
width='1em'
|
|
||||||
height='1em'
|
|
||||||
fill='currentColor'
|
|
||||||
>
|
|
||||||
<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' />
|
|
||||||
</svg>
|
|
||||||
Send
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{paymentRequest && (
|
{paymentRequest && (
|
||||||
<ZapQR
|
<ZapQR
|
||||||
paymentRequest={paymentRequest}
|
paymentRequest={paymentRequest}
|
||||||
@ -481,125 +419,3 @@ const TipButtonWithDialog = React.memo(() => {
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
type PresetAmountProps = {
|
|
||||||
label: string
|
|
||||||
value: number
|
|
||||||
setAmount: Dispatch<SetStateAction<number>>
|
|
||||||
}
|
|
||||||
|
|
||||||
const PresetAmount = React.memo(
|
|
||||||
({ label, value, setAmount }: PresetAmountProps) => {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className='btn btnMain pUMCB_ZapsInsideAmountOptionsBtn'
|
|
||||||
type='button'
|
|
||||||
onClick={() => setAmount(value)}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
|
||||||
viewBox='-64 0 512 512'
|
|
||||||
width='1em'
|
|
||||||
height='1em'
|
|
||||||
fill='currentColor'
|
|
||||||
>
|
|
||||||
<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' />
|
|
||||||
</svg>
|
|
||||||
{label}
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type ZapQRProps = {
|
|
||||||
paymentRequest: PaymentRequest
|
|
||||||
handleClose: () => void
|
|
||||||
handleQRExpiry: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const ZapQR = React.memo(
|
|
||||||
({ paymentRequest, handleClose, handleQRExpiry }: ZapQRProps) => {
|
|
||||||
useDidMount(() => {
|
|
||||||
ZapController.getInstance()
|
|
||||||
.pollZapReceipt(paymentRequest)
|
|
||||||
.then(() => {
|
|
||||||
toast.success(`Successfully sent sats!`)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
toast.error(err.message || err)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
handleClose()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const onQrCodeClicked = async () => {
|
|
||||||
if (!paymentRequest) return
|
|
||||||
|
|
||||||
const zapController = ZapController.getInstance()
|
|
||||||
|
|
||||||
if (await zapController.isWeblnProviderExists()) {
|
|
||||||
zapController.sendPayment(paymentRequest.pr)
|
|
||||||
} else {
|
|
||||||
console.warn('Webln provider not present')
|
|
||||||
|
|
||||||
const href = `lightning:${paymentRequest.pr}`
|
|
||||||
const a = document.createElement('a')
|
|
||||||
a.href = href
|
|
||||||
a.click()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='inputLabelWrapperMain' style={{ alignItems: 'center' }}>
|
|
||||||
<QRCodeSVG
|
|
||||||
className='popUpMainCardBottomQR'
|
|
||||||
onClick={onQrCodeClicked}
|
|
||||||
value={paymentRequest.pr}
|
|
||||||
height={235}
|
|
||||||
width={235}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
className='popUpMainCardBottomLnurl'
|
|
||||||
onClick={() => {
|
|
||||||
copyTextToClipboard(paymentRequest.pr).then((isCopied) => {
|
|
||||||
if (isCopied) toast.success('Lnurl copied to clipboard!')
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{paymentRequest.pr}
|
|
||||||
</label>
|
|
||||||
<Timer onTimerExpired={handleQRExpiry} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const MAX_POLLING_TIME = 2 * 60 * 1000 // 2 minutes in milliseconds
|
|
||||||
|
|
||||||
const renderer = ({ minutes, seconds }: CountdownRenderProps) => (
|
|
||||||
<span>
|
|
||||||
{minutes}:{seconds}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
|
|
||||||
type TimerProps = {
|
|
||||||
onTimerExpired: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const Timer = React.memo(({ onTimerExpired }: TimerProps) => {
|
|
||||||
const expiryTime = useMemo(() => {
|
|
||||||
return Date.now() + MAX_POLLING_TIME
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<i className='fas fa-clock'></i>
|
|
||||||
<Countdown
|
|
||||||
date={expiryTime}
|
|
||||||
renderer={renderer}
|
|
||||||
onComplete={onTimerExpired}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
Loading…
Reference in New Issue
Block a user