Fix non logged in subscribe page and some mobile styles

This commit is contained in:
austinkelsay 2024-09-27 17:00:20 -05:00
parent ace772f32c
commit 6efbdb1ed7
4 changed files with 191 additions and 163 deletions

View File

@ -95,48 +95,6 @@ const UserSubscription = () => {
} }
}; };
const menuItems = [
{
label: "Schedule 1:1",
icon: "pi pi-calendar",
command: () => {
setCalendlyVisible(true);
},
},
{
label: session?.user?.lightningAddress ? "Update PlebDevs Lightning Address" : "Claim PlebDevs Lightning Address",
icon: "pi pi-bolt",
command: () => {
setLightningAddressVisible(true);
},
},
{
label: session?.user?.nip05 ? "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);
},
},
];
useEffect(() => {
console.log(subscribed)
}, [subscribed])
return ( return (
<div className="p-4"> <div className="p-4">
{windowWidth < 768 && ( {windowWidth < 768 && (

View File

@ -150,7 +150,7 @@ const Sidebar = ({ course = false }) => {
<i className="pi pi-plus pl-5 text-sm" /> <p className="pl-2 rounded-md font-bold text-lg">Create</p> <i className="pi pi-plus pl-5 text-sm" /> <p className="pl-2 rounded-md font-bold text-lg">Create</p>
</div> </div>
)} )}
<div onClick={() => session ? router.push('/profile?tab=subscribe') : router.push('/auth/signin')} className={`w-full flex flex-row items-center cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/profile?tab=subscribe') ? 'bg-gray-700' : ''}`}> <div onClick={() => session ? router.push('/profile?tab=subscribe') : router.push('/auth/signin')} className={`w-full flex flex-row items-center cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/profile?tab=subscribe') || isActive('/subscribe') ? 'bg-gray-700' : ''}`}>
<i className="pi pi-star pl-5 text-sm" /> <p className="pl-2 rounded-md font-bold text-lg">Subscribe</p> <i className="pi pi-star pl-5 text-sm" /> <p className="pl-2 rounded-md font-bold text-lg">Subscribe</p>
</div> </div>
<Accordion activeIndex={0} className={styles['p-accordion']}> <Accordion activeIndex={0} className={styles['p-accordion']}>

View File

@ -34,7 +34,7 @@ const AboutPage = () => {
)} )}
<Card className="mb-4"> <Card className="mb-4">
<div className='flex flex-row gap-4'> <div className='flex flex-row gap-4 max-mob:flex-col max-tab:flex-col'>
<Message pt={{ <Message pt={{
icon: { icon: {
className: 'hidden' className: 'hidden'
@ -73,7 +73,7 @@ const AboutPage = () => {
<ul className="list-disc list-inside ml-2 mt-2 space-y-2"> <ul className="list-disc list-inside ml-2 mt-2 space-y-2">
<li><span className="text-lg font-semibold">Documents:</span> Markdown documents posted as NIP-23 long-form events on Nostr.</li> <li><span className="text-lg font-semibold">Documents:</span> Markdown documents posted as NIP-23 long-form events on Nostr.</li>
<li><span className="text-lg font-semibold">Videos:</span> Enhanced markdown files with rich media support, including embedded videos, also saved as NIP-23 events.</li> <li><span className="text-lg font-semibold">Videos:</span> Enhanced markdown files with rich media support, including embedded videos, also saved as NIP-23 events.</li>
<li><span className="text-lg font-semibold">Courses:</span> Nostr lists that combine multiple documents and videos into a structured learning path.</li> <li><span className="text-lg font-semibold">Courses:</span> Nostr lists (NIP-51) that combines multiple documents and videos into a structured learning path.</li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -10,47 +10,66 @@ 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 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 Subscribe = () => {
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 windowWidth = useWindowWidth(); const windowWidth = useWindowWidth();
const [user, setUser] = useState(null);
const [isProcessing, setIsProcessing] = useState(false); const [isProcessing, setIsProcessing] = 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 menu = useRef(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(() => { useEffect(() => {
if (session && session?.user) { if (session && session?.user) {
setSubscribed(session?.user.role.subscribed); setUser(session.user);
const subscribedAt = new Date(session?.user.role.lastPaymentAt); }
}, [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); const subscribedUntil = new Date(subscribedAt.getTime() + 31 * 24 * 60 * 60 * 1000);
setSubscribedUntil(subscribedUntil); setSubscribedUntil(subscribedUntil);
if (session?.user.role.subscriptionExpiredAt) { if (user.role.subscriptionExpiredAt) {
const expiredAt = new Date(session?.user.role.subscriptionExpiredAt) const expiredAt = new Date(user.role.subscriptionExpiredAt)
setSubscriptionExpiredAt(expiredAt); setSubscriptionExpiredAt(expiredAt);
} }
} }
}, [session, session?.user]); }, [user]);
const handleSubscriptionSuccess = async (paymentResponse) => { const handleSubscriptionSuccess = async (response) => {
setIsProcessing(true); setIsProcessing(true);
try { try {
const response = await axios.post('/api/subscription/create', { const apiResponse = await axios.put('/api/users/subscription', {
paymentResponse, userId: session.user.id,
isSubscribed: true,
}); });
if (response.data.success) { if (apiResponse.data) {
showToast('success', 'Subscription successful!');
await update(); await update();
router.push('/dashboard'); showToast('success', 'Subscription Successful', 'Your subscription has been activated.');
} else { } else {
showToast('error', 'Subscription failed. Please try again.'); throw new Error('Failed to update subscription status');
} }
} catch (error) { } catch (error) {
console.error('Subscription error:', error); console.error('Subscription update error:', error);
showToast('error', 'An error occurred. Please try again.'); showToast('error', 'Subscription Update Failed', `Error: ${error.message}`);
} finally { } finally {
setIsProcessing(false); setIsProcessing(false);
} }
@ -58,51 +77,48 @@ const Subscribe = () => {
const handleSubscriptionError = (error) => { const handleSubscriptionError = (error) => {
console.error('Subscription error:', error); console.error('Subscription error:', error);
showToast('error', 'An error occurred during subscription. Please try again.'); showToast('error', 'Subscription Failed', `An error occurred: ${error.message}`);
setIsProcessing(false);
}; };
const handleRecurringSubscriptionSuccess = async (paymentResponse) => { const handleRecurringSubscriptionSuccess = async () => {
setIsProcessing(true); setIsProcessing(true);
try { try {
const response = await axios.post('/api/subscription/recurring', {
paymentResponse,
});
if (response.data.success) {
showToast('success', 'Recurring subscription set up successfully!');
await update(); await update();
router.push('/dashboard'); showToast('success', 'Recurring Subscription Activated', 'Your recurring subscription has been set up successfully.');
} else {
showToast('error', 'Failed to set up recurring subscription. Please try again.');
}
} catch (error) { } catch (error) {
console.error('Recurring subscription error:', error); console.error('Session update error:', error);
showToast('error', 'An error occurred. Please try again.'); showToast('error', 'Session Update Failed', `Error: ${error.message}`);
} finally { } finally {
setIsProcessing(false); setIsProcessing(false);
} }
}; };
const menuItems = [ const menuItems = [
{
label: "Renew Subscription",
icon: "pi pi-bolt",
command: () => {
// Add your renew functionality here
},
},
{ {
label: "Schedule 1:1", label: "Schedule 1:1",
icon: "pi pi-calendar", icon: "pi pi-calendar",
command: () => { command: () => setCalendlyVisible(true),
// Add your schedule functionality here
}, },
{
label: session?.user?.lightningAddress ? "Update PlebDevs Lightning Address" : "Claim PlebDevs Lightning Address",
icon: "pi pi-bolt",
command: () => setLightningAddressVisible(true),
},
{
label: session?.user?.nip05 ? "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", label: "Cancel Subscription",
icon: "pi pi-trash", icon: "pi pi-trash",
command: () => { command: () => setCancelSubscriptionVisible(true),
// Add your cancel functionality here
},
}, },
]; ];
@ -125,13 +141,12 @@ const Subscribe = () => {
return ( return (
<div className="p-4"> <div className="p-4">
{ {windowWidth < 768 && (
windowWidth < 768 && (
<h1 className="text-3xl font-bold mb-6">Subscription Management</h1> <h1 className="text-3xl font-bold mb-6">Subscription Management</h1>
) )}
} <div className="mb-4 p-4 bg-gray-800 rounded-lg w-fit">
{session && session?.user ? ( {session && session?.user ? (
<Card title={subscriptionCardTitleAndButton} className="mb-6"> <>
{subscribed && ( {subscribed && (
<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!" />
@ -149,25 +164,32 @@ const Subscribe = () => {
<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>
)} )}
</Card> </>
) : ( ) : (
<Card title={subscriptionCardTitle} className="mb-6"> <div className="flex flex-col">
<p className='mb-4 text-xl'> <Message className="w-fit" severity="info" text="Login to manage your subscription" />
The plebdevs subscription is a monthly subscription that unlocks all of the paid content, grants you access to the plebdevs 1:1 calendar for tutoring, support, and mentorship, and gives you an exclusive invite to the pleblab bitcoin hackerspace community.
</p>
<p className='text-xl'>
The plebdevs subscription is Lightning only and you can subscribe a month at a time with the pay as you go option or easily setup an auto recurring monthly subscription using Nostr Wallet Connect.
</p>
<div className='flex flex-col w-fit mt-4 bg-gray-900 p-4 rounded-lg'>
<p className='text-xl pb-8'>Login to start your subscription</p>
<GenericButton label="Login" onClick={() => router.push('/auth/signin')} className='text-[#f8f8ff] w-fit' size="small" rounded icon="pi pi-user" />
</div> </div>
)}
</div>
{!session?.user && (
<>
<Card title="Start Your PlebDevs Journey" className="mb-6">
<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>
<Card title="Ready to level up?" className="mb-4">
<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-6"> <Card title="Subscribe to PlebDevs" className="mb-4">
{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>
@ -175,59 +197,107 @@ const Subscribe = () => {
</div> </div>
) : ( ) : (
<div className="flex flex-col"> <div className="flex flex-col">
<h2 className="text-2xl font-semibold mb-4">Choose your subscription plan:</h2> <div className="mb-4">
<div className="flex flex-col gap-4"> <h2 className="text-2xl font-bold text-primary">Unlock Premium Benefits</h2>
<Card className='bg-gray-900 w-fit'> <p className="text-gray-400">Subscribe now and elevate your development journey!</p>
<h3 className="text-xl font-semibold mb-2">Monthly Subscription</h3> </div>
<p className="mb-4">Get access to all PlebDevs features / content one month at a time.</p> <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 className="flex items-center">
<i className="pi pi-star text-2xl text-primary mr-2 text-yellow-500"></i>
<span>I WILL MAKE SURE YOU WIN HARD AND LEVEL UP AS A DEV!</span>
</div>
</div>
<SubscriptionPaymentButtons <SubscriptionPaymentButtons
onSuccess={handleSubscriptionSuccess} onSuccess={handleSubscriptionSuccess}
onRecurringSubscriptionSuccess={handleRecurringSubscriptionSuccess}
onError={handleSubscriptionError} onError={handleSubscriptionError}
amount={10} setIsProcessing={setIsProcessing}
currency="USD" layout={windowWidth < 768 ? "col" : "row"}
buttonText="Subscribe for $10/month"
oneTime={true}
/> />
</Card>
<Card className='bg-gray-900 w-fit'>
<h3 className="text-xl font-semibold mb-2">Recurring Monthly Subscription</h3>
<p className="mb-4">Setup auto recurring monthly payments for uninterrupted access.</p>
<SubscriptionPaymentButtons
onSuccess={handleRecurringSubscriptionSuccess}
onError={handleSubscriptionError}
amount={10}
currency="USD"
buttonText="Set up recurring $10/month"
recurring={true}
/>
</Card>
</div>
</div> </div>
)} )}
</Card> </Card>
<Card title="Subscription Benefits" className="mb-6"> {session?.user && subscribed && (
<ul className="list-disc pl-6"> <>
<li>Access to exclusive content</li> <Card title="Subscription Benefits" className="mb-4">
<li>Priority support</li> <div className="flex flex-col gap-4">
<li>Early access to new features</li> <GenericButton severity="info" outlined className="w-fit text-start" label="Schedule 1:1" icon="pi pi-calendar" onClick={() => setCalendlyVisible(true)} />
<li>Community forums</li> <GenericButton severity="help" outlined className="w-fit text-start" label={session?.user?.nip05 ? "Update Nostr NIP-05" : "Claim PlebDevs Nostr NIP-05"} icon="pi pi-at" onClick={() => setNip05Visible(true)} />
</ul> <GenericButton severity="warning" outlined className="w-fit text-start" label={session?.user?.lightningAddress ? "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>
<Card title="Manage Subscription" className="mb-4">
<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-6"> <Card title="Frequently Asked Questions" className="mb-6">
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div> <div>
<h3 className="text-lg font-semibold">How does the subscription work?</h3> <h3 className="text-lg font-semibold">How does the subscription work?</h3>
<p>Our subscription provides monthly access to all PlebDevs features. You can choose between a one-time payment or a recurring subscription.</p> <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>
<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>
<div> <div>
<h3 className="text-lg font-semibold">Can I cancel my subscription?</h3> <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> <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>
{/* Add more FAQ items as needed */} <div>
<h3 className="text-lg font-semibold">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>
</Card> </Card>
<CalendlyEmbed
visible={calendlyVisible}
onHide={() => setCalendlyVisible(false)}
/>
<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> </div>
); );
}; };