From 6d2b782045e0fa1118351b03a7e65c856eeb7de3 Mon Sep 17 00:00:00 2001 From: kiwihodl Date: Tue, 18 Mar 2025 14:45:44 -0500 Subject: [PATCH] feat(modal): implement backdrop blur for MoreInfo component You can see this commented out in the Subscribe page - Add background blur effect when MoreInfo modal is active - Implement click-outside behavior to close modal - Add smooth transition for blur effect - Match search bar blur functionality - Ensure navigation stays clear while content is blurred - Add dismissable mask for better UX --- src/components/MoreInfo.js | 93 ++-- src/components/moreinfo.module.css | 4 + src/pages/subscribe.js | 781 ++++++++++++++++++----------- 3 files changed, 548 insertions(+), 330 deletions(-) create mode 100644 src/components/moreinfo.module.css diff --git a/src/components/MoreInfo.js b/src/components/MoreInfo.js index cb5aedf..1e0c858 100644 --- a/src/components/MoreInfo.js +++ b/src/components/MoreInfo.js @@ -1,40 +1,63 @@ -import React, { useState } from 'react'; -import { Dialog } from 'primereact/dialog'; -import { Tooltip } from 'primereact/tooltip'; -import useWindowWidth from '@/hooks/useWindowWidth'; +import React, { useState, useEffect } from "react"; +import { Dialog } from "primereact/dialog"; +import { Tooltip } from "primereact/tooltip"; +import useWindowWidth from "@/hooks/useWindowWidth"; +import styles from "./moreinfo.module.css"; -const MoreInfo = ({ tooltip, modalTitle, modalBody, className = '' }) => { - const [visible, setVisible] = useState(false); - const windowWidth = useWindowWidth(); - const isMobile = windowWidth < 768; +const MoreInfo = ({ tooltip, modalTitle, modalBody, className = "" }) => { + const [visible, setVisible] = useState(false); + const windowWidth = useWindowWidth(); + const isMobile = windowWidth < 768; - return ( - <> - setVisible(true)} - data-pr-tooltip={tooltip} - data-pr-position="right" - data-pr-at="right+5 top" - data-pr-my="left center-2" - /> - {!isMobile && } - - setVisible(false)} - className="max-w-3xl" - breakpoints={{ '960px': '75vw', '641px': '90vw' }} - > - {typeof modalBody === 'string' ? ( -

{modalBody}

- ) : ( - modalBody - )} -
- - ); + // Add blur effect when modal is visible + useEffect(() => { + const mainContent = document.querySelector(".main-content"); + if (mainContent) { + if (visible) { + mainContent.classList.add(styles.blurredContent); + } else { + mainContent.classList.remove(styles.blurredContent); + } + } + }, [visible]); + + const onHide = () => { + setVisible(false); + }; + + return ( + <> + setVisible(true)} + data-pr-tooltip={tooltip} + data-pr-position="right" + data-pr-at="right+5 top" + data-pr-my="left center-2" + /> + {!isMobile && } + + + {typeof modalBody === "string" ? ( +

{modalBody}

