refactor(modals): migrate blur effects to Tailwind

- Replace CSS modules with Tailwind classes for blur effects
- Remove moreinfo.module.css and subscribemodal.module.css
- Standardize blur implementation across MoreInfo and SubscribeModal
- Maintain consistent blur behavior with filter and transition classes
- Keep navigation clear while content is blurred
This commit is contained in:
kiwihodl 2025-03-19 08:47:02 -05:00
parent 6d2b782045
commit 14e85ca7ae
No known key found for this signature in database
GPG Key ID: A2878DC38F1E643C
5 changed files with 373 additions and 272 deletions

View File

@ -1,7 +1,7 @@
// datasource db { // datasource db {
// provider = "postgresql" // provider = "postgresql"
// url = env("DATABASE_URL") // url = env("DATABASE_URL")
// } //}
datasource db { datasource db {
provider = "postgresql" provider = "postgresql"

View File

@ -2,21 +2,31 @@ 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(() => { useEffect(() => {
const mainContent = document.querySelector(".main-content"); const mainContent = document.querySelector(".main-content");
if (mainContent) { if (mainContent) {
if (visible) { if (visible) {
mainContent.classList.add(styles.blurredContent); mainContent.classList.add(
"filter",
"blur-md",
"transition-all",
"duration-200",
"ease-in-out"
);
} else { } else {
mainContent.classList.remove(styles.blurredContent); mainContent.classList.remove(
"filter",
"blur-md",
"transition-all",
"duration-200",
"ease-in-out"
);
} }
} }
}, [visible]); }, [visible]);
@ -43,11 +53,11 @@ const MoreInfo = ({ tooltip, modalTitle, modalBody, className = "" }) => {
onHide={onHide} onHide={onHide}
className="max-w-3xl" className="max-w-3xl"
modal modal
dismissableMask // This enables click-outside-to-close dismissableMask
closeOnEscape // This enables closing with Escape key closeOnEscape
breakpoints={{ "960px": "75vw", "641px": "90vw" }} breakpoints={{ "960px": "75vw", "641px": "90vw" }}
pt={{ pt={{
mask: { className: "backdrop-blur-none" }, // Ensures the Dialog's mask doesn't add its own blur mask: { className: "backdrop-blur-none" },
}} }}
> >
{typeof modalBody === "string" ? ( {typeof modalBody === "string" ? (

View File

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

View File

@ -1,258 +1,349 @@
import React, { useState, useRef, useEffect } from 'react'; import React, { useState, useRef, useEffect } from "react";
import { Dialog } from 'primereact/dialog'; import { Dialog } from "primereact/dialog";
import { ProgressSpinner } from 'primereact/progressspinner'; import { ProgressSpinner } from "primereact/progressspinner";
import SubscriptionPaymentButtons from '@/components/bitcoinConnect/SubscriptionPaymentButton'; import SubscriptionPaymentButtons from "@/components/bitcoinConnect/SubscriptionPaymentButton";
import axios from 'axios'; import axios from "axios";
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 { Card } from 'primereact/card'; import { Card } from "primereact/card";
import GenericButton from '@/components/buttons/GenericButton'; import GenericButton from "@/components/buttons/GenericButton";
import { Menu } from "primereact/menu"; import { Menu } from "primereact/menu";
import { Message } from "primereact/message"; import { Message } from "primereact/message";
import CancelSubscription from '@/components/profile/subscription/CancelSubscription'; import CancelSubscription from "@/components/profile/subscription/CancelSubscription";
import CalendlyEmbed from '@/components/profile/subscription/CalendlyEmbed'; import CalendlyEmbed from "@/components/profile/subscription/CalendlyEmbed";
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 NostrIcon from '../../../../public/images/nostr.png'; import NostrIcon from "../../../../public/images/nostr.png";
import Image from 'next/image'; import Image from "next/image";
import RenewSubscription from '@/components/profile/subscription/RenewSubscription'; import RenewSubscription from "@/components/profile/subscription/RenewSubscription";
const SubscribeModal = ({ user }) => { const SubscribeModal = ({ user }) => {
const { data: session, update } = useSession(); const { data: session, update } = useSession();
const { showToast } = useToast(); const { showToast } = useToast();
const router = useRouter(); const router = useRouter();
const menu = useRef(null); const menu = useRef(null);
const [isProcessing, setIsProcessing] = useState(false); const [isProcessing, setIsProcessing] = useState(false);
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [subscribed, setSubscribed] = useState(false); const [subscribed, setSubscribed] = useState(false);
const [subscribedUntil, setSubscribedUntil] = useState(null); const [subscribedUntil, setSubscribedUntil] = useState(null);
const [subscriptionExpiredAt, setSubscriptionExpiredAt] = useState(null); const [subscriptionExpiredAt, setSubscriptionExpiredAt] = useState(null);
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);
const [isModalVisible, setIsModalVisible] = useState(false);
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(
setSubscribedUntil(subscribedUntil); subscribedAt.getTime() + 31 * 24 * 60 * 60 * 1000
if (user.role.subscriptionExpiredAt) { );
const expiredAt = new Date(user.role.subscriptionExpiredAt) setSubscribedUntil(subscribedUntil);
setSubscriptionExpiredAt(expiredAt); 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.');
onHide();
} 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.');
onHide();
} catch (error) {
console.error('Session update error:', error);
showToast('error', 'Session Update Failed', `Error: ${error.message}`);
} finally {
setIsProcessing(false);
}
};
const onHide = () => {
setVisible(false);
setIsProcessing(false);
} }
}, [user]);
const menuItems = [ useEffect(() => {
{ const mainContent = document.querySelector(".main-content");
label: "Schedule 1:1", if (mainContent) {
icon: "pi pi-calendar", if (isModalVisible) {
command: () => { mainContent.classList.add(
setCalendlyVisible(true); "filter",
}, "blur-md",
}, "transition-all",
{ "duration-200",
label: session?.user?.platformLightningAddress ? "Update PlebDevs Lightning Address" : "Claim PlebDevs Lightning Address", "ease-in-out"
icon: "pi pi-bolt", );
command: () => { } else {
setLightningAddressVisible(true); mainContent.classList.remove(
}, "filter",
}, "blur-md",
{ "transition-all",
label: session?.user?.platformNip05?.name ? "Update PlebDevs Nostr NIP-05" : "Claim PlebDevs Nostr NIP-05", "duration-200",
icon: "pi pi-at", "ease-in-out"
command: () => { );
setNip05Visible(true); }
}, }
}, }, [isModalVisible]);
{
label: "Renew Subscription",
icon: "pi pi-sync",
command: () => {
setRenewSubscriptionVisible(true);
},
},
{
label: "Cancel Subscription",
icon: "pi pi-trash",
command: () => {
setCancelSubscriptionVisible(true);
},
},
];
const subscriptionCardTitle = ( const handleSubscriptionSuccess = async (response) => {
<div className="w-full flex flex-row justify-between items-center"> setIsProcessing(true);
<span className="text-xl text-900 font-bold text-white">Plebdevs Subscription</span> try {
{subscribed && ( const apiResponse = await axios.put("/api/users/subscription", {
<i userId: session.user.id,
className="pi pi-ellipsis-h text-2xl cursor-pointer hover:opacity-75" isSubscribed: true,
onClick={(e) => menu.current.toggle(e)} });
></i> if (apiResponse.data) {
)} await update();
<Menu model={menuItems} popup ref={menu} className="w-fit" /> showToast(
</div> "success",
"Subscription Successful",
"Your subscription has been activated."
);
onHide();
} 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);
};
return ( const handleRecurringSubscriptionSuccess = async () => {
<> setIsProcessing(true);
<Card title={subscriptionCardTitle} className="w-full m-2 mx-auto border border-gray-700"> try {
{subscribed && !user?.role?.nwc && ( await update();
<div className="flex flex-col"> showToast(
<Message className="w-fit" severity="success" text="Subscribed!" /> "success",
<p className="mt-3">Thank you for your support 🎉</p> "Recurring Subscription Activated",
<p className="text-sm text-gray-400">Pay-as-you-go subscription will renew on {subscribedUntil.toLocaleDateString()}</p> "Your recurring subscription has been set up successfully."
</div> );
)} onHide();
{subscribed && user?.role?.nwc && ( } catch (error) {
<div className="flex flex-col"> console.error("Session update error:", error);
<Message className="w-fit" severity="success" text="Subscribed!" /> showToast("error", "Session Update Failed", `Error: ${error.message}`);
<p className="mt-3">Thank you for your support 🎉</p> } finally {
<p className="text-sm text-gray-400">Recurring subscription will AUTO renew on {subscribedUntil.toLocaleDateString()}</p> setIsProcessing(false);
</div> }
)} };
{(!subscribed && !subscriptionExpiredAt) && (
<div className="flex flex-col"> const onHide = () => {
<Message className="w-fit" severity="info" text="You currently have no active subscription" /> setIsModalVisible(false);
<GenericButton setIsProcessing(false);
label="Subscribe" };
className="w-auto mt-3 text-[#f8f8ff]"
onClick={() => setVisible(true)} const menuItems = [
/> {
</div> label: "Schedule 1:1",
)} icon: "pi pi-calendar",
{subscriptionExpiredAt && ( command: () => {
<div className="flex flex-col"> setCalendlyVisible(true);
<Message className="w-fit" severity="warn" text={`Your subscription expired on ${subscriptionExpiredAt.toLocaleDateString()}`} /> },
<GenericButton },
label="Subscribe" {
className="w-auto mt-4 text-[#f8f8ff]" label: session?.user?.platformLightningAddress
onClick={() => setVisible(true)} ? "Update PlebDevs Lightning Address"
/> : "Claim PlebDevs Lightning Address",
</div> icon: "pi pi-bolt",
)} command: () => {
</Card> setLightningAddressVisible(true);
<Dialog },
header="Subscribe to PlebDevs" },
visible={visible} {
onHide={onHide} label: session?.user?.platformNip05?.name
className="p-fluid pb-0 w-fit" ? "Update PlebDevs Nostr NIP-05"
> : "Claim PlebDevs Nostr NIP-05",
{isProcessing ? ( icon: "pi pi-at",
<div className="w-full flex flex-col mx-auto justify-center items-center mt-4"> command: () => {
<div className='w-full h-full flex items-center justify-center'><ProgressSpinner /></div> setNip05Visible(true);
<span className="ml-2">Processing subscription...</span> },
</div> },
) : ( {
<Card className="shadow-lg"> label: "Renew Subscription",
<div className="text-center mb-4"> icon: "pi pi-sync",
<h2 className="text-2xl font-bold text-primary">Unlock Premium Benefits</h2> command: () => {
<p className="text-gray-400">Subscribe now and elevate your development journey!</p> setRenewSubscriptionVisible(true);
</div> },
<div className="flex flex-col gap-4 mb-4 w-[60%] mx-auto"> },
<div className="flex items-center"> {
<i className="pi pi-book text-2xl text-primary mr-2 text-blue-400"></i> label: "Cancel Subscription",
<span>Access ALL current and future PlebDevs content</span> icon: "pi pi-trash",
</div> command: () => {
<div className="flex items-center"> setCancelSubscriptionVisible(true);
<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> },
</div> ];
<div className="flex items-center">
<i className="pi pi-bolt text-2xl text-primary mr-2 text-yellow-500"></i> const subscriptionCardTitle = (
<span>Claim your own personal plebdevs.com Lightning Address</span> <div className="w-full flex flex-row justify-between items-center">
</div> <span className="text-xl text-900 font-bold text-white">
<div className="flex items-center"> Plebdevs Subscription
<Image src={NostrIcon} alt="Nostr" width={26} height={26} className='mr-2' /> </span>
<span>Claim your own personal plebdevs.com Nostr NIP-05 identity</span> {subscribed && (
</div> <i
</div> className="pi pi-ellipsis-h text-2xl cursor-pointer hover:opacity-75"
<SubscriptionPaymentButtons onClick={(e) => menu.current.toggle(e)}
onSuccess={handleSubscriptionSuccess} ></i>
onRecurringSubscriptionSuccess={handleRecurringSubscriptionSuccess} )}
onError={handleSubscriptionError} <Menu model={menuItems} popup ref={menu} className="w-fit" />
setIsProcessing={setIsProcessing} </div>
/> );
</Card>
)} return (
</Dialog> <>
<CalendlyEmbed <Card
visible={calendlyVisible} title={subscriptionCardTitle}
onHide={() => setCalendlyVisible(false)} className="w-full m-2 mx-auto border border-gray-700"
userId={session?.user?.id} >
userName={session?.user?.username || user?.kind0?.username} {subscribed && !user?.role?.nwc && (
userEmail={session?.user?.email} <div className="flex flex-col">
<Message className="w-fit" severity="success" text="Subscribed!" />
<p className="mt-3">Thank you for your support 🎉</p>
<p className="text-sm text-gray-400">
Pay-as-you-go subscription will renew on{" "}
{subscribedUntil.toLocaleDateString()}
</p>
</div>
)}
{subscribed && user?.role?.nwc && (
<div className="flex flex-col">
<Message className="w-fit" severity="success" text="Subscribed!" />
<p className="mt-3">Thank you for your support 🎉</p>
<p className="text-sm text-gray-400">
Recurring subscription will AUTO renew on{" "}
{subscribedUntil.toLocaleDateString()}
</p>
</div>
)}
{!subscribed && !subscriptionExpiredAt && (
<div className="flex flex-col">
<Message
className="w-fit"
severity="info"
text="You currently have no active subscription"
/> />
<CancelSubscription <GenericButton
visible={cancelSubscriptionVisible} label="Subscribe"
onHide={() => setCancelSubscriptionVisible(false)} className="w-auto mt-3 text-[#f8f8ff]"
onClick={() => setIsModalVisible(true)}
/> />
<RenewSubscription </div>
visible={renewSubscriptionVisible} )}
onHide={() => setRenewSubscriptionVisible(false)} {subscriptionExpiredAt && (
subscribedUntil={subscribedUntil} <div className="flex flex-col">
<Message
className="w-fit"
severity="warn"
text={`Your subscription expired on ${subscriptionExpiredAt.toLocaleDateString()}`}
/> />
<Nip05Form <GenericButton
visible={nip05Visible} label="Subscribe"
onHide={() => setNip05Visible(false)} className="w-auto mt-4 text-[#f8f8ff]"
onClick={() => setIsModalVisible(true)}
/> />
<LightningAddressForm </div>
visible={lightningAddressVisible} )}
onHide={() => setLightningAddressVisible(false)} </Card>
<Dialog
header="Subscribe to PlebDevs"
visible={isModalVisible}
onHide={onHide}
className="p-fluid pb-0 w-fit"
modal
dismissableMask
closeOnEscape
pt={{
mask: { className: "backdrop-blur-none" },
}}
>
{isProcessing ? (
<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>
<span className="ml-2">Processing subscription...</span>
</div>
) : (
<Card className="shadow-lg">
<div className="text-center mb-4">
<h2 className="text-2xl font-bold text-primary">
Unlock Premium Benefits
</h2>
<p className="text-gray-400">
Subscribe now and elevate your development journey!
</p>
</div>
<div className="flex flex-col gap-4 mb-4 w-[60%] mx-auto">
<div className="flex items-center">
<i className="pi pi-book text-2xl text-primary mr-2 text-blue-400"></i>
<span>Access ALL current and future PlebDevs content</span>
</div>
<div className="flex items-center">
<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>
</div>
<div className="flex items-center">
<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>
</div>
<div className="flex items-center">
<Image
src={NostrIcon}
alt="Nostr"
width={26}
height={26}
className="mr-2"
/>
<span>
Claim your own personal plebdevs.com Nostr NIP-05 identity
</span>
</div>
</div>
<SubscriptionPaymentButtons
onSuccess={handleSubscriptionSuccess}
onRecurringSubscriptionSuccess={
handleRecurringSubscriptionSuccess
}
onError={handleSubscriptionError}
setIsProcessing={setIsProcessing}
/> />
</> </Card>
); )}
</Dialog>
<CalendlyEmbed
visible={calendlyVisible}
onHide={() => setCalendlyVisible(false)}
userId={session?.user?.id}
userName={session?.user?.username || user?.kind0?.username}
userEmail={session?.user?.email}
/>
<CancelSubscription
visible={cancelSubscriptionVisible}
onHide={() => setCancelSubscriptionVisible(false)}
/>
<RenewSubscription
visible={renewSubscriptionVisible}
onHide={() => setRenewSubscriptionVisible(false)}
subscribedUntil={subscribedUntil}
/>
<Nip05Form visible={nip05Visible} onHide={() => setNip05Visible(false)} />
<LightningAddressForm
visible={lightningAddressVisible}
onHide={() => setLightningAddressVisible(false)}
/>
</>
);
}; };
export default SubscribeModal; export default SubscribeModal;

View File

@ -18,7 +18,7 @@ import RenewSubscription from "@/components/profile/subscription/RenewSubscripti
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'; import MoreInfo from "@/components/MoreInfo";
const Subscribe = () => { const Subscribe = () => {
const { data: session, update } = useSession(); const { data: session, update } = useSession();
@ -212,22 +212,26 @@ const Subscribe = () => {
/> />
{/* Test MoreInfo modal blur here */} {/* Test MoreInfo modal blur here */}
{/* <div className="flex items-center gap-2"> {/* <div className="flex items-center gap-2">
<Message className="w-fit" severity="info" text="Login to manage your subscription" /> <Message
<MoreInfo className="w-fit"
tooltip="About Subscriptions" severity="info"
modalTitle="Subscription Information" text="Login to manage your subscription"
modalBody={ />
<div className="space-y-3"> <MoreInfo
<p>As a PlebDevs subscriber, you get access to:</p> tooltip="About Subscriptions"
<ul className="list-disc pl-4"> modalTitle="Subscription Information"
<li>Full access to all courses and content</li> modalBody={
<li>Exclusive developer resources</li> <div className="space-y-3">
<li>Priority support</li> <p>As a PlebDevs subscriber, you get access to:</p>
<li>Community features</li> <ul className="list-disc pl-4">
</ul> <li>Full access to all courses and content</li>
</div> <li>Exclusive developer resources</li>
} <li>Priority support</li>
/> <li>Community features</li>
</ul>
</div>
}
/>
</div> */} </div> */}
</div> </div>
)} )}