From a0a9b9fcc86727d4e4363b835184532b99482c4d Mon Sep 17 00:00:00 2001 From: austinkelsay Date: Tue, 17 Sep 2024 13:28:58 -0500 Subject: [PATCH] Improvements to progress spinners, fixed resource and course payment buttons to not have to fetch invoice until button is pressed --- jsconfig.json | 10 +- .../bitcoinConnect/CoursePaymentButton.js | 100 +++++++++++------- .../bitcoinConnect/ResourcePaymentButton.js | 98 +++++++++-------- .../carousels/templates/CourseTemplate.js | 2 +- .../content/courses/CourseDetails.js | 4 +- .../content/courses/CourseDetailsNew.js | 3 +- .../content/dropdowns/MessageDropdownItem.js | 3 +- src/components/feeds/DiscordFeed.js | 17 +-- src/components/forms/course/CourseForm.js | 2 +- src/components/profile/UserContent.js | 2 +- src/components/profile/UserProfile.js | 2 +- src/components/profile/UserSettings.js | 2 +- .../profile/subscription/SubscribeModal.js | 2 +- .../profile/subscription/UserSubscription.js | 2 +- src/config/appConfig.js | 17 +++ src/pages/course/[slug]/draft/index.js | 4 +- src/pages/course/[slug]/index.js | 4 +- src/pages/create.js | 2 +- src/pages/details/[slug]/index.js | 4 +- src/pages/profile.js | 2 +- src/pages/subscribe.js | 2 +- 21 files changed, 167 insertions(+), 117 deletions(-) create mode 100644 src/config/appConfig.js diff --git a/jsconfig.json b/jsconfig.json index b8d6842..443124b 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,7 +1,7 @@ { - "compilerOptions": { - "paths": { - "@/*": ["./src/*"] + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + } } - } -} +} \ No newline at end of file diff --git a/src/components/bitcoinConnect/CoursePaymentButton.js b/src/components/bitcoinConnect/CoursePaymentButton.js index d614881..e031a54 100644 --- a/src/components/bitcoinConnect/CoursePaymentButton.js +++ b/src/components/bitcoinConnect/CoursePaymentButton.js @@ -1,67 +1,78 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState, useEffect } from 'react'; import dynamic from 'next/dynamic'; -import { Button } from 'primereact/button'; -import { Dialog } from 'primereact/dialog'; // Import Dialog component -import { initializeBitcoinConnect } from './BitcoinConnect'; +import { Dialog } from 'primereact/dialog'; import { LightningAddress } from '@getalby/lightning-tools'; import { useToast } from '@/hooks/useToast'; import { useSession } from 'next-auth/react'; -import { useRouter } from 'next/router'; +import { ProgressSpinner } from 'primereact/progressspinner'; +import axios from 'axios'; import GenericButton from '@/components/buttons/GenericButton'; -import axios from 'axios'; // Import axios for API calls +import { useRouter } from 'next/router'; const Payment = dynamic( () => import('@getalby/bitcoin-connect-react').then((mod) => mod.Payment), - { - ssr: false, - } + { ssr: false } ); const CoursePaymentButton = ({ lnAddress, amount, onSuccess, onError, courseId }) => { const [invoice, setInvoice] = useState(null); - const [userId, setUserId] = useState(null); + const [isLoading, setIsLoading] = useState(false); const { showToast } = useToast(); const { data: session, status } = useSession(); - const [dialogVisible, setDialogVisible] = useState(false); // New state for dialog visibility + const [dialogVisible, setDialogVisible] = useState(false); const router = useRouter(); useEffect(() => { - initializeBitcoinConnect(); - }, []); - - useEffect(() => { - if (session && session.user) { - setUserId(session.user.id); + let intervalId; + if (invoice) { + intervalId = setInterval(async () => { + const paid = await invoice.verifyPayment(); + + if (paid && invoice.preimage) { + clearInterval(intervalId); + // handle success + handlePaymentSuccess({ paid, preimage: invoice.preimage }); + } + }, 2000); + } else { + console.log('no invoice'); } - }, [status, session]); - - useEffect(() => { - const fetchInvoice = async () => { - try { - const ln = new LightningAddress(lnAddress); - await ln.fetch(); - const invoice = await ln.requestInvoice({ satoshi: amount }); - setInvoice(invoice); - } catch (error) { - console.error('Error fetching invoice:', error); - showToast('error', 'Invoice Error', 'Failed to fetch the invoice.'); - if (onError) onError(error); + + return () => { + if (intervalId) { + clearInterval(intervalId); } }; + }, [invoice]); - fetchInvoice(); - }, [lnAddress, amount, onError, showToast]); + const fetchInvoice = async () => { + setIsLoading(true); + try { + const ln = new LightningAddress(lnAddress); + await ln.fetch(); + const invoice = await ln.requestInvoice({ satoshi: amount }); + setInvoice(invoice); + setDialogVisible(true); + } catch (error) { + console.error('Error fetching invoice:', error); + showToast('error', 'Invoice Error', 'Failed to fetch the invoice.'); + if (onError) onError(error); + } + setIsLoading(false); + }; + + useEffect(() => { + console.log('invoice', invoice); + }, [invoice]); const handlePaymentSuccess = async (response) => { try { const purchaseData = { - userId: userId, + userId: session.user.id, courseId: courseId, amountPaid: parseInt(amount, 10) }; - console.log('purchaseData', purchaseData); - const result = await axios.post('/api/purchase/course', purchaseData); if (result.status === 200) { @@ -75,27 +86,36 @@ const CoursePaymentButton = ({ lnAddress, amount, onSuccess, onError, courseId } showToast('error', 'Purchase Update Failed', 'Payment was successful, but failed to update user purchases.'); if (onError) onError(error); } - setDialogVisible(false); // Close the dialog on successful payment + setDialogVisible(false); }; return ( <> { if (status === 'unauthenticated') { console.log('unauthenticated'); router.push('/auth/signin'); } else { - setDialogVisible(true); + fetchInvoice(); } }} - disabled={!invoice} + disabled={isLoading} severity='primary' rounded - icon='pi pi-wallet' - className='text-[#f8f8ff] text-sm' + className={`text-[#f8f8ff] text-sm ${isLoading ? 'hidden' : ''}`} /> + {isLoading && ( +
+ +
+ )} setDialogVisible(false)} diff --git a/src/components/bitcoinConnect/ResourcePaymentButton.js b/src/components/bitcoinConnect/ResourcePaymentButton.js index 6ba70d7..4794b98 100644 --- a/src/components/bitcoinConnect/ResourcePaymentButton.js +++ b/src/components/bitcoinConnect/ResourcePaymentButton.js @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react'; import dynamic from 'next/dynamic'; import { Dialog } from 'primereact/dialog'; -import { initializeBitcoinConnect } from './BitcoinConnect'; import { LightningAddress } from '@getalby/lightning-tools'; import { useToast } from '@/hooks/useToast'; import { useSession } from 'next-auth/react'; @@ -17,43 +16,56 @@ const Payment = dynamic( const ResourcePaymentButton = ({ lnAddress, amount, onSuccess, onError, resourceId }) => { const [invoice, setInvoice] = useState(null); - const [userId, setUserId] = useState(null); + const [isLoading, setIsLoading] = useState(false); const { showToast } = useToast(); const { data: session, status } = useSession(); const [dialogVisible, setDialogVisible] = useState(false); const router = useRouter(); useEffect(() => { - initializeBitcoinConnect(); - }, []); + let intervalId; + if (invoice) { + intervalId = setInterval(async () => { + const paid = await invoice.verifyPayment(); - useEffect(() => { - if (session && session.user) { - setUserId(session.user.id); + if (paid && invoice.preimage) { + clearInterval(intervalId); + // handle success + handlePaymentSuccess({ paid, preimage: invoice.preimage }); + } + }, 2000); + } else { + console.log('no invoice'); } - }, [status, session]); - useEffect(() => { - const fetchInvoice = async () => { - try { - const ln = new LightningAddress(lnAddress); - await ln.fetch(); - const invoice = await ln.requestInvoice({ satoshi: amount }); - setInvoice(invoice); - } catch (error) { - console.error('Error fetching invoice:', error); - showToast('error', 'Invoice Error', 'Failed to fetch the invoice.'); - if (onError) onError(error); - } + return () => { + if (intervalId) { + clearInterval(intervalId); + } }; +}, [invoice]); - fetchInvoice(); - }, [lnAddress, amount, onError, showToast]); + const fetchInvoice = async () => { + setIsLoading(true); + try { + const ln = new LightningAddress(lnAddress); + await ln.fetch(); + const invoice = await ln.requestInvoice({ satoshi: amount }); + setInvoice(invoice); + setDialogVisible(true); + } catch (error) { + console.error('Error fetching invoice:', error); + showToast('error', 'Invoice Error', 'Failed to fetch the invoice.'); + if (onError) onError(error); + } + setIsLoading(false); + }; const handlePaymentSuccess = async (response) => { + console.log('handlePaymentSuccess', response); try { const purchaseData = { - userId: userId, + userId: session.user.id, resourceId: resourceId, amountPaid: parseInt(amount, 10) }; @@ -76,31 +88,31 @@ const ResourcePaymentButton = ({ lnAddress, amount, onSuccess, onError, resource return ( <> - { - invoice ? ( - { - if (status === 'unauthenticated') { - console.log('unauthenticated'); - router.push('/auth/signin'); - } else { - setDialogVisible(true); - } - }} - disabled={!invoice} - severity='primary' - rounded - className="text-[#f8f8ff] text-sm" - /> - ) : ( + { + if (status === 'unauthenticated') { + console.log('unauthenticated'); + router.push('/auth/signin'); + } else { + fetchInvoice(); + } + }} + disabled={isLoading} + severity='primary' + rounded + className={`text-[#f8f8ff] text-sm ${isLoading ? 'hidden' : ''}`} + /> + {isLoading && ( +
- )} +
+ )} setDialogVisible(false)} diff --git a/src/components/content/carousels/templates/CourseTemplate.js b/src/components/content/carousels/templates/CourseTemplate.js index bfcd79b..8f6fdcb 100644 --- a/src/components/content/carousels/templates/CourseTemplate.js +++ b/src/components/content/carousels/templates/CourseTemplate.js @@ -47,7 +47,7 @@ export function CourseTemplate({ course }) { } }, [course]); - if (!nAddress) return ; + if (!nAddress) return
if (zapsError) return
Error: {zapsError}
; diff --git a/src/components/content/courses/CourseDetails.js b/src/components/content/courses/CourseDetails.js index 6a66376..7b18fab 100644 --- a/src/components/content/courses/CourseDetails.js +++ b/src/components/content/courses/CourseDetails.js @@ -116,9 +116,7 @@ export default function CourseDetails({ processedEvent, paidCourse, lessons, dec if (!processedEvent || !author) { return ( -
- -
+
); } diff --git a/src/components/content/courses/CourseDetailsNew.js b/src/components/content/courses/CourseDetailsNew.js index 373de88..e92abc8 100644 --- a/src/components/content/courses/CourseDetailsNew.js +++ b/src/components/content/courses/CourseDetailsNew.js @@ -16,6 +16,7 @@ import useWindowWidth from "@/hooks/useWindowWidth"; import { useNDKContext } from "@/context/NDKContext"; import { findKind0Fields } from '@/utils/nostr'; import { defaultRelayUrls } from "@/context/NDKContext"; +import { ProgressSpinner } from 'primereact/progressspinner'; const lnAddress = process.env.NEXT_PUBLIC_LIGHTNING_ADDRESS; @@ -108,7 +109,7 @@ export default function CourseDetailsNew({ processedEvent, paidCourse, lessons, }; if (!processedEvent || !author) { - return
Loading...
; + return
; } return ( diff --git a/src/components/content/dropdowns/MessageDropdownItem.js b/src/components/content/dropdowns/MessageDropdownItem.js index 462e3ef..df165e2 100644 --- a/src/components/content/dropdowns/MessageDropdownItem.js +++ b/src/components/content/dropdowns/MessageDropdownItem.js @@ -1,6 +1,7 @@ import React, { useState, useEffect } from "react"; import CommunityMessage from "@/components/feeds/messages/CommunityMessage"; import { parseMessageEvent, findKind0Fields } from "@/utils/nostr"; +import { ProgressSpinner } from 'primereact/progressspinner'; import { useNDKContext } from "@/context/NDKContext"; const MessageDropdownItem = ({ message, onSelect }) => { @@ -79,7 +80,7 @@ const MessageDropdownItem = ({ message, onSelect }) => { return (
{loading ? ( -
Loading...
+
) : ( )} diff --git a/src/components/feeds/DiscordFeed.js b/src/components/feeds/DiscordFeed.js index 6935195..1914dd1 100644 --- a/src/components/feeds/DiscordFeed.js +++ b/src/components/feeds/DiscordFeed.js @@ -1,8 +1,7 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { ProgressSpinner } from 'primereact/progressspinner'; import { useDiscordQuery } from '@/hooks/communityQueries/useDiscordQuery'; import { useRouter } from 'next/router'; -import { highlightText } from '@/utils/text'; import CommunityMessage from '@/components/feeds/messages/CommunityMessage'; import useWindowWidth from '@/hooks/useWindowWidth'; @@ -11,6 +10,14 @@ const DiscordFeed = ({ searchQuery }) => { const { data, error, isLoading } = useDiscordQuery({page: router.query.page}); const windowWidth = useWindowWidth(); + // Memoize the filtered data + const filteredData = useMemo(() => { + if (!data) return []; + return data.filter(message => + message.content.toLowerCase().includes(searchQuery.toLowerCase()) + ); + }, [data, searchQuery]); + if (isLoading) { return (
@@ -23,14 +30,10 @@ const DiscordFeed = ({ searchQuery }) => { return
Failed to load messages. Please try again later.
; } - const filteredData = data.filter(message => - message.content.toLowerCase().includes(searchQuery.toLowerCase()) - ); - return (
- {filteredData && filteredData.length > 0 ? ( + {filteredData.length > 0 ? ( filteredData.map(message => ( { }; if (documentsLoading || videosLoading || draftsLoading) { - return ; + return
} return ( diff --git a/src/components/profile/UserContent.js b/src/components/profile/UserContent.js index 1c5118d..3ac85c1 100644 --- a/src/components/profile/UserContent.js +++ b/src/components/profile/UserContent.js @@ -142,7 +142,7 @@ const UserContent = () => {
{isLoading ? ( - +
) : isError ? (

Error loading content.

) : content.length > 0 ? ( diff --git a/src/components/profile/UserProfile.js b/src/components/profile/UserProfile.js index 6ad083a..3310855 100644 --- a/src/components/profile/UserProfile.js +++ b/src/components/profile/UserProfile.js @@ -89,7 +89,7 @@ const UserProfile = () => { )}
{!session || !session?.user || !ndk ? ( - +
) : ( {
{!session || !session?.user || !ndk ? ( - +
) : ( <> { > {isProcessing ? (
- +
Processing subscription...
) : ( diff --git a/src/components/profile/subscription/UserSubscription.js b/src/components/profile/subscription/UserSubscription.js index 579da12..0a6fc25 100644 --- a/src/components/profile/subscription/UserSubscription.js +++ b/src/components/profile/subscription/UserSubscription.js @@ -146,7 +146,7 @@ const UserSubscription = ({ user }) => { {isProcessing ? (
- +
Processing subscription...
) : ( diff --git a/src/config/appConfig.js b/src/config/appConfig.js new file mode 100644 index 0000000..327968b --- /dev/null +++ b/src/config/appConfig.js @@ -0,0 +1,17 @@ +const appConfig = { + defaultRelayUrls: [ + "wss://nos.lol/", + "wss://relay.damus.io/", + "wss://relay.snort.social/", + "wss://relay.nostr.band/", + "wss://relay.mutinywallet.com/", + "wss://relay.primal.net/", + "wss://nostr21.com/", + "wss://nostrue.com/", + "wss://purplerelay.com/", + // "wss://relay.devs.tools/" + ], + authorPubkeys: ["8cb60e215678879cda0bef4d5b3fc1a5c5925d2adb5d8c4fa7b7d03b5f2deaea"] + }; + +export default appConfig; \ No newline at end of file diff --git a/src/pages/course/[slug]/draft/index.js b/src/pages/course/[slug]/draft/index.js index 00eede9..8b3c28b 100644 --- a/src/pages/course/[slug]/draft/index.js +++ b/src/pages/course/[slug]/draft/index.js @@ -7,6 +7,8 @@ import DraftCourseLesson from "@/components/content/courses/DraftCourseLesson"; import { useNDKContext } from "@/context/NDKContext"; import { useSession } from "next-auth/react"; import { useIsAdmin } from "@/hooks/useIsAdmin"; +import { ProgressSpinner } from 'primereact/progressspinner'; + const DraftCourse = () => { const { data: session, status } = useSession(); const [course, setCourse] = useState(null); @@ -94,7 +96,7 @@ const DraftCourse = () => { }, [lessons, ndk, fetchAuthor, session, status]); if (status === "loading") { - return
Loading...
; + return
; } return ( diff --git a/src/pages/course/[slug]/index.js b/src/pages/course/[slug]/index.js index 46f2b91..d881cbd 100644 --- a/src/pages/course/[slug]/index.js +++ b/src/pages/course/[slug]/index.js @@ -194,9 +194,7 @@ const Course = () => { if (loading) { return ( -
- -
+
); } diff --git a/src/pages/create.js b/src/pages/create.js index 62cc553..c48e875 100644 --- a/src/pages/create.js +++ b/src/pages/create.js @@ -41,7 +41,7 @@ const Create = () => { if (!isAdmin) return null; - if (isLoading) return ; + if (isLoading) return
; return (
diff --git a/src/pages/details/[slug]/index.js b/src/pages/details/[slug]/index.js index 47e5a57..ab6bde7 100644 --- a/src/pages/details/[slug]/index.js +++ b/src/pages/details/[slug]/index.js @@ -192,9 +192,7 @@ export default function Details() { } if (loading) { - return
- -
; + return
} if (error) { diff --git a/src/pages/profile.js b/src/pages/profile.js index 6be3763..182f860 100644 --- a/src/pages/profile.js +++ b/src/pages/profile.js @@ -41,7 +41,7 @@ const Profile = () => { if (status === 'loading' || isLoading) { return ( - +
); } diff --git a/src/pages/subscribe.js b/src/pages/subscribe.js index 47d18e0..9f32915 100644 --- a/src/pages/subscribe.js +++ b/src/pages/subscribe.js @@ -170,7 +170,7 @@ const Subscribe = () => { {isProcessing ? (
- +
Processing subscription...
) : (