+ ) : ( + modalBody + )} +
+ + ); }; export default MoreInfo; diff --git a/src/components/moreinfo.module.css b/src/components/moreinfo.module.css new file mode 100644 index 0000000..c818a4c --- /dev/null +++ b/src/components/moreinfo.module.css @@ -0,0 +1,4 @@ +.blurredContent { + filter: blur(8px); + transition: filter 0.2s ease-in-out; +} \ No newline at end of file diff --git a/src/pages/subscribe.js b/src/pages/subscribe.js index 737cf53..fad7eb5 100644 --- a/src/pages/subscribe.js +++ b/src/pages/subscribe.js @@ -1,306 +1,497 @@ -import React, { useState, useRef, useEffect } from 'react'; -import { useSession } from 'next-auth/react'; -import { useRouter } from 'next/router'; -import { useToast } from '@/hooks/useToast'; -import axios from 'axios'; -import { Card } from 'primereact/card'; -import { Message } from 'primereact/message'; -import useWindowWidth from '@/hooks/useWindowWidth'; +import React, { useState, useRef, useEffect } from "react"; +import { useSession } from "next-auth/react"; +import { useRouter } from "next/router"; +import { useToast } from "@/hooks/useToast"; +import axios from "axios"; +import { Card } from "primereact/card"; +import { Message } from "primereact/message"; +import useWindowWidth from "@/hooks/useWindowWidth"; import { Menu } from "primereact/menu"; -import GenericButton from '@/components/buttons/GenericButton'; -import { ProgressSpinner } from 'primereact/progressspinner'; -import SubscriptionPaymentButtons from '@/components/bitcoinConnect/SubscriptionPaymentButton'; -import Image from 'next/image'; -import NostrIcon from '../../public/images/nostr.png'; -import CalendlyEmbed from '@/components/profile/subscription/CalendlyEmbed'; -import CancelSubscription from '@/components/profile/subscription/CancelSubscription'; -import RenewSubscription from '@/components/profile/subscription/RenewSubscription'; -import Nip05Form from '@/components/profile/subscription/Nip05Form'; -import LightningAddressForm from '@/components/profile/subscription/LightningAddressForm'; +import GenericButton from "@/components/buttons/GenericButton"; +import { ProgressSpinner } from "primereact/progressspinner"; +import SubscriptionPaymentButtons from "@/components/bitcoinConnect/SubscriptionPaymentButton"; +import Image from "next/image"; +import NostrIcon from "../../public/images/nostr.png"; +import CalendlyEmbed from "@/components/profile/subscription/CalendlyEmbed"; +import CancelSubscription from "@/components/profile/subscription/CancelSubscription"; +import RenewSubscription from "@/components/profile/subscription/RenewSubscription"; +import Nip05Form from "@/components/profile/subscription/Nip05Form"; +import LightningAddressForm from "@/components/profile/subscription/LightningAddressForm"; + +import MoreInfo from '@/components/MoreInfo'; const Subscribe = () => { - const { data: session, update } = useSession(); - const { showToast } = useToast(); - const router = useRouter(); - const menu = useRef(null); - const windowWidth = useWindowWidth(); - const [user, setUser] = useState(null); - const [isProcessing, setIsProcessing] = useState(false); - const [subscribed, setSubscribed] = useState(false); - const [subscribedUntil, setSubscribedUntil] = useState(null); - const [subscriptionExpiredAt, setSubscriptionExpiredAt] = useState(null); - const [calendlyVisible, setCalendlyVisible] = useState(false); - const [lightningAddressVisible, setLightningAddressVisible] = useState(false); - const [nip05Visible, setNip05Visible] = useState(false); - const [cancelSubscriptionVisible, setCancelSubscriptionVisible] = useState(false); - const [renewSubscriptionVisible, setRenewSubscriptionVisible] = useState(false); + const { data: session, update } = useSession(); + const { showToast } = useToast(); + const router = useRouter(); + const menu = useRef(null); + const windowWidth = useWindowWidth(); + const [user, setUser] = useState(null); + const [isProcessing, setIsProcessing] = useState(false); + const [subscribed, setSubscribed] = useState(false); + const [subscribedUntil, setSubscribedUntil] = useState(null); + const [subscriptionExpiredAt, setSubscriptionExpiredAt] = useState(null); + const [calendlyVisible, setCalendlyVisible] = useState(false); + const [lightningAddressVisible, setLightningAddressVisible] = useState(false); + const [nip05Visible, setNip05Visible] = useState(false); + const [cancelSubscriptionVisible, setCancelSubscriptionVisible] = + useState(false); + const [renewSubscriptionVisible, setRenewSubscriptionVisible] = + useState(false); - useEffect(() => { - if (session && session?.user) { - setUser(session.user); - } - }, [session]) + useEffect(() => { + if (session && session?.user) { + setUser(session.user); + } + }, [session]); - useEffect(() => { - if (user && user.role) { - setSubscribed(user.role.subscribed); - const subscribedAt = new Date(user.role.lastPaymentAt); - const subscribedUntil = new Date(subscribedAt.getTime() + 31 * 24 * 60 * 60 * 1000); - setSubscribedUntil(subscribedUntil); - if (user.role.subscriptionExpiredAt) { - const expiredAt = new Date(user.role.subscriptionExpiredAt) - setSubscriptionExpiredAt(expiredAt); - } - } - }, [user]); + useEffect(() => { + if (user && user.role) { + setSubscribed(user.role.subscribed); + const subscribedAt = new Date(user.role.lastPaymentAt); + const subscribedUntil = new Date( + subscribedAt.getTime() + 31 * 24 * 60 * 60 * 1000 + ); + setSubscribedUntil(subscribedUntil); + if (user.role.subscriptionExpiredAt) { + const expiredAt = new Date(user.role.subscriptionExpiredAt); + setSubscriptionExpiredAt(expiredAt); + } + } + }, [user]); - const handleSubscriptionSuccess = async (response) => { - setIsProcessing(true); - try { - const apiResponse = await axios.put('/api/users/subscription', { - userId: session.user.id, - isSubscribed: true, - }); - if (apiResponse.data) { - await update(); - showToast('success', 'Subscription Successful', 'Your subscription has been activated.'); - } else { - throw new Error('Failed to update subscription status'); - } - } catch (error) { - console.error('Subscription update error:', error); - showToast('error', 'Subscription Update Failed', `Error: ${error.message}`); - } finally { - setIsProcessing(false); - } - }; + const handleSubscriptionSuccess = async (response) => { + setIsProcessing(true); + try { + const apiResponse = await axios.put("/api/users/subscription", { + userId: session.user.id, + isSubscribed: true, + }); + if (apiResponse.data) { + await update(); + showToast( + "success", + "Subscription Successful", + "Your subscription has been activated." + ); + } else { + throw new Error("Failed to update subscription status"); + } + } catch (error) { + console.error("Subscription update error:", error); + showToast( + "error", + "Subscription Update Failed", + `Error: ${error.message}` + ); + } finally { + setIsProcessing(false); + } + }; - const handleSubscriptionError = (error) => { - console.error('Subscription error:', error); - showToast('error', 'Subscription Failed', `An error occurred: ${error.message}`); - setIsProcessing(false); - }; - - const handleRecurringSubscriptionSuccess = async () => { - setIsProcessing(true); - try { - await update(); - showToast('success', 'Recurring Subscription Activated', 'Your recurring subscription has been set up successfully.'); - } catch (error) { - console.error('Session update error:', error); - showToast('error', 'Session Update Failed', `Error: ${error.message}`); - } finally { - setIsProcessing(false); - } - }; - - const menuItems = [ - { - label: "Schedule 1:1", - icon: "pi pi-calendar", - command: () => setCalendlyVisible(true), - }, - { - label: session?.user?.platformLightningAddress ? "Update PlebDevs Lightning Address" : "Claim PlebDevs Lightning Address", - icon: "pi pi-bolt", - command: () => setLightningAddressVisible(true), - }, - { - label: session?.user?.platformNip05?.name ? "Update PlebDevs Nostr NIP-05" : "Claim PlebDevs Nostr NIP-05", - icon: "pi pi-at", - command: () => setNip05Visible(true), - }, - { - label: "Renew Subscription", - icon: "pi pi-sync", - command: () => setRenewSubscriptionVisible(true), - }, - { - label: "Cancel Subscription", - icon: "pi pi-trash", - command: () => setCancelSubscriptionVisible(true), - }, - ]; - - return ( -
- {windowWidth < 768 && ( -

Subscription Management

- )} -
- {session && session?.user ? ( - <> - {subscribed && !user?.role?.nwc && ( -
- -

Thank you for your support 🎉

-

Pay-as-you-go subscription must be manually renewed on {subscribedUntil.toLocaleDateString()}

-
- )} - {subscribed && user?.role?.nwc && ( -
- -

Thank you for your support 🎉

-

Recurring subscription will AUTO renew on {subscribedUntil.toLocaleDateString()}

-
- )} - {(!subscribed && !subscriptionExpiredAt) && ( -
- -
- )} - {subscriptionExpiredAt && ( -
- -
- )} - - ) : ( -
- -
- )} -
- - {!session?.user && ( - <> - -

- The PlebDevs subscription unlocks all paid content, grants access to our 1:1 calendar for tutoring, support, and mentorship, and grants you your own personal plebdevs.com Lightning Address and Nostr NIP-05 identity. -

-

- Subscribe monthly with a pay-as-you-go option or set up an auto-recurring subscription using Nostr Wallet Connect. -

-
- -

Login to start your subscription!

- router.push('/auth/signin')} className='text-[#f8f8ff] w-fit' rounded icon="pi pi-user" /> -
- - )} - - - {isProcessing ? ( -
-
- Processing subscription... -
- ) : ( -
-
-

Unlock Premium Benefits

-

Subscribe now and elevate your development journey!

-
-
-
- - Access ALL current and future PlebDevs content -
-
- - Personal mentorship & guidance and access to exclusive 1:1 booking calendar -
-
- - Claim your own personal plebdevs.com Lightning Address -
-
- Nostr - Claim your own personal plebdevs.com Nostr NIP-05 identity -
-
- -
- )} -
- - {session?.user && subscribed && ( - <> - -
- setCalendlyVisible(true)} /> - setNip05Visible(true)} /> - } onClick={() => setLightningAddressVisible(true)} /> -
- - -
- setRenewSubscriptionVisible(true)} /> - setCancelSubscriptionVisible(true)} /> -
-
- - )} - - -
-
-

