From a85314f0a78ee4e79905e7483689516f9c034b9a Mon Sep 17 00:00:00 2001 From: daniyal Date: Wed, 4 Sep 2024 11:28:15 +0500 Subject: [PATCH] chore(refactor): reduce code duplication in zap --- src/components/ProfileSection.tsx | 217 ++------------------- src/components/Zap.tsx | 236 ++++++++++++++++++++++- src/layout/header.tsx | 310 ++++++++---------------------- src/pages/mod.tsx | 239 ++--------------------- 4 files changed, 341 insertions(+), 661 deletions(-) diff --git a/src/components/ProfileSection.tsx b/src/components/ProfileSection.tsx index 8dbc1c9..3051b1f 100644 --- a/src/components/ProfileSection.tsx +++ b/src/components/ProfileSection.tsx @@ -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) => { {isOpen && ( -
+
@@ -307,122 +299,6 @@ type ZapButtonWithPopUpProps = { const ZapButtonWithPopUp = ({ pubkey }: ZapButtonWithPopUpProps) => { const [isOpen, setIsOpen] = useState(false) - const [amount, setAmount] = useState(0) - const [message, setMessage] = useState('') - - const [isLoading, setIsLoading] = useState(false) - const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('') - - const [paymentRequest, setPaymentRequest] = useState() - - 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) => { - const unformattedValue = unformatNumber(event.target.value) - setAmount(unformattedValue) - } - - const generatePaymentRequest = - useCallback(async (): Promise => { - 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) => {
{isOpen && ( -
-
-
-
-
-
-

Tip/Zap

-
-
- - - -
-
-
-
-
-
- - -
-
- -
-
-
- - setMessage(e.target.value)} - /> -
- - {paymentRequest && ( - - )} -
-
-
-
-
-
+ setIsOpen(false)} + /> )} - {isLoading && } ) } diff --git a/src/components/Zap.tsx b/src/components/Zap.tsx index 2f441c5..8ef64ad 100644 --- a/src/components/Zap.tsx +++ b/src/components/Zap.tsx @@ -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) => {
) }) + +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(0) + const [message, setMessage] = useState('') + const [paymentRequest, setPaymentRequest] = useState() + + const userState = useAppSelector((state) => state.user) + + const handleAmountChange = (event: React.ChangeEvent) => { + const unformattedValue = unformatNumber(event.target.value) + setAmount(unformattedValue) + } + + const generatePaymentRequest = + useCallback(async (): Promise => { + 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 && } +
+
+
+
+
+
+

{title}

+
+
+ + + +
+
+
+
+
+
+ {labelDescriptionMain} + + +
+
+ +
+
+
+ + setMessage(e.target.value)} + /> +
+ + {paymentRequest && ( + + )} + {lastNode} +
+
+
+
+
+
+ + ) +} diff --git a/src/layout/header.tsx b/src/layout/header.tsx index 8b1599b..27a157f 100644 --- a/src/layout/header.tsx +++ b/src/layout/header.tsx @@ -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 = () => {
-
+
{ Blog
- @@ -235,124 +251,13 @@ export const Header = () => { } const TipButtonWithDialog = React.memo(() => { + const [adminNpub, setAdminNpub] = useState(null) const [isOpen, setIsOpen] = useState(false) - const [isLoading, setIsLoading] = useState(false) - const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('') - - const [amount, setAmount] = useState(0) - const [message, setMessage] = useState('') - - const [paymentRequest, setPaymentRequest] = useState() - - 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) => { - const unformattedValue = unformatNumber(event.target.value) - setAmount(unformattedValue) - } - - const generatePaymentRequest = - useCallback(async (): Promise => { - 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, - 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) - } + useDidMount(async () => { + const metadataController = await MetadataController.getInstance() + setAdminNpub(metadataController.adminNpubs[0]) + }) return ( <> @@ -371,91 +276,38 @@ const TipButtonWithDialog = React.memo(() => { Tip - {isOpen && ( -
-
-
-
-
-
-

Tip/Zap DEG Mods

-
-
- - - -
-
-
-
-
-
-

- If you don't want the development and maintenance of DEG - Mods to stop, then a tip helps! -

- - -
-
- -
-
-
- - setMessage(e.target.value)} - /> -
- - {paymentRequest && ( - - )} -
-

- DEG Mod's Silent Payment Bitcoin Address (Be careful. Learn more):
- sp1qq205tj23sq3z6qjxt5ts5ps8gdwcrkwypej3h2z2hdclmaptl25xxqjfqhc2de4gaxprgm0yqwfr737swpvvmrph9ctkeyk60knz6xpjhqumafrd -

-
-
-
-
+ {isOpen && adminNpub && ( + setIsOpen(false)} + labelDescriptionMain={ +

+ If you don't want the development and maintenance of DEG Mods to + stop, then a tip helps! +

+ } + lastNode={ +
+

+ DEG Mod's Silent Payment Bitcoin Address (Be careful.{' '} + + Learn more + + ): +
+ + sp1qq205tj23sq3z6qjxt5ts5ps8gdwcrkwypej3h2z2hdclmaptl25xxqjfqhc2de4gaxprgm0yqwfr737swpvvmrph9ctkeyk60knz6xpjhqumafrd + +

-
-
+ } + /> )} - {isLoading && } ) }) diff --git a/src/pages/mod.tsx b/src/pages/mod.tsx index d65544c..9efc2de 100644 --- a/src/pages/mod.tsx +++ b/src/pages/mod.tsx @@ -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) => {
- {isOpen && } - - ) -} - -type ZapModalProps = { - modDetails: ModDetails - handleClose: Dispatch> -} - -const ZapModal = ({ modDetails, handleClose }: ZapModalProps) => { - return ( -
-
-
-
-
-
-

Tip/Zap

-
-
handleClose(false)} - > - - - -
-
-
-
- - -
-
-
-
-
-
- ) -} - -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() - - const userState = useAppSelector((state) => state.user) - - const handleAmountChange = (event: React.ChangeEvent) => { - 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 => { - 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 ( - <> -
-
- - -
-
- -
-
-
- - setMessage(e.target.value)} - /> -
- - {paymentRequest && ( - setIsOpen(false)} + lastNode={} + notCloseAfterZap /> )} - {isLoading && } ) }