From 5d884cf2b666337bb1fe4aab1690bae146bf1e68 Mon Sep 17 00:00:00 2001 From: austinkelsay Date: Wed, 14 May 2025 14:43:01 -0500 Subject: [PATCH] update and unify modals into generic component --- src/components/MoreInfo.js | 6 +- .../bitcoinConnect/CoursePaymentButton.js | 8 +- .../bitcoinConnect/ResourcePaymentButton.js | 8 +- src/components/forms/course/LessonSelector.js | 10 +- src/components/onboarding/WelcomeModal.js | 9 +- src/components/profile/UserBadges.js | 12 +- src/components/profile/UserProfileCard.js | 10 +- .../profile/subscription/CalendlyEmbed.js | 8 +- .../subscription/CancelSubscription.js | 10 +- .../subscription/LightningAddressForm.js | 8 +- .../profile/subscription/Nip05Form.js | 8 +- .../profile/subscription/RenewSubscription.js | 7 +- .../profile/subscription/SubscribeModal.js | 9 +- src/components/ui/Modal.js | 112 ++++++++++++++++++ 14 files changed, 173 insertions(+), 52 deletions(-) create mode 100644 src/components/ui/Modal.js diff --git a/src/components/MoreInfo.js b/src/components/MoreInfo.js index aed142a..eacd40b 100644 --- a/src/components/MoreInfo.js +++ b/src/components/MoreInfo.js @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Dialog } from 'primereact/dialog'; +import Modal from '@/components/ui/Modal'; import { Tooltip } from 'primereact/tooltip'; import useWindowWidth from '@/hooks/useWindowWidth'; @@ -24,7 +24,7 @@ const MoreInfo = ({ /> {!isMobile && } - setVisible(false)} @@ -32,7 +32,7 @@ const MoreInfo = ({ breakpoints={{ '960px': '75vw', '641px': '90vw' }} > {typeof modalBody === 'string' ?

{modalBody}

: modalBody} -
+ ); }; diff --git a/src/components/bitcoinConnect/CoursePaymentButton.js b/src/components/bitcoinConnect/CoursePaymentButton.js index 49bdb1f..98cb732 100644 --- a/src/components/bitcoinConnect/CoursePaymentButton.js +++ b/src/components/bitcoinConnect/CoursePaymentButton.js @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import dynamic from 'next/dynamic'; -import { Dialog } from 'primereact/dialog'; +import Modal from '@/components/ui/Modal'; import { LightningAddress } from '@getalby/lightning-tools'; import { track } from '@vercel/analytics'; import { useToast } from '@/hooks/useToast'; @@ -227,11 +227,11 @@ const CoursePaymentButton = ({ lnAddress, amount, onSuccess, onError, courseId } /> )} - setDialogVisible(false)} header="Make Payment" - style={{ width: isMobile ? '90vw' : '50vw' }} + width={isMobile ? '90vw' : '50vw'} > {invoice ? ( Loading payment details...

)} -
+ ); }; diff --git a/src/components/bitcoinConnect/ResourcePaymentButton.js b/src/components/bitcoinConnect/ResourcePaymentButton.js index 75617b3..455319b 100644 --- a/src/components/bitcoinConnect/ResourcePaymentButton.js +++ b/src/components/bitcoinConnect/ResourcePaymentButton.js @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import dynamic from 'next/dynamic'; -import { Dialog } from 'primereact/dialog'; +import Modal from '@/components/ui/Modal'; import { track } from '@vercel/analytics'; import { LightningAddress } from '@getalby/lightning-tools'; import { useToast } from '@/hooks/useToast'; @@ -122,11 +122,11 @@ const ResourcePaymentButton = ({ lnAddress, amount, onSuccess, onError, resource /> )} - setDialogVisible(false)} header="Make Payment" - style={{ width: isMobile ? '90vw' : '50vw' }} + width={isMobile ? '90vw' : '50vw'} > {invoice ? ( Loading payment details...

)} -
+ ); }; diff --git a/src/components/forms/course/LessonSelector.js b/src/components/forms/course/LessonSelector.js index 52fd7aa..639d6a3 100644 --- a/src/components/forms/course/LessonSelector.js +++ b/src/components/forms/course/LessonSelector.js @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import { Dropdown } from 'primereact/dropdown'; import GenericButton from '@/components/buttons/GenericButton'; -import { Dialog } from 'primereact/dialog'; +import Modal from '@/components/ui/Modal'; import { Accordion, AccordionTab } from 'primereact/accordion'; import EmbeddedDocumentForm from '@/components/forms/course/embedded/EmbeddedDocumentForm'; import EmbeddedVideoForm from '@/components/forms/course/embedded/EmbeddedVideoForm'; @@ -233,23 +233,23 @@ const LessonSelector = ({ - setShowDocumentForm(false)} header="Create New Document" > - + - setShowVideoForm(false)} header="Create New Video" > - + ); }; diff --git a/src/components/onboarding/WelcomeModal.js b/src/components/onboarding/WelcomeModal.js index 1a49bae..fce0514 100644 --- a/src/components/onboarding/WelcomeModal.js +++ b/src/components/onboarding/WelcomeModal.js @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { Dialog } from 'primereact/dialog'; +import Modal from '@/components/ui/Modal'; import { useRouter } from 'next/router'; const WelcomeModal = () => { @@ -26,10 +26,11 @@ const WelcomeModal = () => { }; return ( -
@@ -74,7 +75,7 @@ const WelcomeModal = () => {

Let's start your coding journey! 🚀

-
+ ); }; diff --git a/src/components/profile/UserBadges.js b/src/components/profile/UserBadges.js index b779025..b406138 100644 --- a/src/components/profile/UserBadges.js +++ b/src/components/profile/UserBadges.js @@ -1,5 +1,5 @@ import React, { useState, useEffect, useCallback } from 'react'; -import { Dialog } from 'primereact/dialog'; +import Modal from '@/components/ui/Modal'; import Image from 'next/image'; import { useNDKContext } from '@/context/NDKContext'; import { useSession } from 'next-auth/react'; @@ -99,7 +99,13 @@ const UserBadges = ({ visible, onHide }) => { }; return ( - +
{loading ? (
@@ -147,7 +153,7 @@ const UserBadges = ({ visible, onHide }) => {
)}
-
+ ); }; diff --git a/src/components/profile/UserProfileCard.js b/src/components/profile/UserProfileCard.js index 1414973..e8d2e79 100644 --- a/src/components/profile/UserProfileCard.js +++ b/src/components/profile/UserProfileCard.js @@ -1,7 +1,7 @@ import React, { useRef, useState } from 'react'; import Image from 'next/image'; import { Menu } from 'primereact/menu'; -import { Dialog } from 'primereact/dialog'; +import Modal from '@/components/ui/Modal'; import { nip19 } from 'nostr-tools'; import { useImageProxy } from '@/hooks/useImageProxy'; import { useToast } from '@/hooks/useToast'; @@ -284,12 +284,12 @@ const UserProfileCard = ({ user }) => { <> {windowWidth <= 1440 ? : } setShowBadges(false)} /> - setShowRelaysModal(false)} header="Manage Relays" - className="w-[90vw] max-w-[800px]" - modal + width="full" + className="max-w-[800px]" > { setUserRelays={setUserRelays} reInitializeNDK={reInitializeNDK} /> - + ); }; diff --git a/src/components/profile/subscription/CalendlyEmbed.js b/src/components/profile/subscription/CalendlyEmbed.js index fc57588..77fcc71 100644 --- a/src/components/profile/subscription/CalendlyEmbed.js +++ b/src/components/profile/subscription/CalendlyEmbed.js @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import { Dialog } from 'primereact/dialog'; +import Modal from '@/components/ui/Modal'; import { Button } from 'primereact/button'; import useWindowWidth from '@/hooks/useWindowWidth'; @@ -25,10 +25,10 @@ const CalendlyEmbed = ({ visible, onHide, userId, userEmail, userName }) => { ); return ( - @@ -37,7 +37,7 @@ const CalendlyEmbed = ({ visible, onHide, userId, userEmail, userName }) => { data-url={`https://calendly.com/plebdevs/30min?hide_event_type_details=1&hide_gdpr_banner=1&email=${encodeURIComponent(userEmail)}&name=${encodeURIComponent(userName)}&custom_data=${encodeURIComponent(JSON.stringify({ user_id: userId }))}`} style={{ minWidth: '320px', height: '700px' }} /> - + ); }; diff --git a/src/components/profile/subscription/CancelSubscription.js b/src/components/profile/subscription/CancelSubscription.js index 4392e06..1441219 100644 --- a/src/components/profile/subscription/CancelSubscription.js +++ b/src/components/profile/subscription/CancelSubscription.js @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { Dialog } from 'primereact/dialog'; +import Modal from '@/components/ui/Modal'; import axios from 'axios'; import { useSession } from 'next-auth/react'; import { useToast } from '@/hooks/useToast'; @@ -31,7 +31,11 @@ const CancelSubscription = ({ visible, onHide }) => { }; return ( - +

Are you sure you want to cancel your subscription?

{isProcessing ? ( @@ -46,7 +50,7 @@ const CancelSubscription = ({ visible, onHide }) => { /> )}
-
+ ); }; diff --git a/src/components/profile/subscription/LightningAddressForm.js b/src/components/profile/subscription/LightningAddressForm.js index 7256bb5..7a4823c 100644 --- a/src/components/profile/subscription/LightningAddressForm.js +++ b/src/components/profile/subscription/LightningAddressForm.js @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { Dialog } from 'primereact/dialog'; +import Modal from '@/components/ui/Modal'; import axios from 'axios'; import { useSession } from 'next-auth/react'; import { useToast } from '@/hooks/useToast'; @@ -111,11 +111,11 @@ const LightningAddressForm = ({ visible, onHide }) => { }; return ( - {existingLightningAddress ? (

Update your Lightning Address details

@@ -216,7 +216,7 @@ const LightningAddressForm = ({ visible, onHide }) => { /> )} -
+ ); }; diff --git a/src/components/profile/subscription/Nip05Form.js b/src/components/profile/subscription/Nip05Form.js index 9e96be2..586d97d 100644 --- a/src/components/profile/subscription/Nip05Form.js +++ b/src/components/profile/subscription/Nip05Form.js @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { Dialog } from 'primereact/dialog'; +import Modal from '@/components/ui/Modal'; import axios from 'axios'; import { useSession } from 'next-auth/react'; import { useToast } from '@/hooks/useToast'; @@ -82,11 +82,11 @@ const Nip05Form = ({ visible, onHide }) => { }; return ( - {existingNip05 ?

Update your Pubkey and Name

:

Confirm your Pubkey and Name

}
@@ -126,7 +126,7 @@ const Nip05Form = ({ visible, onHide }) => { />
)} -
+ ); }; diff --git a/src/components/profile/subscription/RenewSubscription.js b/src/components/profile/subscription/RenewSubscription.js index b26d222..56cb3f4 100644 --- a/src/components/profile/subscription/RenewSubscription.js +++ b/src/components/profile/subscription/RenewSubscription.js @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Dialog } from 'primereact/dialog'; +import Modal from '@/components/ui/Modal'; import { Card } from 'primereact/card'; import { ProgressSpinner } from 'primereact/progressspinner'; import SubscriptionPaymentButtons from '@/components/bitcoinConnect/SubscriptionPaymentButton'; @@ -50,11 +50,10 @@ const RenewSubscription = ({ visible, onHide, subscribedUntil }) => { }; return ( - {isProcessing ? (
@@ -81,7 +80,7 @@ const RenewSubscription = ({ visible, onHide, subscribedUntil }) => { /> )} -
+ ); }; diff --git a/src/components/profile/subscription/SubscribeModal.js b/src/components/profile/subscription/SubscribeModal.js index 131128e..dcc36cb 100644 --- a/src/components/profile/subscription/SubscribeModal.js +++ b/src/components/profile/subscription/SubscribeModal.js @@ -1,5 +1,5 @@ import React, { useState, useRef, useEffect } from 'react'; -import { Dialog } from 'primereact/dialog'; +import Modal from '@/components/ui/Modal'; import { ProgressSpinner } from 'primereact/progressspinner'; import SubscriptionPaymentButtons from '@/components/bitcoinConnect/SubscriptionPaymentButton'; import axios from 'axios'; @@ -216,11 +216,10 @@ const SubscribeModal = ({ user }) => { )} - {isProcessing ? (
@@ -230,7 +229,7 @@ const SubscribeModal = ({ user }) => { Processing subscription...
) : ( - +

Unlock Premium Benefits

Subscribe now and elevate your development journey!

@@ -295,7 +294,7 @@ const SubscribeModal = ({ user }) => {
)} -
+ setCalendlyVisible(false)} diff --git a/src/components/ui/Modal.js b/src/components/ui/Modal.js new file mode 100644 index 0000000..5f35479 --- /dev/null +++ b/src/components/ui/Modal.js @@ -0,0 +1,112 @@ +import React from 'react'; +import { Dialog } from 'primereact/dialog'; +import { Button } from 'primereact/button'; + +/** + * A generic modal component based on PrimeReact Dialog with dark styling + * @param {Object} props - Component props + * @param {string} props.header - Modal header text + * @param {boolean} props.visible - Whether the modal is visible + * @param {Function} props.onHide - Function to call when modal is closed + * @param {React.ReactNode} props.children - Modal content + * @param {string} props.className - Additional CSS classes for the modal + * @param {Object} props.style - Additional inline styles for the modal + * @param {string} props.width - Width of the modal (fit, full, or pixel value) + * @param {React.ReactNode} props.footer - Custom footer content + * @param {Object} props.headerStyle - Additional styles for the header + * @param {Object} props.contentStyle - Additional styles for the content + * @param {Object} props.footerStyle - Additional styles for the footer + * @param {Object} props.breakpoints - Responsive breakpoints (e.g. {'960px': '75vw'}) + * @param {boolean} props.modal - Whether the modal requires a click on the mask to hide + * @param {boolean} props.draggable - Whether the modal is draggable + * @param {boolean} props.resizable - Whether the modal is resizable + * @param {boolean} props.maximizable - Whether the modal can be maximized + * @param {boolean} props.dismissableMask - Whether clicking outside closes the modal + * @param {boolean} props.showCloseButton - Whether to show the default close button in the footer + */ +const Modal = ({ + header, + visible, + onHide, + children, + className = '', + style = {}, + width = 'fit', + footer, + headerStyle = {}, + contentStyle = {}, + footerStyle = {}, + breakpoints, + modal, + draggable, + resizable, + maximizable, + dismissableMask, + showCloseButton = false, + ...otherProps +}) => { + // Base dark styling + const baseStyle = { backgroundColor: '#1f2937' }; + const baseHeaderStyle = { backgroundColor: '#1f2937', color: 'white' }; + const baseContentStyle = { backgroundColor: '#1f2937' }; + const baseFooterStyle = { backgroundColor: '#1f2937', borderTop: '1px solid #374151' }; + + // Determine width class + let widthClass = ''; + if (width === 'fit') { + widthClass = 'w-fit'; + } else if (width === 'full') { + widthClass = 'w-full'; + } else { + // Custom width will be handled via style + style.width = width; + } + + // Create footer with close button if requested + const footerContent = showCloseButton ? ( +
+
+ ) : footer; + + // Apply tailwind CSS to modify dialog elements + const dialogClassNames = ` + .p-dialog-footer { + background-color: #1f2937 !important; + border-top: 1px solid #374151 !important; + } + `; + + return ( + <> + + + {children} + + + ); +}; + +export default Modal; \ No newline at end of file