How does the subscription work?

-

Think of the subscriptions as a Patreon-type model. You pay a monthly fee and in return you get access to premium features and all of the paid content. You can cancel at any time.

-
-
-

What are the benefits of a subscription?

-

The subscription gives you access to all of the premium features and all of the paid content. You can cancel at any time.

-
-
-

How much does the subscription cost?

-

The subscription is 50,000 sats per month.

-
-
-

How do I Subscribe? (Pay as you go)

-

The pay as you go subscription is a one-time payment that gives you access to all of the premium features for one month. You will need to manually renew your subscription every month.

-
-
-

How do I Subscribe? (Recurring)

-

The recurring subscription option allows you to submit a Nostr Wallet Connect URI that will be used to automatically send the subscription fee every month. You can cancel at any time.

-
-
-

Can I cancel my subscription?

-

Yes, you can cancel your subscription at any time. Your access will remain active until the end of the current billing period.

-
-
-

What happens if I don't renew my subscription?

-

If you don't renew your subscription, your access to 1:1 calendar and paid content will be removed. However, you will still have access to your PlebDevs Lightning Address, NIP-05, and any content that you paid for.

-
-
-

What is Nostr Wallet Connect?

-

Nostr Wallet Connect is a Nostr-based authentication method that allows you to connect your Nostr wallet to the PlebDevs platform. This will allow you to subscribe to the platform in an auto recurring manner which still gives you full control over your wallet and the ability to cancel at any time from your wallet.

