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
This commit is contained in:
kiwihodl 2025-03-18 14:45:44 -05:00
parent 6f2dea70e9
commit 6d2b782045
No known key found for this signature in database
GPG Key ID: A2878DC38F1E643C
3 changed files with 548 additions and 330 deletions

View File

@ -1,13 +1,30 @@
import React, { useState } from 'react'; import React, { useState, useEffect } from "react";
import { Dialog } from 'primereact/dialog'; import { Dialog } from "primereact/dialog";
import { Tooltip } from 'primereact/tooltip'; import { Tooltip } from "primereact/tooltip";
import useWindowWidth from '@/hooks/useWindowWidth'; import useWindowWidth from "@/hooks/useWindowWidth";
import styles from "./moreinfo.module.css";
const MoreInfo = ({ tooltip, modalTitle, modalBody, className = '' }) => { const MoreInfo = ({ tooltip, modalTitle, modalBody, className = "" }) => {
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const windowWidth = useWindowWidth(); const windowWidth = useWindowWidth();
const isMobile = windowWidth < 768; const isMobile = windowWidth < 768;
// 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 ( return (
<> <>
<i <i
@ -23,11 +40,17 @@ const MoreInfo = ({ tooltip, modalTitle, modalBody, className = '' }) => {
<Dialog <Dialog
header={modalTitle} header={modalTitle}
visible={visible} visible={visible}
onHide={() => setVisible(false)} onHide={onHide}
className="max-w-3xl" className="max-w-3xl"
breakpoints={{ '960px': '75vw', '641px': '90vw' }} modal
dismissableMask // This enables click-outside-to-close
closeOnEscape // This enables closing with Escape key
breakpoints={{ "960px": "75vw", "641px": "90vw" }}
pt={{
mask: { className: "backdrop-blur-none" }, // Ensures the Dialog's mask doesn't add its own blur
}}
> >
{typeof modalBody === 'string' ? ( {typeof modalBody === "string" ? (
<p className="text-gray-200">{modalBody}</p> <p className="text-gray-200">{modalBody}</p>
) : ( ) : (
modalBody modalBody

View File

@ -0,0 +1,4 @@
.blurredContent {
filter: blur(8px);
transition: filter 0.2s ease-in-out;
}

View File

@ -1,22 +1,24 @@
import React, { useState, useRef, useEffect } from 'react'; import React, { useState, useRef, useEffect } from "react";
import { useSession } from 'next-auth/react'; import { useSession } from "next-auth/react";
import { useRouter } from 'next/router'; import { useRouter } from "next/router";
import { useToast } from '@/hooks/useToast'; import { useToast } from "@/hooks/useToast";
import axios from 'axios'; import axios from "axios";
import { Card } from 'primereact/card'; import { Card } from "primereact/card";
import { Message } from 'primereact/message'; import { Message } from "primereact/message";
import useWindowWidth from '@/hooks/useWindowWidth'; import useWindowWidth from "@/hooks/useWindowWidth";
import { Menu } from "primereact/menu"; import { Menu } from "primereact/menu";
import GenericButton from '@/components/buttons/GenericButton'; import GenericButton from "@/components/buttons/GenericButton";
import { ProgressSpinner } from 'primereact/progressspinner'; import { ProgressSpinner } from "primereact/progressspinner";
import SubscriptionPaymentButtons from '@/components/bitcoinConnect/SubscriptionPaymentButton'; import SubscriptionPaymentButtons from "@/components/bitcoinConnect/SubscriptionPaymentButton";
import Image from 'next/image'; import Image from "next/image";
import NostrIcon from '../../public/images/nostr.png'; import NostrIcon from "../../public/images/nostr.png";
import CalendlyEmbed from '@/components/profile/subscription/CalendlyEmbed'; import CalendlyEmbed from "@/components/profile/subscription/CalendlyEmbed";
import CancelSubscription from '@/components/profile/subscription/CancelSubscription'; import CancelSubscription from "@/components/profile/subscription/CancelSubscription";
import RenewSubscription from '@/components/profile/subscription/RenewSubscription'; import RenewSubscription from "@/components/profile/subscription/RenewSubscription";
import Nip05Form from '@/components/profile/subscription/Nip05Form'; import Nip05Form from "@/components/profile/subscription/Nip05Form";
import LightningAddressForm from '@/components/profile/subscription/LightningAddressForm'; import LightningAddressForm from "@/components/profile/subscription/LightningAddressForm";
import MoreInfo from '@/components/MoreInfo';
const Subscribe = () => { const Subscribe = () => {
const { data: session, update } = useSession(); const { data: session, update } = useSession();
@ -32,23 +34,27 @@ const Subscribe = () => {
const [calendlyVisible, setCalendlyVisible] = useState(false); const [calendlyVisible, setCalendlyVisible] = useState(false);
const [lightningAddressVisible, setLightningAddressVisible] = useState(false); const [lightningAddressVisible, setLightningAddressVisible] = useState(false);
const [nip05Visible, setNip05Visible] = useState(false); const [nip05Visible, setNip05Visible] = useState(false);
const [cancelSubscriptionVisible, setCancelSubscriptionVisible] = useState(false); const [cancelSubscriptionVisible, setCancelSubscriptionVisible] =
const [renewSubscriptionVisible, setRenewSubscriptionVisible] = useState(false); useState(false);
const [renewSubscriptionVisible, setRenewSubscriptionVisible] =
useState(false);
useEffect(() => { useEffect(() => {
if (session && session?.user) { if (session && session?.user) {
setUser(session.user); setUser(session.user);
} }
}, [session]) }, [session]);
useEffect(() => { useEffect(() => {
if (user && user.role) { if (user && user.role) {
setSubscribed(user.role.subscribed); setSubscribed(user.role.subscribed);
const subscribedAt = new Date(user.role.lastPaymentAt); const subscribedAt = new Date(user.role.lastPaymentAt);
const subscribedUntil = new Date(subscribedAt.getTime() + 31 * 24 * 60 * 60 * 1000); const subscribedUntil = new Date(
subscribedAt.getTime() + 31 * 24 * 60 * 60 * 1000
);
setSubscribedUntil(subscribedUntil); setSubscribedUntil(subscribedUntil);
if (user.role.subscriptionExpiredAt) { if (user.role.subscriptionExpiredAt) {
const expiredAt = new Date(user.role.subscriptionExpiredAt) const expiredAt = new Date(user.role.subscriptionExpiredAt);
setSubscriptionExpiredAt(expiredAt); setSubscriptionExpiredAt(expiredAt);
} }
} }
@ -57,27 +63,39 @@ const Subscribe = () => {
const handleSubscriptionSuccess = async (response) => { const handleSubscriptionSuccess = async (response) => {
setIsProcessing(true); setIsProcessing(true);
try { try {
const apiResponse = await axios.put('/api/users/subscription', { const apiResponse = await axios.put("/api/users/subscription", {
userId: session.user.id, userId: session.user.id,
isSubscribed: true, isSubscribed: true,
}); });
if (apiResponse.data) { if (apiResponse.data) {
await update(); await update();
showToast('success', 'Subscription Successful', 'Your subscription has been activated.'); showToast(
"success",
"Subscription Successful",
"Your subscription has been activated."
);
} else { } else {
throw new Error('Failed to update subscription status'); throw new Error("Failed to update subscription status");
} }
} catch (error) { } catch (error) {
console.error('Subscription update error:', error); console.error("Subscription update error:", error);
showToast('error', 'Subscription Update Failed', `Error: ${error.message}`); showToast(
"error",
"Subscription Update Failed",
`Error: ${error.message}`
);
} finally { } finally {
setIsProcessing(false); setIsProcessing(false);
} }
}; };
const handleSubscriptionError = (error) => { const handleSubscriptionError = (error) => {
console.error('Subscription error:', error); console.error("Subscription error:", error);
showToast('error', 'Subscription Failed', `An error occurred: ${error.message}`); showToast(
"error",
"Subscription Failed",
`An error occurred: ${error.message}`
);
setIsProcessing(false); setIsProcessing(false);
}; };
@ -85,10 +103,14 @@ const Subscribe = () => {
setIsProcessing(true); setIsProcessing(true);
try { try {
await update(); await update();
showToast('success', 'Recurring Subscription Activated', 'Your recurring subscription has been set up successfully.'); showToast(
"success",
"Recurring Subscription Activated",
"Your recurring subscription has been set up successfully."
);
} catch (error) { } catch (error) {
console.error('Session update error:', error); console.error("Session update error:", error);
showToast('error', 'Session Update Failed', `Error: ${error.message}`); showToast("error", "Session Update Failed", `Error: ${error.message}`);
} finally { } finally {
setIsProcessing(false); setIsProcessing(false);
} }
@ -101,12 +123,16 @@ const Subscribe = () => {
command: () => setCalendlyVisible(true), command: () => setCalendlyVisible(true),
}, },
{ {
label: session?.user?.platformLightningAddress ? "Update PlebDevs Lightning Address" : "Claim PlebDevs Lightning Address", label: session?.user?.platformLightningAddress
? "Update PlebDevs Lightning Address"
: "Claim PlebDevs Lightning Address",
icon: "pi pi-bolt", icon: "pi pi-bolt",
command: () => setLightningAddressVisible(true), command: () => setLightningAddressVisible(true),
}, },
{ {
label: session?.user?.platformNip05?.name ? "Update PlebDevs Nostr NIP-05" : "Claim PlebDevs Nostr NIP-05", label: session?.user?.platformNip05?.name
? "Update PlebDevs Nostr NIP-05"
: "Claim PlebDevs Nostr NIP-05",
icon: "pi pi-at", icon: "pi pi-at",
command: () => setNip05Visible(true), command: () => setNip05Visible(true),
}, },
@ -132,32 +158,77 @@ const Subscribe = () => {
<> <>
{subscribed && !user?.role?.nwc && ( {subscribed && !user?.role?.nwc && (
<div className="flex flex-col"> <div className="flex flex-col">
<Message className="w-fit" severity="success" text="Subscribed!" /> <Message
className="w-fit"
severity="success"
text="Subscribed!"
/>
<p className="mt-4">Thank you for your support 🎉</p> <p className="mt-4">Thank you for your support 🎉</p>
<p className="text-sm text-gray-400">Pay-as-you-go subscription must be manually renewed on {subscribedUntil.toLocaleDateString()}</p> <p className="text-sm text-gray-400">
Pay-as-you-go subscription must be manually renewed on{" "}
{subscribedUntil.toLocaleDateString()}
</p>
</div> </div>
)} )}
{subscribed && user?.role?.nwc && ( {subscribed && user?.role?.nwc && (
<div className="flex flex-col"> <div className="flex flex-col">
<Message className="w-fit" severity="success" text="Subscribed!" /> <Message
className="w-fit"
severity="success"
text="Subscribed!"
/>
<p className="mt-4">Thank you for your support 🎉</p> <p className="mt-4">Thank you for your support 🎉</p>
<p className="text-sm text-gray-400">Recurring subscription will AUTO renew on {subscribedUntil.toLocaleDateString()}</p> <p className="text-sm text-gray-400">
Recurring subscription will AUTO renew on{" "}
{subscribedUntil.toLocaleDateString()}
</p>
</div> </div>
)} )}
{(!subscribed && !subscriptionExpiredAt) && ( {!subscribed && !subscriptionExpiredAt && (
<div className="flex flex-col"> <div className="flex flex-col">
<Message className="w-fit" severity="info" text="You currently have no active subscription" /> <Message
className="w-fit"
severity="info"
text="You currently have no active subscription"
/>
</div> </div>
)} )}
{subscriptionExpiredAt && ( {subscriptionExpiredAt && (
<div className="flex flex-col"> <div className="flex flex-col">
<Message className="w-fit" severity="warn" text={`Your subscription expired on ${subscriptionExpiredAt.toLocaleDateString()}`} /> <Message
className="w-fit"
severity="warn"
text={`Your subscription expired on ${subscriptionExpiredAt.toLocaleDateString()}`}
/>
</div> </div>
)} )}
</> </>
) : ( ) : (
<div className="flex flex-col"> <div className="flex flex-col">
<Message
className="w-fit"
severity="info"
text="Login to manage your subscription"
/>
{/* Test MoreInfo modal blur here */}
{/* <div className="flex items-center gap-2">
<Message className="w-fit" severity="info" text="Login to manage your subscription" /> <Message className="w-fit" severity="info" text="Login to manage your subscription" />
<MoreInfo
tooltip="About Subscriptions"
modalTitle="Subscription Information"
modalBody={
<div className="space-y-3">
<p>As a PlebDevs subscriber, you get access to:</p>
<ul className="list-disc pl-4">
<li>Full access to all courses and content</li>
<li>Exclusive developer resources</li>
<li>Priority support</li>
<li>Community features</li>
</ul>
</div>
}
/>
</div> */}
</div> </div>
)} )}
</div> </div>
@ -165,16 +236,26 @@ const Subscribe = () => {
{!session?.user && ( {!session?.user && (
<> <>
<Card title="Start Your PlebDevs Journey" className="mb-2"> <Card title="Start Your PlebDevs Journey" className="mb-2">
<p className='mb-4 text-xl'> <p className="mb-4 text-xl">
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. 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.
</p> </p>
<p className='text-xl mb-4'> <p className="text-xl mb-4">
Subscribe monthly with a pay-as-you-go option or set up an auto-recurring subscription using Nostr Wallet Connect. Subscribe monthly with a pay-as-you-go option or set up an
auto-recurring subscription using Nostr Wallet Connect.
</p> </p>
</Card> </Card>
<Card title="Ready to level up?" className="mb-2"> <Card title="Ready to level up?" className="mb-2">
<p className='text-xl pb-4'>Login to start your subscription!</p> <p className="text-xl pb-4">Login to start your subscription!</p>
<GenericButton label="Login" onClick={() => router.push('/auth/signin')} className='text-[#f8f8ff] w-fit' rounded icon="pi pi-user" /> <GenericButton
label="Login"
onClick={() => router.push("/auth/signin")}
className="text-[#f8f8ff] w-fit"
rounded
icon="pi pi-user"
/>
</Card> </Card>
</> </>
)} )}
@ -182,14 +263,20 @@ const Subscribe = () => {
<Card title="Subscribe to PlebDevs" className="mb-2"> <Card title="Subscribe to PlebDevs" className="mb-2">
{isProcessing ? ( {isProcessing ? (
<div className="w-full flex flex-col mx-auto justify-center items-center mt-4"> <div className="w-full flex flex-col mx-auto justify-center items-center mt-4">
<div className='w-full h-full flex items-center justify-center'><ProgressSpinner /></div> <div className="w-full h-full flex items-center justify-center">
<ProgressSpinner />
</div>
<span className="ml-2">Processing subscription...</span> <span className="ml-2">Processing subscription...</span>
</div> </div>
) : ( ) : (
<div className="flex flex-col"> <div className="flex flex-col">
<div className="mb-4"> <div className="mb-4">
<h2 className="text-2xl font-bold text-primary">Unlock Premium Benefits</h2> <h2 className="text-2xl font-bold text-primary">
<p className="text-gray-400">Subscribe now and elevate your development journey!</p> Unlock Premium Benefits
</h2>
<p className="text-gray-400">
Subscribe now and elevate your development journey!
</p>
</div> </div>
<div className="flex flex-col gap-4 mb-4"> <div className="flex flex-col gap-4 mb-4">
<div className="flex items-center"> <div className="flex items-center">
@ -198,20 +285,35 @@ const Subscribe = () => {
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<i className="pi pi-calendar text-2xl text-primary mr-2 text-red-400"></i> <i className="pi pi-calendar text-2xl text-primary mr-2 text-red-400"></i>
<span>Personal mentorship & guidance and access to exclusive 1:1 booking calendar</span> <span>
Personal mentorship & guidance and access to exclusive 1:1
booking calendar
</span>
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<i className="pi pi-bolt text-2xl text-primary mr-2 text-yellow-500"></i> <i className="pi pi-bolt text-2xl text-primary mr-2 text-yellow-500"></i>
<span>Claim your own personal plebdevs.com Lightning Address</span> <span>
Claim your own personal plebdevs.com Lightning Address
</span>
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<Image src={NostrIcon} alt="Nostr" width={25} height={25} className='mr-2' /> <Image
<span>Claim your own personal plebdevs.com Nostr NIP-05 identity</span> src={NostrIcon}
alt="Nostr"
width={25}
height={25}
className="mr-2"
/>
<span>
Claim your own personal plebdevs.com Nostr NIP-05 identity
</span>
</div> </div>
</div> </div>
<SubscriptionPaymentButtons <SubscriptionPaymentButtons
onSuccess={handleSubscriptionSuccess} onSuccess={handleSubscriptionSuccess}
onRecurringSubscriptionSuccess={handleRecurringSubscriptionSuccess} onRecurringSubscriptionSuccess={
handleRecurringSubscriptionSuccess
}
onError={handleSubscriptionError} onError={handleSubscriptionError}
setIsProcessing={setIsProcessing} setIsProcessing={setIsProcessing}
layout={windowWidth < 768 ? "col" : "row"} layout={windowWidth < 768 ? "col" : "row"}
@ -224,15 +326,62 @@ const Subscribe = () => {
<> <>
<Card title="Subscription Benefits" className="mb-2"> <Card title="Subscription Benefits" className="mb-2">
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<GenericButton severity="info" outlined className="w-fit text-start" label="Schedule 1:1" icon="pi pi-calendar" onClick={() => setCalendlyVisible(true)} /> <GenericButton
<GenericButton severity="help" outlined className="w-fit text-start" label={session?.user?.platformNip05?.name ? "Update Nostr NIP-05" : "Claim PlebDevs Nostr NIP-05"} icon="pi pi-at" onClick={() => setNip05Visible(true)} /> severity="info"
<GenericButton severity="warning" outlined className="w-fit text-start" label={session?.user?.platformLightningAddress ? "Update Lightning Address" : "Claim PlebDevs Lightning Address"} icon={<i style={{ color: "orange" }} className="pi pi-bolt mr-2"></i>} onClick={() => setLightningAddressVisible(true)} /> outlined
className="w-fit text-start"
label="Schedule 1:1"
icon="pi pi-calendar"
onClick={() => setCalendlyVisible(true)}
/>
<GenericButton
severity="help"
outlined
className="w-fit text-start"
label={
session?.user?.platformNip05?.name
? "Update Nostr NIP-05"
: "Claim PlebDevs Nostr NIP-05"
}
icon="pi pi-at"
onClick={() => setNip05Visible(true)}
/>
<GenericButton
severity="warning"
outlined
className="w-fit text-start"
label={
session?.user?.platformLightningAddress
? "Update Lightning Address"
: "Claim PlebDevs Lightning Address"
}
icon={
<i
style={{ color: "orange" }}
className="pi pi-bolt mr-2"
></i>
}
onClick={() => setLightningAddressVisible(true)}
/>
</div> </div>
</Card> </Card>
<Card title="Manage Subscription" className="mb-2"> <Card title="Manage Subscription" className="mb-2">
<div className='flex flex-col gap-4'> <div className="flex flex-col gap-4">
<GenericButton outlined className="w-fit" label="Renew Subscription" icon="pi pi-sync" onClick={() => setRenewSubscriptionVisible(true)} /> <GenericButton
<GenericButton severity="danger" outlined className="w-fit" label="Cancel Subscription" icon="pi pi-trash" onClick={() => setCancelSubscriptionVisible(true)} /> outlined
className="w-fit"
label="Renew Subscription"
icon="pi pi-sync"
onClick={() => setRenewSubscriptionVisible(true)}
/>
<GenericButton
severity="danger"
outlined
className="w-fit"
label="Cancel Subscription"
icon="pi pi-trash"
onClick={() => setCancelSubscriptionVisible(true)}
/>
</div> </div>
</Card> </Card>
</> </>
@ -241,36 +390,81 @@ const Subscribe = () => {
<Card title="Frequently Asked Questions" className="mb-2"> <Card title="Frequently Asked Questions" className="mb-2">
<div className="flex flex-col gap-4 max-w-[80%] max-mob:max-w-full"> <div className="flex flex-col gap-4 max-w-[80%] max-mob:max-w-full">
<div> <div>
<h3 className="text-lg font-semibold">How does the subscription work?</h3> <h3 className="text-lg font-semibold">
<p>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.</p> How does the subscription work?
</h3>
<p>
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.
</p>
</div> </div>
<div className='flex flex-col gap-2'> <div className="flex flex-col gap-2">
<h3 className="text-lg font-semibold">What are the benefits of a subscription?</h3> <h3 className="text-lg font-semibold">
<p>The subscription gives you access to all of the premium features and all of the paid content. You can cancel at any time.</p> What are the benefits of a subscription?
</h3>
<p>
The subscription gives you access to all of the premium features
and all of the paid content. You can cancel at any time.
</p>
</div> </div>
<div className='flex flex-col gap-2'> <div className="flex flex-col gap-2">
<h3 className="text-lg font-semibold">How much does the subscription cost?</h3> <h3 className="text-lg font-semibold">
How much does the subscription cost?
</h3>
<p>The subscription is 50,000 sats per month.</p> <p>The subscription is 50,000 sats per month.</p>
</div> </div>
<div> <div>
<h3 className="text-lg font-semibold">How do I Subscribe? (Pay as you go)</h3> <h3 className="text-lg font-semibold">
<p>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.</p> How do I Subscribe? (Pay as you go)
</h3>
<p>
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.
</p>
</div> </div>
<div> <div>
<h3 className="text-lg font-semibold">How do I Subscribe? (Recurring)</h3> <h3 className="text-lg font-semibold">
<p>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.</p> How do I Subscribe? (Recurring)
</h3>
<p>
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.
</p>
</div> </div>
<div> <div>
<h3 className="text-lg font-semibold">Can I cancel my subscription?</h3> <h3 className="text-lg font-semibold">
<p>Yes, you can cancel your subscription at any time. Your access will remain active until the end of the current billing period.</p> Can I cancel my subscription?
</h3>
<p>
Yes, you can cancel your subscription at any time. Your access
will remain active until the end of the current billing period.
</p>
</div> </div>
<div> <div>
<h3 className="text-lg font-semibold">What happens if I don&apos;t renew my subscription?</h3> <h3 className="text-lg font-semibold">
<p>If you don&apos;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.</p> What happens if I don&apos;t renew my subscription?
</h3>
<p>
If you don&apos;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.
</p>
</div> </div>
<div> <div>
<h3 className="text-lg font-semibold">What is Nostr Wallet Connect?</h3> <h3 className="text-lg font-semibold">
<p>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.</p> What is Nostr Wallet Connect?
</h3>
<p>
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.
</p>
</div> </div>
</div> </div>
</Card> </Card>
@ -291,10 +485,7 @@ const Subscribe = () => {
onHide={() => setRenewSubscriptionVisible(false)} onHide={() => setRenewSubscriptionVisible(false)}
subscribedUntil={subscribedUntil} subscribedUntil={subscribedUntil}
/> />
<Nip05Form <Nip05Form visible={nip05Visible} onHide={() => setNip05Visible(false)} />
visible={nip05Visible}
onHide={() => setNip05Visible(false)}
/>
<LightningAddressForm <LightningAddressForm
visible={lightningAddressVisible} visible={lightningAddressVisible}
onHide={() => setLightningAddressVisible(false)} onHide={() => setLightningAddressVisible(false)}