mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-06 18:31:00 +00:00
pay as you go subscriptions working and recurring subscriptions flow is halfway done, had to change schema.
This commit is contained in:
parent
e3cced22c6
commit
c8870bc1ff
@ -4,7 +4,6 @@ CREATE TABLE "User" (
|
|||||||
"pubkey" TEXT NOT NULL,
|
"pubkey" TEXT NOT NULL,
|
||||||
"username" TEXT,
|
"username" TEXT,
|
||||||
"avatar" TEXT,
|
"avatar" TEXT,
|
||||||
"roleId" TEXT,
|
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
@ -14,7 +13,11 @@ CREATE TABLE "User" (
|
|||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "Role" (
|
CREATE TABLE "Role" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
"subscribed" BOOLEAN NOT NULL DEFAULT false,
|
"subscribed" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"subscriptionStartDate" TIMESTAMP(3),
|
||||||
|
"lastPaymentAt" TIMESTAMP(3),
|
||||||
|
"nwc" TEXT,
|
||||||
|
|
||||||
CONSTRAINT "Role_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "Role_pkey" PRIMARY KEY ("id")
|
||||||
);
|
);
|
||||||
@ -121,6 +124,9 @@ CREATE UNIQUE INDEX "User_pubkey_key" ON "User"("pubkey");
|
|||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Role_userId_key" ON "Role"("userId");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "Course_noteId_key" ON "Course"("noteId");
|
CREATE UNIQUE INDEX "Course_noteId_key" ON "Course"("noteId");
|
||||||
|
|
||||||
@ -128,7 +134,7 @@ CREATE UNIQUE INDEX "Course_noteId_key" ON "Course"("noteId");
|
|||||||
CREATE UNIQUE INDEX "Resource_noteId_key" ON "Resource"("noteId");
|
CREATE UNIQUE INDEX "Resource_noteId_key" ON "Resource"("noteId");
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE "User" ADD CONSTRAINT "User_roleId_fkey" FOREIGN KEY ("roleId") REFERENCES "Role"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
ALTER TABLE "Role" ADD CONSTRAINT "Role_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE "Purchase" ADD CONSTRAINT "Purchase_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
ALTER TABLE "Purchase" ADD CONSTRAINT "Purchase_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
@ -17,16 +17,19 @@ model User {
|
|||||||
resources Resource[]
|
resources Resource[]
|
||||||
courseDrafts CourseDraft[]
|
courseDrafts CourseDraft[]
|
||||||
drafts Draft[]
|
drafts Draft[]
|
||||||
role Role? @relation(fields: [roleId], references: [id])
|
role Role?
|
||||||
roleId String?
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
model Role {
|
model Role {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
|
user User @relation(fields: [userId], references: [id])
|
||||||
|
userId String @unique
|
||||||
subscribed Boolean @default(false)
|
subscribed Boolean @default(false)
|
||||||
users User[]
|
subscriptionStartDate DateTime?
|
||||||
|
lastPaymentAt DateTime?
|
||||||
|
nwc String?
|
||||||
}
|
}
|
||||||
|
|
||||||
model Purchase {
|
model Purchase {
|
||||||
|
158
src/components/bitcoinConnect/SubscriptionPaymentButton.js
Normal file
158
src/components/bitcoinConnect/SubscriptionPaymentButton.js
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Button } from 'primereact/button';
|
||||||
|
import { initializeBitcoinConnect } from './BitcoinConnect';
|
||||||
|
import { LightningAddress } from '@getalby/lightning-tools';
|
||||||
|
import { useToast } from '@/hooks/useToast';
|
||||||
|
import { useSession } from 'next-auth/react';
|
||||||
|
import { useLocalStorageWithEffect } from '@/hooks/useLocalStroage';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
|
||||||
|
const PaymentModal = dynamic(
|
||||||
|
() => import('@getalby/bitcoin-connect-react').then((mod) => mod.Payment),
|
||||||
|
{ ssr: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
const SubscriptionPaymentButtons = ({ onSuccess, onError }) => {
|
||||||
|
const [invoice, setInvoice] = useState(null);
|
||||||
|
const [paid, setPaid] = useState(null);
|
||||||
|
const [nwcUrl, setNwcUrl] = useState(null);
|
||||||
|
const { showToast } = useToast();
|
||||||
|
const { data: session } = useSession();
|
||||||
|
|
||||||
|
const lnAddress = process.env.NEXT_PUBLIC_LIGHTNING_ADDRESS;
|
||||||
|
const amount = 25;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
initializeBitcoinConnect();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let intervalId;
|
||||||
|
if (invoice) {
|
||||||
|
intervalId = setInterval(async () => {
|
||||||
|
const paid = await invoice.verifyPayment();
|
||||||
|
|
||||||
|
console.log('paid', paid);
|
||||||
|
|
||||||
|
if (paid && invoice.preimage) {
|
||||||
|
setPaid({
|
||||||
|
preimage: invoice.preimage,
|
||||||
|
});
|
||||||
|
clearInterval(intervalId);
|
||||||
|
// handle success
|
||||||
|
onSuccess();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
console.log('no invoice');
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (intervalId) {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [invoice]);
|
||||||
|
|
||||||
|
const fetchInvoice = async () => {
|
||||||
|
try {
|
||||||
|
const ln = new LightningAddress(lnAddress);
|
||||||
|
await ln.fetch();
|
||||||
|
const newInvoice = await ln.requestInvoice({ satoshi: amount });
|
||||||
|
console.log('newInvoice', newInvoice);
|
||||||
|
setInvoice(newInvoice);
|
||||||
|
return newInvoice;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching invoice:', error);
|
||||||
|
showToast('error', 'Invoice Error', 'Failed to fetch the invoice.');
|
||||||
|
if (onError) onError(error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePaymentSuccess = async (response) => {
|
||||||
|
console.log('Payment successful', response);
|
||||||
|
clearInterval(checkPaymentInterval);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePaymentError = async (error) => {
|
||||||
|
console.error('Payment error', error);
|
||||||
|
clearInterval(checkPaymentInterval);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRecurringSubscription = async () => {
|
||||||
|
const { init, launchModal, onConnected } = await import('@getalby/bitcoin-connect-react');
|
||||||
|
|
||||||
|
init({
|
||||||
|
appName: 'plebdevs.com',
|
||||||
|
filters: ['nwc'],
|
||||||
|
onConnected: async (connector) => {
|
||||||
|
console.log('connector', connector);
|
||||||
|
if (connector.type === 'nwc') {
|
||||||
|
console.log('connector inside nwc', connector);
|
||||||
|
const nwcConnector = connector;
|
||||||
|
const url = await nwcConnector.getNWCUrl();
|
||||||
|
setNwcUrl(url);
|
||||||
|
console.log('NWC URL:', url);
|
||||||
|
// Here you can handle the NWC URL, e.g., send it to your backend
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
launchModal();
|
||||||
|
|
||||||
|
// Set up a listener for the connection event
|
||||||
|
const unsubscribe = onConnected((provider) => {
|
||||||
|
console.log('Connected provider:', provider);
|
||||||
|
const nwc = provider?.client?.options?.nostrWalletConnectUrl;
|
||||||
|
// try to make payment
|
||||||
|
// if successful, encrypt and send to db with subscription object on the user
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clean up the listener when the component unmounts
|
||||||
|
return () => {
|
||||||
|
unsubscribe();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
!invoice && (
|
||||||
|
<div className="w-full flex flex-row justify-between">
|
||||||
|
<Button
|
||||||
|
label="Pay as you go"
|
||||||
|
icon="pi pi-bolt"
|
||||||
|
onClick={() => {
|
||||||
|
fetchInvoice();
|
||||||
|
}}
|
||||||
|
severity='primary'
|
||||||
|
className="mt-4 text-[#f8f8ff]"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
label="Setup Recurring Subscription"
|
||||||
|
className="mt-4 text-[#f8f8ff]"
|
||||||
|
onClick={handleRecurringSubscription}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
|
||||||
|
invoice && invoice.paymentRequest && (
|
||||||
|
<div className="w-full mx-auto mt-8">
|
||||||
|
<PaymentModal
|
||||||
|
invoice={invoice?.paymentRequest}
|
||||||
|
onPaid={handlePaymentSuccess}
|
||||||
|
onError={handlePaymentError}
|
||||||
|
paymentMethods='external'
|
||||||
|
title={`Pay ${amount} sats`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SubscriptionPaymentButtons;
|
@ -34,6 +34,8 @@ export default function CourseDetails({ processedEvent, paidCourse, lessons, dec
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const {ndk, addSigner} = useNDKContext();
|
const {ndk, addSigner} = useNDKContext();
|
||||||
|
|
||||||
|
const lnAddress = process.env.NEXT_PUBLIC_LN_ADDRESS;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("processedEvent", processedEvent);
|
console.log("processedEvent", processedEvent);
|
||||||
}, [processedEvent]);
|
}, [processedEvent]);
|
||||||
@ -146,7 +148,7 @@ export default function CourseDetails({ processedEvent, paidCourse, lessons, dec
|
|||||||
<div className='w-full flex justify-between items-center'>
|
<div className='w-full flex justify-between items-center'>
|
||||||
{paidCourse && !decryptionPerformed && (
|
{paidCourse && !decryptionPerformed && (
|
||||||
<CoursePaymentButton
|
<CoursePaymentButton
|
||||||
lnAddress={'bitcoinplebdev@stacker.news'}
|
lnAddress={lnAddress}
|
||||||
amount={processedEvent.price}
|
amount={processedEvent.price}
|
||||||
onSuccess={handlePaymentSuccess}
|
onSuccess={handlePaymentSuccess}
|
||||||
onError={handlePaymentError}
|
onError={handlePaymentError}
|
||||||
|
@ -9,6 +9,8 @@ import { useZapsSubscription } from "@/hooks/nostrQueries/zaps/useZapsSubscripti
|
|||||||
import { getTotalFromZaps } from "@/utils/lightning";
|
import { getTotalFromZaps } from "@/utils/lightning";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
|
|
||||||
|
const lnAddress = process.env.NEXT_PUBLIC_LN_ADDRESS;
|
||||||
|
|
||||||
const ResourceDetails = ({processedEvent, topics, title, summary, image, price, author, paidResource, decryptedContent, handlePaymentSuccess, handlePaymentError}) => {
|
const ResourceDetails = ({processedEvent, topics, title, summary, image, price, author, paidResource, decryptedContent, handlePaymentSuccess, handlePaymentError}) => {
|
||||||
const [zapAmount, setZapAmount] = useState(0);
|
const [zapAmount, setZapAmount] = useState(0);
|
||||||
|
|
||||||
@ -67,7 +69,7 @@ const ResourceDetails = ({processedEvent, topics, title, summary, image, price,
|
|||||||
/>
|
/>
|
||||||
<div className='w-full flex flex-row justify-between'>
|
<div className='w-full flex flex-row justify-between'>
|
||||||
{paidResource && !decryptedContent && <ResourcePaymentButton
|
{paidResource && !decryptedContent && <ResourcePaymentButton
|
||||||
lnAddress={'bitcoinplebdev@stacker.news'}
|
lnAddress={lnAddress}
|
||||||
amount={price}
|
amount={price}
|
||||||
onSuccess={handlePaymentSuccess}
|
onSuccess={handlePaymentSuccess}
|
||||||
onError={handlePaymentError}
|
onError={handlePaymentError}
|
||||||
|
67
src/components/profile/SubscribeModal.js
Normal file
67
src/components/profile/SubscribeModal.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Dialog } from 'primereact/dialog';
|
||||||
|
import { Button } from 'primereact/button';
|
||||||
|
import SubscriptionPaymentButtons from '@/components/bitcoinConnect/SubscriptionPaymentButton';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { useSession } from 'next-auth/react';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { useToast } from '@/hooks/useToast';
|
||||||
|
|
||||||
|
const SubscribeModal = ({ visible, onHide }) => {
|
||||||
|
const { data: session, update } = useSession();
|
||||||
|
const { showToast } = useToast();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleSubscriptionSuccess = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.put('/api/users/subscription', {
|
||||||
|
userId: session.user.id,
|
||||||
|
isSubscribed: true,
|
||||||
|
});
|
||||||
|
if (response.data) {
|
||||||
|
await update();
|
||||||
|
showToast('success', 'Subscription successful', 'success');
|
||||||
|
onHide();
|
||||||
|
router.reload();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Subscription update error:', error);
|
||||||
|
showToast('error', 'Subscription failed', 'error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubscriptionError = (error) => {
|
||||||
|
console.error('Subscription error:', error);
|
||||||
|
showToast('error', 'Subscription failed', 'error');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
header="Subscribe"
|
||||||
|
visible={visible}
|
||||||
|
style={{ width: '50vw' }}
|
||||||
|
onHide={onHide}
|
||||||
|
>
|
||||||
|
<p className="m-0 font-bold">
|
||||||
|
Subscribe to PlebDevs and get access to:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>- All of our content free and paid</li>
|
||||||
|
<li>- PlebLab Bitcoin Hackerspace Slack</li>
|
||||||
|
<li>- An exclusive calendar to book 1:1's with our team</li>
|
||||||
|
</ul>
|
||||||
|
<p className="m-0 font-bold">
|
||||||
|
ALSO
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>- I WILL MAKE SURE YOU WIN HARD AND LEVEL UP AS A DEV</li>
|
||||||
|
</ul>
|
||||||
|
<SubscriptionPaymentButtons
|
||||||
|
onSuccess={handleSubscriptionSuccess}
|
||||||
|
onError={handleSubscriptionError}
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SubscribeModal;
|
@ -98,3 +98,29 @@ export const deleteUser = async (id) => {
|
|||||||
where: { id },
|
where: { id },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateUserSubscription = async (userId, isSubscribed) => {
|
||||||
|
const now = new Date();
|
||||||
|
return await prisma.user.update({
|
||||||
|
where: { id: userId },
|
||||||
|
data: {
|
||||||
|
role: {
|
||||||
|
upsert: {
|
||||||
|
create: {
|
||||||
|
subscribed: isSubscribed,
|
||||||
|
subscriptionStartDate: isSubscribed ? now : null,
|
||||||
|
lastPaymentAt: isSubscribed ? now : null,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
subscribed: isSubscribed,
|
||||||
|
subscriptionStartDate: isSubscribed ? { set: now } : { set: null },
|
||||||
|
lastPaymentAt: isSubscribed ? now : { set: null },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
role: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
45
src/hooks/useLocalStroage.js
Normal file
45
src/hooks/useLocalStroage.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
// This version of the hook initializes state without immediately attempting to read from localStorage
|
||||||
|
function useLocalStorage(key, initialValue) {
|
||||||
|
const [storedValue, setStoredValue] = useState(initialValue);
|
||||||
|
|
||||||
|
// Function to update localStorage and state
|
||||||
|
const setValue = value => {
|
||||||
|
try {
|
||||||
|
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
||||||
|
setStoredValue(valueToStore); // Update state
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.localStorage.setItem(key, JSON.stringify(valueToStore)); // Update localStorage
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return [storedValue, setValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom hook to handle fetching and setting data from localStorage
|
||||||
|
export function useLocalStorageWithEffect(key, initialValue) {
|
||||||
|
const [storedValue, setStoredValue] = useLocalStorage(key, initialValue);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const item = window.localStorage.getItem(key);
|
||||||
|
// Only update if the item exists to prevent overwriting the initial value with null
|
||||||
|
if (item !== null) {
|
||||||
|
setStoredValue(JSON.parse(item));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}, [key]); // Dependencies array ensures this runs once on mount
|
||||||
|
|
||||||
|
return [storedValue, setStoredValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useLocalStorage;
|
18
src/pages/api/users/subscription.js
Normal file
18
src/pages/api/users/subscription.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { updateUserSubscription } from "@/db/models/userModels";
|
||||||
|
|
||||||
|
export default async function handler(req, res) {
|
||||||
|
if (req.method === 'PUT') {
|
||||||
|
try {
|
||||||
|
const { userId, isSubscribed } = req.body;
|
||||||
|
|
||||||
|
const updatedUser = await updateUserSubscription(userId, isSubscribed);
|
||||||
|
|
||||||
|
res.status(200).json(updatedUser);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.setHeader('Allow', ['PUT']);
|
||||||
|
res.status(405).end(`Method ${req.method} Not Allowed`);
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ import { Button } from "primereact/button";
|
|||||||
import { DataTable } from "primereact/datatable";
|
import { DataTable } from "primereact/datatable";
|
||||||
import { Menu } from "primereact/menu";
|
import { Menu } from "primereact/menu";
|
||||||
import { Column } from "primereact/column";
|
import { Column } from "primereact/column";
|
||||||
|
import { Message } from "primereact/message";
|
||||||
import { useImageProxy } from "@/hooks/useImageProxy";
|
import { useImageProxy } from "@/hooks/useImageProxy";
|
||||||
import { useSession } from 'next-auth/react';
|
import { useSession } from 'next-auth/react';
|
||||||
import { ProgressSpinner } from "primereact/progressspinner";
|
import { ProgressSpinner } from "primereact/progressspinner";
|
||||||
@ -12,10 +13,13 @@ import { formatDateTime } from "@/utils/time";
|
|||||||
import UserContent from "@/components/profile/UserContent";
|
import UserContent from "@/components/profile/UserContent";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import BitcoinConnectButton from "@/components/bitcoinConnect/BitcoinConnect";
|
import BitcoinConnectButton from "@/components/bitcoinConnect/BitcoinConnect";
|
||||||
|
import SubscribeModal from "@/components/profile/SubscribeModal";
|
||||||
|
|
||||||
const Profile = () => {
|
const Profile = () => {
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const [bitcoinConnect, setBitcoinConnect] = useState(false);
|
const [subscribeModalVisible, setSubscribeModalVisible] = useState(false); // Add this state
|
||||||
|
const [subscribed, setSubscribed] = useState(false);
|
||||||
|
const [subscribedUntil, setSubscribedUntil] = useState(null);
|
||||||
|
|
||||||
const { data: session, status } = useSession();
|
const { data: session, status } = useSession();
|
||||||
const { returnImageProxy } = useImageProxy();
|
const { returnImageProxy } = useImageProxy();
|
||||||
@ -23,18 +27,15 @@ const Profile = () => {
|
|||||||
const menu = useRef(null);
|
const menu = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window === 'undefined') return;
|
if (session && session.user) {
|
||||||
|
|
||||||
const bitcoinConnectConfig = window.localStorage.getItem('bc:config');
|
|
||||||
|
|
||||||
if (bitcoinConnectConfig) {
|
|
||||||
setBitcoinConnect(true);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (session) {
|
|
||||||
setUser(session.user);
|
setUser(session.user);
|
||||||
|
if (session.user.role) {
|
||||||
|
setSubscribed(session.user.role.subscribed);
|
||||||
|
const subscribedAt = new Date(session.user.role.subscribedAt);
|
||||||
|
// The user is subscribed until the date in subscribedAt + 30 days
|
||||||
|
const subscribedUntil = new Date(subscribedAt.getTime() + 30 * 24 * 60 * 60 * 1000);
|
||||||
|
setSubscribedUntil(subscribedUntil);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [session]);
|
}, [session]);
|
||||||
|
|
||||||
@ -61,6 +62,10 @@ const Profile = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const openSubscribeModal = () => {
|
||||||
|
setSubscribeModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
user && (
|
user && (
|
||||||
<div className="w-[90vw] mx-auto max-tab:w-[100vw] max-mob:w-[100vw]">
|
<div className="w-[90vw] mx-auto max-tab:w-[100vw] max-mob:w-[100vw]">
|
||||||
@ -87,16 +92,26 @@ const Profile = () => {
|
|||||||
{user.pubkey}
|
{user.pubkey}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="flex flex-col w-1/2 mx-auto my-4 justify-between items-center">
|
<div className="flex flex-col w-1/2 mx-auto my-4 justify-between items-center">
|
||||||
<h2>Connect Your Lightning Wallet</h2>
|
<h2 className="text-xl my-2">Connect Your Lightning Wallet</h2>
|
||||||
{bitcoinConnect ? <BitcoinConnectButton /> : <p>Connecting...</p>}
|
<BitcoinConnectButton />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-1/2 mx-auto my-4 justify-between items-center">
|
<div className="flex flex-col w-1/2 mx-auto my-4 justify-between items-center border-2 border-gray-700 bg-[#121212] p-8 rounded-md">
|
||||||
<h2>Subscription</h2>
|
{subscribed ? (
|
||||||
<p className="text-center">You currently have no active subscription</p>
|
<>
|
||||||
|
<Message severity="success" text="Subscribed!" />
|
||||||
|
<p className="mt-8">Thank you for your support 🎉</p>
|
||||||
|
<p className="text-sm text-gray-400">Pay-as-you-go subscription active until {subscribedUntil.toLocaleDateString()}</p>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Message severity="info" text="You currently have no active subscription" />
|
||||||
<Button
|
<Button
|
||||||
label="Subscribe"
|
label="Subscribe"
|
||||||
className="p-button-raised p-button-success w-auto my-2 text-[#f8f8ff]"
|
className="w-auto mt-8 text-[#f8f8ff]"
|
||||||
|
onClick={openSubscribeModal} // Add this onClick handler
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!session || !session?.user || !ndk ? (
|
{!session || !session?.user || !ndk ? (
|
||||||
@ -122,6 +137,11 @@ const Profile = () => {
|
|||||||
|
|
||||||
)}
|
)}
|
||||||
<UserContent />
|
<UserContent />
|
||||||
|
<SubscribeModal
|
||||||
|
visible={subscribeModalVisible}
|
||||||
|
onHide={() => setSubscribeModalVisible(false)}
|
||||||
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user