-
-
-
- - setCalendlyVisible(false)} - userId={session?.user?.id} - userName={session?.user?.username || user?.kind0?.username} - userEmail={session?.user?.email} - /> - setCancelSubscriptionVisible(false)} - /> - setRenewSubscriptionVisible(false)} - subscribedUntil={subscribedUntil} - /> - setNip05Visible(false)} - /> - setLightningAddressVisible(false)} - /> -
+ const handleSubscriptionError = (error) => { + console.error("Subscription error:", error); + showToast( + "error", + "Subscription Failed", + `An error occurred: ${error.message}` ); + setIsProcessing(false); + }; + + const handleRecurringSubscriptionSuccess = async () => { + setIsProcessing(true); + try { + await update(); + showToast( + "success", + "Recurring Subscription Activated", + "Your recurring subscription has been set up successfully." + ); + } catch (error) { + console.error("Session update error:", error); + showToast("error", "Session Update Failed", `Error: ${error.message}`); + } finally { + setIsProcessing(false); + } + }; + + const menuItems = [ + { + label: "Schedule 1:1", + icon: "pi pi-calendar", + command: () => setCalendlyVisible(true), + }, + { + label: session?.user?.platformLightningAddress + ? "Update PlebDevs Lightning Address" + : "Claim PlebDevs Lightning Address", + icon: "pi pi-bolt", + command: () => setLightningAddressVisible(true), + }, + { + label: session?.user?.platformNip05?.name + ? "Update PlebDevs Nostr NIP-05" + : "Claim PlebDevs Nostr NIP-05", + icon: "pi pi-at", + command: () => setNip05Visible(true), + }, + { + label: "Renew Subscription", + icon: "pi pi-sync", + command: () => setRenewSubscriptionVisible(true), + }, + { + label: "Cancel Subscription", + icon: "pi pi-trash", + command: () => setCancelSubscriptionVisible(true), + }, + ]; + + return ( +
+ {windowWidth < 768 && ( +

Subscription Management

+ )} +
+ {session && session?.user ? ( + <> + {subscribed && !user?.role?.nwc && ( +
+ +

Thank you for your support 🎉

+

+ Pay-as-you-go subscription must be manually renewed on{" "} + {subscribedUntil.toLocaleDateString()} +

+
+ )} + {subscribed && user?.role?.nwc && ( +
+ +

Thank you for your support 🎉

+

+ Recurring subscription will AUTO renew on{" "} + {subscribedUntil.toLocaleDateString()} +

+
+ )} + {!subscribed && !subscriptionExpiredAt && ( +
+ +
+ )} + {subscriptionExpiredAt && ( +
+ +
+ )} + + ) : ( +
+ + {/* Test MoreInfo modal blur here */} + {/*
+ + +

As a PlebDevs subscriber, you get access to:

+
    +
  • Full access to all courses and content
  • +
  • Exclusive developer resources
  • +
  • Priority support
  • +
  • Community features
  • +
+
+ } + /> +
*/} +
+ )} +
+ + {!session?.user && ( + <> + +

+ The PlebDevs subscription unlocks all paid content, grants access + to our 1:1 calendar for tutoring, support, and mentorship, and + grants you your own personal plebdevs.com Lightning Address and + Nostr NIP-05 identity. +

+

+ Subscribe monthly with a pay-as-you-go option or set up an + auto-recurring subscription using Nostr Wallet Connect. +

+
+ +

Login to start your subscription!

+ router.push("/auth/signin")} + className="text-[#f8f8ff] w-fit" + rounded + icon="pi pi-user" + /> +
+ + )} + + + {isProcessing ? ( +
+
+ +
+ Processing subscription... +
+ ) : ( +
+
+

+ Unlock Premium Benefits +

+

+ Subscribe now and elevate your development journey! +

+
+
+
+ + Access ALL current and future PlebDevs content +
+
+ + + Personal mentorship & guidance and access to exclusive 1:1 + booking calendar + +
+
+ + + Claim your own personal plebdevs.com Lightning Address + +
+
+ Nostr + + Claim your own personal plebdevs.com Nostr NIP-05 identity + +
+
+ +
+ )} +
+ + {session?.user && subscribed && ( + <> + +
+ setCalendlyVisible(true)} + /> + setNip05Visible(true)} + /> + + } + onClick={() => setLightningAddressVisible(true)} + /> +
+ + +
+ setRenewSubscriptionVisible(true)} + /> + setCancelSubscriptionVisible(true)} + /> +
+
+ + )} + + +
+
+

