mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-04-19 10:51:20 +00:00
Combine about and subscribe pages, add fourth button to bottom bar on mobile, fix styles, test
This commit is contained in:
parent
ccaca7b23e
commit
c8e5e13840
@ -20,6 +20,9 @@ const BottomBar = () => {
|
||||
<div onClick={() => router.push('/feed?channel=global')} className={`hover:bg-gray-700 cursor-pointer px-4 py-2 rounded-lg ${isActive('/feed') ? 'bg-gray-700' : ''}`}>
|
||||
<i className="pi pi-comments text-2xl" />
|
||||
</div>
|
||||
<div onClick={() => router.push('/about')} className={`hover:bg-gray-700 cursor-pointer px-4 py-2 rounded-lg ${isActive('/about') ? 'bg-gray-700' : ''}`}>
|
||||
<i className="pi pi-info-circle text-2xl" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -27,7 +27,7 @@ const SubscriptionPaymentButtons = ({ onSuccess, onError, onRecurringSubscriptio
|
||||
const router = useRouter();
|
||||
|
||||
const lnAddress = process.env.NEXT_PUBLIC_LIGHTNING_ADDRESS;
|
||||
const amount = 50000;
|
||||
const amount = 50;
|
||||
|
||||
useEffect(() => {
|
||||
initializeBitcoinConnect();
|
||||
@ -92,7 +92,7 @@ const SubscriptionPaymentButtons = ({ onSuccess, onError, onRecurringSubscriptio
|
||||
const initNwcOptions = {
|
||||
name: "plebdevs.com",
|
||||
requestMethods: ['pay_invoice'],
|
||||
maxAmount: 50000,
|
||||
maxAmount: 500,
|
||||
editable: false,
|
||||
budgetRenewal: 'monthly',
|
||||
expiresAt: yearFromNow,
|
||||
|
@ -105,7 +105,7 @@ const InteractivePromotionalCarousel = () => {
|
||||
}, [selectedPromotion]);
|
||||
|
||||
return (
|
||||
<div className={`flex ${isTabView ? 'flex-col' : 'flex-row'} bg-gray-900 text-white m-4 mx-0 rounded-lg ${isTabView ? 'h-auto' : 'h-[620px]'} ${isTabView ? 'w-full mx-0 ml-0 mt-0' : null}`}>
|
||||
<div className={`flex ${isTabView ? 'flex-col' : 'flex-row'} bg-gray-900 text-white m-4 mb-2 mx-0 rounded-lg ${isTabView ? 'h-auto' : 'h-[620px]'} ${isTabView ? 'w-full mx-0 ml-0 mt-0' : null}`}>
|
||||
<div className={isTabView ? 'w-full' : 'lg:w-2/3 relative'}>
|
||||
{selectedPromotion.video ? (
|
||||
<video
|
||||
@ -142,7 +142,7 @@ const InteractivePromotionalCarousel = () => {
|
||||
case "PLEBDEVS":
|
||||
return (
|
||||
<div className="flex flex-row gap-2">
|
||||
<GenericButton onClick={() => router.push('/subscribe')} severity="warning" icon={<i className="pi pi-star pr-2 pb-1" />} label="Subscribe" className="w-fit py-2 font-semibold" size="small" outlined />
|
||||
<GenericButton onClick={() => router.push('/about')} severity="warning" icon={<i className="pi pi-star pr-2 pb-1" />} label="Subscribe" className="w-fit py-2 font-semibold" size="small" outlined />
|
||||
<GenericButton onClick={() => router.push('/content?tag=all')} severity="primary" icon={<i className="pi pi-eye pr-2" />} label="All content" className="w-fit py-2 font-semibold" size="small" outlined />
|
||||
</div>
|
||||
);
|
||||
@ -160,7 +160,7 @@ const InteractivePromotionalCarousel = () => {
|
||||
);
|
||||
case "LIGHTNING / NOSTR":
|
||||
return (
|
||||
<GenericButton onClick={() => router.push('/subscribe')} severity="warning" icon={<i className="pi pi-star pr-2 pb-1" />} label="Subscribe" className="w-fit py-2 font-semibold" size="small" outlined />
|
||||
<GenericButton onClick={() => router.push('/about')} severity="warning" icon={<i className="pi pi-star pr-2 pb-1" />} label="Subscribe" className="w-fit py-2 font-semibold" size="small" outlined />
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
@ -185,7 +185,7 @@ const InteractivePromotionalCarousel = () => {
|
||||
case "PLEBDEVS":
|
||||
return (
|
||||
<div className="flex flex-row gap-4 mt-4">
|
||||
<GenericButton onClick={() => router.push('/subscribe')} severity="warning" icon={<i className="pi pi-star pr-2 pb-1" />} label="Subscribe" className="py-2 font-semibold" size="small" outlined />
|
||||
<GenericButton onClick={() => router.push('/about')} severity="warning" icon={<i className="pi pi-star pr-2 pb-1" />} label="Subscribe" className="py-2 font-semibold" size="small" outlined />
|
||||
<GenericButton onClick={() => router.push('/content?tag=all')} severity="primary" icon={<i className="pi pi-eye pr-2" />} label="All content" className="py-2 font-semibold" size="small" outlined />
|
||||
<GenericButton onClick={() => copyToClipboard()} icon={<i className="pi pi-bolt pr-2" />} label="Donate" className={`py-2 font-semibold text-yellow-300 ${yellowFocusOutlineStyle}`} size="small" outlined />
|
||||
</div>
|
||||
@ -204,7 +204,7 @@ const InteractivePromotionalCarousel = () => {
|
||||
);
|
||||
case "LIGHTNING / NOSTR":
|
||||
return (
|
||||
<GenericButton onClick={() => router.push('/subscribe')} severity="warning" icon={<i className="pi pi-star pr-2 pb-1" />} label="Subscribe" className="py-2 font-semibold" size="small" outlined />
|
||||
<GenericButton onClick={() => router.push('/about')} severity="warning" icon={<i className="pi pi-star pr-2 pb-1" />} label="Subscribe" className="py-2 font-semibold" size="small" outlined />
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
|
@ -54,7 +54,7 @@ const Navbar = () => {
|
||||
{
|
||||
label: 'Subscribe',
|
||||
icon: 'pi pi-star',
|
||||
command: () => router.push('/subscribe')
|
||||
command: () => router.push('/about')
|
||||
},
|
||||
{
|
||||
label: 'About',
|
||||
|
@ -190,7 +190,7 @@ const UserSubscription = () => {
|
||||
</>
|
||||
)}
|
||||
|
||||
<Card title="Frequently Asked Questions" className="mt-2 border border-gray-700 rounded-lg">
|
||||
<Card title="Frequently Asked Questions" className="border border-gray-700 rounded-lg">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">How does the subscription work?</h3>
|
||||
|
@ -1,20 +1,170 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import Image from 'next/image';
|
||||
import NostrIcon from '../../public/images/nostr.png';
|
||||
import { Card } from 'primereact/card';
|
||||
import { Message } from 'primereact/message';
|
||||
import { useToast } from "@/hooks/useToast";
|
||||
import useWindowWidth from "@/hooks/useWindowWidth";
|
||||
import GenericButton from '@/components/buttons/GenericButton';
|
||||
import InteractivePromotionalCarousel from '@/components/content/carousels/InteractivePromotionalCarousel';
|
||||
import axios from 'axios';
|
||||
import { Menu } from "primereact/menu";
|
||||
import { ProgressSpinner } from 'primereact/progressspinner';
|
||||
import SubscriptionPaymentButtons from '@/components/bitcoinConnect/SubscriptionPaymentButton';
|
||||
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 AboutPage = () => {
|
||||
const { data: session, update } = useSession();
|
||||
const { showToast } = useToast();
|
||||
const router = useRouter();
|
||||
const windowWidth = useWindowWidth();
|
||||
const menu = useRef(null);
|
||||
|
||||
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 isTabView = windowWidth <= 1160;
|
||||
const isMobile = windowWidth < 668;
|
||||
|
||||
// FAQ content for the modal
|
||||
const faqContent = (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">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 className='flex flex-col gap-2'>
|
||||
<h3 className="text-lg font-semibold">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 className='flex flex-col gap-2'>
|
||||
<h3 className="text-lg font-semibold">How much does the subscription cost?</h3>
|
||||
<p>The subscription is 50,000 sats per month.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">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>
|
||||
<h3 className="text-lg font-semibold">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>
|
||||
<h3 className="text-lg font-semibold">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>
|
||||
<h3 className="text-lg font-semibold">What happens if I don't renew my subscription?</h3>
|
||||
<p>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.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">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>
|
||||
);
|
||||
|
||||
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]);
|
||||
|
||||
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),
|
||||
},
|
||||
];
|
||||
|
||||
const copyToClipboard = async (text) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
@ -34,7 +184,168 @@ const AboutPage = () => {
|
||||
return (
|
||||
<div className={`${isTabView ? 'w-full' : 'w-full px-12'} ${isMobile ? 'p-0' : 'p-4'} mx-auto`}>
|
||||
<InteractivePromotionalCarousel />
|
||||
<Card title="Key Features" className={`mb-4 ${isMobile ? 'm-2' : null}`}>
|
||||
|
||||
{/* For non-logged in users */}
|
||||
{!session?.user && (
|
||||
<>
|
||||
<Card title="Start Your PlebDevs Journey" className="mb-2">
|
||||
<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.
|
||||
</p>
|
||||
<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.
|
||||
</p>
|
||||
</Card>
|
||||
<Card title="Ready to level up?" className="mb-2">
|
||||
<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" />
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Subscription Card */}
|
||||
<Card
|
||||
className={`mb-2 relative ${isMobile ? 'm-2' : null}`}
|
||||
header={
|
||||
<div className="flex justify-between items-center p-4 pb-0">
|
||||
<h2 className="text-xl font-bold m-0">Subscribe to PlebDevs</h2>
|
||||
<MoreInfo
|
||||
tooltip="Subscription FAQ"
|
||||
modalTitle="Frequently Asked Questions"
|
||||
modalBody={faqContent}
|
||||
className="text-gray-400 hover:text-white"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{!isProcessing ? (
|
||||
<div className="flex flex-col">
|
||||
{/* Only show premium benefits when not subscribed or session doesn't exist */}
|
||||
{(!session?.user || (session?.user && !subscribed)) && (
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<h3 className="text-xl font-bold text-primary">Unlock Premium Benefits</h3>
|
||||
<p className="text-gray-400">Subscribe now and elevate your development journey!</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 mb-4">
|
||||
<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={25} height={25} className='mr-2' />
|
||||
<span>Claim your own personal plebdevs.com Nostr NIP-05 identity</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="mb-2 rounded-lg">
|
||||
{/* Status Messages */}
|
||||
{session && session?.user ? (
|
||||
<>
|
||||
{subscribed && !user?.role?.nwc && (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center bg-green-900/50 border border-green-700 rounded p-2 text-green-300 w-fit">
|
||||
<i className="pi pi-check-circle mr-2"></i>
|
||||
<span>Subscribed!</span>
|
||||
</div>
|
||||
<p className="mt-3 font-medium">Thank you for your support 🎉</p>
|
||||
<p className="text-sm text-gray-400 mt-1">Pay-as-you-go subscription must be manually renewed on {subscribedUntil?.toLocaleDateString()}</p>
|
||||
</div>
|
||||
)}
|
||||
{subscribed && user?.role?.nwc && (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center bg-green-900/50 border border-green-700 rounded p-2 text-green-300 w-fit">
|
||||
<i className="pi pi-check-circle mr-2"></i>
|
||||
<span>Subscribed!</span>
|
||||
</div>
|
||||
<p className="mt-3 font-medium">Thank you for your support 🎉</p>
|
||||
<p className="text-sm text-gray-400 mt-1">Recurring subscription will AUTO renew on {subscribedUntil?.toLocaleDateString()}</p>
|
||||
</div>
|
||||
)}
|
||||
{(!subscribed && !subscriptionExpiredAt) && (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center bg-blue-900/50 border border-blue-700 rounded p-2 text-blue-300 w-fit">
|
||||
<i className="pi pi-info-circle mr-2"></i>
|
||||
<span>You currently have no active subscription</span>
|
||||
</div>
|
||||
<p className="mt-3 text-gray-400">
|
||||
Subscribe below to unlock all premium features and content.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{subscriptionExpiredAt && (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center bg-yellow-900/50 border border-yellow-700 rounded p-2 text-yellow-300 w-fit">
|
||||
<i className="pi pi-exclamation-triangle mr-2"></i>
|
||||
<span>Your subscription expired on {subscriptionExpiredAt.toLocaleDateString()}</span>
|
||||
</div>
|
||||
<p className="mt-3 text-gray-400">
|
||||
Renew below to continue enjoying all premium benefits.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center bg-blue-900/50 border border-blue-700 rounded p-2 text-blue-300 w-fit">
|
||||
<i className="pi pi-info-circle mr-2"></i>
|
||||
<span>Login to manage your subscription</span>
|
||||
</div>
|
||||
<p className="mt-3 text-gray-400">
|
||||
Sign in to access subscription features and management.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Payment Buttons */}
|
||||
{(!session?.user || (session?.user && !subscribed)) && (
|
||||
<SubscriptionPaymentButtons
|
||||
onSuccess={handleSubscriptionSuccess}
|
||||
onRecurringSubscriptionSuccess={handleRecurringSubscriptionSuccess}
|
||||
onError={handleSubscriptionError}
|
||||
setIsProcessing={setIsProcessing}
|
||||
layout={windowWidth < 768 ? "col" : "row"}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<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>
|
||||
|
||||
{/* Subscription Management */}
|
||||
{session?.user && subscribed && (
|
||||
<>
|
||||
<Card title="Subscription Benefits" className="mb-2">
|
||||
<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 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>
|
||||
</Card>
|
||||
<Card title="Manage Subscription" className="mb-2">
|
||||
<div className='flex flex-col gap-4'>
|
||||
<GenericButton 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>
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Card title="Key Features" className={`mb-2 ${isMobile ? 'm-2' : null}`}>
|
||||
<div className="flex flex-col gap-4 max-w-[80%] max-mob:max-w-full">
|
||||
<div className="flex flex-col items-start justify-center">
|
||||
<div className='flex items-start'>
|
||||
@ -134,7 +445,7 @@ const AboutPage = () => {
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card title="Connect with Us" className="mb-4 max-tab:mx-2">
|
||||
<Card title="Connect with Us" className="max-tab:mx-2 mb-20 lg:mb-2">
|
||||
<div className="flex flex-wrap gap-4 justify-center">
|
||||
<GenericButton
|
||||
severity="secondary"
|
||||
@ -175,6 +486,32 @@ const AboutPage = () => {
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Dialog Components */}
|
||||
<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)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,306 +0,0 @@
|
||||
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';
|
||||
|
||||
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);
|
||||
|
||||
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]);
|
||||
|
||||
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 (
|
||||
<div className="w-full px-12 my-4">
|
||||
{windowWidth < 768 && (
|
||||
<h1 className="text-3xl font-bold mb-6">Subscription Management</h1>
|
||||
)}
|
||||
<div className="mb-2 p-4 bg-gray-800 rounded-lg w-fit">
|
||||
{session && session?.user ? (
|
||||
<>
|
||||
{subscribed && !user?.role?.nwc && (
|
||||
<div className="flex flex-col">
|
||||
<Message className="w-fit" severity="success" text="Subscribed!" />
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
{subscribed && user?.role?.nwc && (
|
||||
<div className="flex flex-col">
|
||||
<Message className="w-fit" severity="success" text="Subscribed!" />
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
{(!subscribed && !subscriptionExpiredAt) && (
|
||||
<div className="flex flex-col">
|
||||
<Message className="w-fit" severity="info" text="You currently have no active subscription" />
|
||||
</div>
|
||||
)}
|
||||
{subscriptionExpiredAt && (
|
||||
<div className="flex flex-col">
|
||||
<Message className="w-fit" severity="warn" text={`Your subscription expired on ${subscriptionExpiredAt.toLocaleDateString()}`} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="flex flex-col">
|
||||
<Message className="w-fit" severity="info" text="Login to manage your subscription" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!session?.user && (
|
||||
<>
|
||||
<Card title="Start Your PlebDevs Journey" className="mb-2">
|
||||
<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.
|
||||
</p>
|
||||
<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.
|
||||
</p>
|
||||
</Card>
|
||||
<Card title="Ready to level up?" className="mb-2">
|
||||
<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" />
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Card title="Subscribe to PlebDevs" className="mb-2">
|
||||
{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>
|
||||
) : (
|
||||
<div className="flex flex-col">
|
||||
<div className="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">
|
||||
<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={25} height={25} 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}
|
||||
layout={windowWidth < 768 ? "col" : "row"}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{session?.user && subscribed && (
|
||||
<>
|
||||
<Card title="Subscription Benefits" className="mb-2">
|
||||
<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 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>
|
||||
</Card>
|
||||
<Card title="Manage Subscription" className="mb-2">
|
||||
<div className='flex flex-col gap-4'>
|
||||
<GenericButton 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>
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Card title="Frequently Asked Questions" className="mb-2">
|
||||
<div className="flex flex-col gap-4 max-w-[80%] max-mob:max-w-full">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">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 className='flex flex-col gap-2'>
|
||||
<h3 className="text-lg font-semibold">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 className='flex flex-col gap-2'>
|
||||
<h3 className="text-lg font-semibold">How much does the subscription cost?</h3>
|
||||
<p>The subscription is 50,000 sats per month.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">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>
|
||||
<h3 className="text-lg font-semibold">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>
|
||||
<h3 className="text-lg font-semibold">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>
|
||||
<h3 className="text-lg font-semibold">What happens if I don't renew my subscription?</h3>
|
||||
<p>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.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">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>
|
||||
</Card>
|
||||
|
||||
<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)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Subscribe;
|
Loading…
x
Reference in New Issue
Block a user