+ How does the subscription work? +

+

+ Think of the subscriptions as a Patreon-type model. You pay a + monthly fee and in return you get access to premium features and + all of the paid content. You can cancel at any time. +

+
+
+

+ What are the benefits of a subscription? +

+

+ The subscription gives you access to all of the premium features + and all of the paid content. You can cancel at any time. +

+
+
+

+ How much does the subscription cost? +

+

The subscription is 50,000 sats per month.

+
+
+

+ How do I Subscribe? (Pay as you go) +

+

+ The pay as you go subscription is a one-time payment that gives + you access to all of the premium features for one month. You will + need to manually renew your subscription every month. +

+
+
+

+ How do I Subscribe? (Recurring) +

+

+ The recurring subscription option allows you to submit a Nostr + Wallet Connect URI that will be used to automatically send the + subscription fee every month. You can cancel at any time. +

+
+
+

+ Can I cancel my subscription? +

+

+ Yes, you can cancel your subscription at any time. Your access + will remain active until the end of the current billing period. +

+
+
+

+ What happens if I don't renew my subscription? +

+

+ If you don't renew your subscription, your access to 1:1 + calendar and paid content will be removed. However, you will still + have access to your PlebDevs Lightning Address, NIP-05, and any + content that you paid for. +

+
+
+

+ What is Nostr Wallet Connect? +

+

+ Nostr Wallet Connect is a Nostr-based authentication method that + allows you to connect your Nostr wallet to the PlebDevs platform. + This will allow you to subscribe to the platform in an auto + recurring manner which still gives you full control over your + wallet and the ability to cancel at any time from your wallet. +

+
+
+
+ + setCalendlyVisible(false)} + userId={session?.user?.id} + userName={session?.user?.username || user?.kind0?.username} + userEmail={session?.user?.email} + /> + setCancelSubscriptionVisible(false)} + /> + setRenewSubscriptionVisible(false)} + subscribedUntil={subscribedUntil} + /> + setNip05Visible(false)} /> + setLightningAddressVisible(false)} + /> + + ); }; export default Subscribe;