mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-04-19 19:01:19 +00:00
frontend tab changes
This commit is contained in:
parent
6db4f4939c
commit
c54f0cfed2
@ -18,7 +18,7 @@ const PaymentModal = dynamic(
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
const SubscriptionPaymentButtons = ({ onSuccess, onError, onRecurringSubscriptionSuccess, setIsProcessing }) => {
|
||||
const SubscriptionPaymentButtons = ({ onSuccess, onError, onRecurringSubscriptionSuccess, setIsProcessing, oneTime = false, recurring = false }) => {
|
||||
const [invoice, setInvoice] = useState(null);
|
||||
const [showRecurringOptions, setShowRecurringOptions] = useState(false);
|
||||
const [nwcInput, setNwcInput] = useState('');
|
||||
@ -207,31 +207,35 @@ const SubscriptionPaymentButtons = ({ onSuccess, onError, onRecurringSubscriptio
|
||||
<>
|
||||
{!invoice && (
|
||||
<div className="w-full flex flex-row justify-between">
|
||||
<Button
|
||||
label="Pay as you go"
|
||||
icon="pi pi-bolt"
|
||||
onClick={async () => {
|
||||
const invoice = await fetchInvoice();
|
||||
setInvoice(invoice);
|
||||
}}
|
||||
severity='primary'
|
||||
className="w-fit mt-4 text-[#f8f8ff]"
|
||||
/>
|
||||
<Button
|
||||
label="Setup Recurring Subscription"
|
||||
icon={
|
||||
<Image
|
||||
src="/images/nwc-logo.svg"
|
||||
alt="NWC Logo"
|
||||
width={16}
|
||||
height={16}
|
||||
className="mr-2"
|
||||
/>
|
||||
}
|
||||
severity='help'
|
||||
className="w-fit mt-4 text-[#f8f8ff] bg-purple-600"
|
||||
onClick={() => setShowRecurringOptions(!showRecurringOptions)}
|
||||
/>
|
||||
{(oneTime || (!oneTime && !recurring)) && (
|
||||
<Button
|
||||
label="Pay as you go"
|
||||
icon="pi pi-bolt"
|
||||
onClick={async () => {
|
||||
const invoice = await fetchInvoice();
|
||||
setInvoice(invoice);
|
||||
}}
|
||||
severity='primary'
|
||||
className="w-fit mt-4 text-[#f8f8ff]"
|
||||
/>
|
||||
)}
|
||||
{(recurring || (!oneTime && !recurring)) && (
|
||||
<Button
|
||||
label="Setup Recurring Subscription"
|
||||
icon={
|
||||
<Image
|
||||
src="/images/nwc-logo.svg"
|
||||
alt="NWC Logo"
|
||||
width={16}
|
||||
height={16}
|
||||
className="mr-2"
|
||||
/>
|
||||
}
|
||||
severity='help'
|
||||
className="w-fit mt-4 text-[#f8f8ff] bg-purple-600"
|
||||
onClick={() => setShowRecurringOptions(!showRecurringOptions)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{showRecurringOptions && (
|
||||
|
@ -6,7 +6,7 @@ import { useNDKContext } from "@/context/NDKContext";
|
||||
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
|
||||
const MessageInput = ({ collapsed, onToggle, onMessageSent }) => {
|
||||
const MessageInput = ({ onMessageSent }) => {
|
||||
const [message, setMessage] = useState('');
|
||||
const { ndk, addSigner } = useNDKContext();
|
||||
const { showToast } = useToast();
|
||||
@ -34,7 +34,7 @@ const MessageInput = ({ collapsed, onToggle, onMessageSent }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Panel header={null} toggleable collapsed={collapsed} onToggle={onToggle} className="w-full" pt={{
|
||||
<Panel header={null} toggleable collapsed={false} className="w-full" pt={{
|
||||
header: {
|
||||
className: 'bg-transparent',
|
||||
border: 'none',
|
||||
@ -51,21 +51,20 @@ const MessageInput = ({ collapsed, onToggle, onMessageSent }) => {
|
||||
<InputTextarea
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
rows={5}
|
||||
cols={30}
|
||||
rows={2}
|
||||
cols={10}
|
||||
autoResize
|
||||
placeholder="Type your message here..."
|
||||
className="w-full"
|
||||
/>
|
||||
<div className="w-full flex flex-row justify-end">
|
||||
<Button
|
||||
label="Send"
|
||||
icon="pi pi-send"
|
||||
outlined
|
||||
className='mt-2'
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full flex flex-row justify-end mt-4">
|
||||
<Button
|
||||
label="Send"
|
||||
icon="pi pi-send"
|
||||
outlined
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
|
@ -5,7 +5,7 @@ import { useImageProxy } from '@/hooks/useImageProxy';
|
||||
import { Button } from 'primereact/button';
|
||||
import { Menu } from 'primereact/menu';
|
||||
import useWindowWidth from '@/hooks/useWindowWidth';
|
||||
import {useSession, signOut} from 'next-auth/react';
|
||||
import { useSession, signOut } from 'next-auth/react';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import 'primereact/resources/primereact.min.css';
|
||||
import 'primeicons/primeicons.css';
|
||||
@ -55,7 +55,7 @@ const UserAvatar = () => {
|
||||
{
|
||||
label: 'Profile',
|
||||
icon: 'pi pi-user',
|
||||
command: () => router.push('/profile')
|
||||
command: () => router.push('/profile?tab=profile')
|
||||
},
|
||||
{
|
||||
label: 'Create',
|
||||
@ -87,22 +87,62 @@ const UserAvatar = () => {
|
||||
} else {
|
||||
userAvatar = (
|
||||
<div className='flex flex-row items-center justify-between'>
|
||||
<Button severity='help' rounded label="About" className='text-[#f8f8ff] mr-4' onClick={() => setVisible(true)} />
|
||||
<Dialog header="Header" visible={visible} style={{ width: '50vw' }} onHide={() => {if (!visible) return; setVisible(false); }}>
|
||||
<p className="m-0">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
|
||||
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
</p>
|
||||
</Dialog>
|
||||
<Button
|
||||
label="Login"
|
||||
icon="pi pi-user"
|
||||
className="text-[#f8f8ff]"
|
||||
rounded
|
||||
onClick={() => router.push('/auth/signin')}
|
||||
size={windowWidth < 768 ? 'small' : 'normal'}
|
||||
<Button severity='help' rounded label="About" className='text-[#f8f8ff] mr-4' onClick={() => setVisible(true)} />
|
||||
<Dialog header="About" visible={visible} style={{ width: '50vw' }} onHide={() => { if (!visible) return; setVisible(false); }}>
|
||||
<div className="space-y-6">
|
||||
<p className="text-lg"><i className="pi pi-info-circle mr-2"></i>PlebDevs is a custom-built education platform designed to help aspiring developers, with a special focus on Bitcoin Lightning and Nostr technologies.</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-xl font-semibold"><i className="pi pi-star mr-2"></i>Key Features:</h3>
|
||||
<ul className="space-y-4">
|
||||
<li><i className="pi pi-cloud mr-2"></i><span className="font-semibold">Content Distribution:</span> All educational content is published to Nostr and actively pulled from Nostr relays, ensuring decentralized and up-to-date information.</li>
|
||||
|
||||
<li>
|
||||
<i className="pi pi-file-edit mr-2"></i><span className="font-semibold">Content Types:</span>
|
||||
<ul className="list-disc list-inside ml-6 mt-2 space-y-1">
|
||||
<li><span className="italic">Resources:</span> Markdown documents posted as NIP-23 long-form events on Nostr.</li>
|
||||
<li><span className="italic">Workshops:</span> Enhanced markdown files with rich media support, including embedded videos, also saved as NIP-23 events.</li>
|
||||
<li><span className="italic">Courses:</span> Nostr lists that combine multiple resources and workshops into a structured learning path.</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<i className="pi pi-dollar mr-2"></i><span className="font-semibold">Monetization:</span>
|
||||
<ul className="list-disc list-inside ml-6 mt-2 space-y-1">
|
||||
<li>All content is zappable, allowing for micropayments.</li>
|
||||
<li>Some content is 'paid', requiring either atomic payments or a subscription for access.</li>
|
||||
<li>Subscription options include pay-as-you-go and recurring payments via Nostr Wallet Connect.</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li><i className="pi pi-users mr-2"></i><span className="font-semibold">Community Engagement:</span> A dedicated community section pulls in relevant PlebDevs channels. Users can read all PlebDevs content and interact with the community via Nostr.</li>
|
||||
|
||||
<li>
|
||||
<i className="pi pi-check-circle mr-2"></i><span className="font-semibold">Subscription Benefits:</span>
|
||||
<ul className="list-disc list-inside ml-6 mt-2 space-y-1">
|
||||
<li>Access to all content, including paid resources.</li>
|
||||
<li>Exclusive 1:1 calendar for personalized support.</li>
|
||||
<li>Access to exclusive channels.</li>
|
||||
<li>Personal mentorship to ensure success in becoming a developer.</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li><i className="pi pi-cog mr-2"></i><span className="font-semibold">Technology Stack:</span> The platform leverages Nostr for content distribution and community interaction, and Bitcoin Lightning Network for micropayments and subscriptions.</li>
|
||||
|
||||
<li><i className="pi pi-user mr-2"></i><span className="font-semibold">User Experience:</span> Seamless integration of learning resources, community engagement, and payment systems, with a focus on practical skills development in Bitcoin, Lightning, and Nostr technologies.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p className="italic text-lg"><i className="pi pi-flag mr-2"></i>PlebDevs aims to provide a comprehensive, decentralized learning experience for aspiring developers, with a strong emphasis on emerging technologies in the Bitcoin ecosystem.</p>
|
||||
</div>
|
||||
</Dialog>
|
||||
<Button
|
||||
label="Login"
|
||||
icon="pi pi-user"
|
||||
className="text-[#f8f8ff]"
|
||||
rounded
|
||||
onClick={() => router.push('/auth/signin')}
|
||||
size={windowWidth < 768 ? 'small' : 'normal'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -114,7 +114,7 @@ const UserContent = () => {
|
||||
|
||||
return (
|
||||
<div className="w-full min-bottom-bar:w-[87vw] mx-auto">
|
||||
<div className="border-y-2 border-gray-300 mt-12">
|
||||
<div className="border-b-2 border-gray-300 mt-8">
|
||||
<h2 className="text-center my-4">Your Content</h2>
|
||||
</div>
|
||||
<div className="flex flex-row w-full justify-between px-4">
|
||||
|
116
src/components/profile/UserProfile.js
Normal file
116
src/components/profile/UserProfile.js
Normal file
@ -0,0 +1,116 @@
|
||||
import React, { useRef, useState, useEffect } from "react";
|
||||
import { DataTable } from "primereact/datatable";
|
||||
import { Button } from "primereact/button";
|
||||
import { Menu } from "primereact/menu";
|
||||
import { Column } from "primereact/column";
|
||||
import { useImageProxy } from "@/hooks/useImageProxy";
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { ProgressSpinner } from "primereact/progressspinner";
|
||||
import PurchasedListItem from "@/components/profile/PurchasedListItem";
|
||||
import { useNDKContext } from "@/context/NDKContext";
|
||||
import { formatDateTime } from "@/utils/time";
|
||||
import { findKind0Fields } from "@/utils/nostr";
|
||||
import Image from "next/image";
|
||||
import BitcoinConnectButton from "@/components/bitcoinConnect/BitcoinConnect";
|
||||
import UserContent from "@/components/profile/UserContent";
|
||||
import SubscribeModal from "@/components/profile/subscription/SubscribeModal";
|
||||
const UserProfile = () => {
|
||||
const [user, setUser] = useState(null);
|
||||
const { data: session } = useSession();
|
||||
const { returnImageProxy } = useImageProxy();
|
||||
const { ndk, addSigner } = useNDKContext();
|
||||
const menu = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (session?.user) {
|
||||
setUser(session.user);
|
||||
}
|
||||
}, [session]);
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
label: "Edit",
|
||||
icon: "pi pi-pencil",
|
||||
command: () => {
|
||||
// Add your edit functionality here
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Delete",
|
||||
icon: "pi pi-trash",
|
||||
command: () => {
|
||||
// Add your delete functionality here
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const header = (
|
||||
<div className="flex flex-wrap align-items-center justify-content-between gap-2">
|
||||
<span className="text-xl text-900 font-bold text-[#f8f8ff]">Purchases</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
user && (
|
||||
<div className="h-full w-full min-bottom-bar:w-[87vw] max-sidebar:w-[100vw] mx-auto">
|
||||
<div className="w-full flex flex-col justify-center mx-auto">
|
||||
<div className="relative flex w-full items-center justify-center">
|
||||
<Image
|
||||
alt="user's avatar"
|
||||
src={returnImageProxy(user.avatar, user.pubkey)}
|
||||
width={100}
|
||||
height={100}
|
||||
className="rounded-full my-4"
|
||||
/>
|
||||
<i
|
||||
className="pi pi-ellipsis-h absolute right-24 text-2xl my-4 cursor-pointer hover:opacity-75"
|
||||
onClick={(e) => menu.current.toggle(e)}
|
||||
></i>
|
||||
<Menu model={menuItems} popup ref={menu} />
|
||||
</div>
|
||||
|
||||
<h1 className="text-center text-2xl my-2">
|
||||
{user.username || "Anon"}
|
||||
</h1>
|
||||
<h2 className="text-center text-xl my-2 truncate max-tab:px-4 max-mob:px-4">
|
||||
{user.pubkey}
|
||||
</h2>
|
||||
{user && (
|
||||
<SubscribeModal user={user} />
|
||||
)}
|
||||
</div>
|
||||
{!session || !session?.user || !ndk ? (
|
||||
<ProgressSpinner />
|
||||
) : (
|
||||
<DataTable
|
||||
emptyMessage="No purchases"
|
||||
value={session.user?.purchased}
|
||||
header={header}
|
||||
style={{ maxWidth: "90%", margin: "0 auto", borderRadius: "10px" }}
|
||||
pt={{
|
||||
wrapper: {
|
||||
className: "rounded-lg rounded-t-none"
|
||||
},
|
||||
header: {
|
||||
className: "rounded-t-lg"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Column field="amountPaid" header="Cost"></Column>
|
||||
<Column
|
||||
body={(rowData) => {
|
||||
console.log("rowData", rowData);
|
||||
return <PurchasedListItem eventId={rowData?.resource?.noteId || rowData?.course?.noteId} category={rowData?.course ? "courses" : "resources"} />
|
||||
}}
|
||||
header="Name"
|
||||
></Column>
|
||||
<Column body={session.user?.purchased?.some((item) => item.courseId) ? "course" : "resource"} header="Category"></Column>
|
||||
<Column body={rowData => formatDateTime(rowData?.createdAt)} header="Date"></Column>
|
||||
</DataTable>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default UserProfile;
|
108
src/components/profile/UserSettings.js
Normal file
108
src/components/profile/UserSettings.js
Normal file
@ -0,0 +1,108 @@
|
||||
import React, { useRef, useState, useEffect } from "react";
|
||||
import { Button } from "primereact/button";
|
||||
import { DataTable } from "primereact/datatable";
|
||||
import { Column } from "primereact/column";
|
||||
import { useImageProxy } from "@/hooks/useImageProxy";
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { ProgressSpinner } from "primereact/progressspinner";
|
||||
import { useNDKContext } from "@/context/NDKContext";
|
||||
import Image from "next/image";
|
||||
import BitcoinConnectButton from "@/components/bitcoinConnect/BitcoinConnect";
|
||||
|
||||
const UserSettings = () => {
|
||||
const [user, setUser] = useState(null);
|
||||
const { data: session } = useSession();
|
||||
const { returnImageProxy } = useImageProxy();
|
||||
const { ndk } = useNDKContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (session?.user) {
|
||||
setUser(session.user);
|
||||
}
|
||||
}, [session]);
|
||||
|
||||
const relayUrls = [
|
||||
"wss://nos.lol/",
|
||||
"wss://relay.damus.io/",
|
||||
"wss://relay.snort.social/",
|
||||
"wss://relay.nostr.band/",
|
||||
"wss://nostr.mutinywallet.com/",
|
||||
"wss://relay.mutinywallet.com/",
|
||||
"wss://relay.primal.net/"
|
||||
];
|
||||
|
||||
const relayStatusBody = (url) => {
|
||||
// Placeholder for relay status, replace with actual logic later
|
||||
const isConnected = Math.random() > 0.5;
|
||||
return (
|
||||
<i className={`pi ${isConnected ? 'pi-check-circle text-green-500' : 'pi-times-circle text-red-500'}`}></i>
|
||||
);
|
||||
};
|
||||
|
||||
const relayActionsBody = () => {
|
||||
return (
|
||||
<div>
|
||||
<Button icon="pi pi-plus" className="p-button-rounded p-button-success p-button-text mr-2" />
|
||||
<Button icon="pi pi-trash" className="p-button-rounded p-button-danger p-button-text" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const header = (
|
||||
<div className="flex flex-row justify-between">
|
||||
<span className="text-xl text-900 font-bold text-[#f8f8ff]">Relays</span>
|
||||
<Button icon="pi pi-plus" className="p-button-rounded p-button-success p-button-text mr-2" />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
user && (
|
||||
<div className="h-full w-full min-bottom-bar:w-[87vw] max-sidebar:w-[100vw] mx-auto">
|
||||
<div className="w-full flex flex-col justify-center mx-auto">
|
||||
<div className="relative flex w-full items-center justify-center">
|
||||
<Image
|
||||
alt="user's avatar"
|
||||
src={returnImageProxy(user.avatar, user.pubkey)}
|
||||
width={100}
|
||||
height={100}
|
||||
className="rounded-full my-4"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h1 className="text-center text-2xl my-2">
|
||||
{user.username || "Anon"}
|
||||
</h1>
|
||||
<h2 className="text-center text-xl my-2 truncate max-tab:px-4 max-mob:px-4">
|
||||
{user.pubkey}
|
||||
</h2>
|
||||
<div className="flex flex-col w-1/2 mx-auto my-8 mb-12 justify-between items-center">
|
||||
<h2 className="text-xl my-2">Connect Your Lightning Wallet</h2>
|
||||
<BitcoinConnectButton />
|
||||
</div>
|
||||
</div>
|
||||
{!session || !session?.user || !ndk ? (
|
||||
<ProgressSpinner />
|
||||
) : (
|
||||
<DataTable value={relayUrls}
|
||||
style={{ maxWidth: "90%", margin: "0 auto", borderRadius: "10px" }}
|
||||
header={header}
|
||||
pt={{
|
||||
wrapper: {
|
||||
className: "rounded-lg rounded-t-none"
|
||||
},
|
||||
header: {
|
||||
className: "rounded-t-lg"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Column field={(url) => url} header="Relay URL"></Column>
|
||||
<Column body={relayStatusBody} header="Status"></Column>
|
||||
<Column body={relayActionsBody} header="Actions"></Column>
|
||||
</DataTable>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default UserSettings;
|
@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { ProgressSpinner } from 'primereact/progressspinner';
|
||||
import SubscriptionPaymentButtons from '@/components/bitcoinConnect/SubscriptionPaymentButton';
|
||||
@ -8,13 +8,33 @@ import { useRouter } from 'next/router';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { Card } from 'primereact/card';
|
||||
import { Badge } from 'primereact/badge';
|
||||
import { Button } from "primereact/button";
|
||||
import { Menu } from "primereact/menu";
|
||||
import { Message } from "primereact/message";
|
||||
|
||||
// todo encrypt nwc before saving in db
|
||||
const SubscribeModal = ({ visible, onHide }) => {
|
||||
const SubscribeModal = ({ user }) => {
|
||||
const { data: session, update } = useSession();
|
||||
const { showToast } = useToast();
|
||||
const router = useRouter();
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [subscribed, setSubscribed] = useState(false);
|
||||
const [subscribedUntil, setSubscribedUntil] = useState(null);
|
||||
const [subscriptionExpiredAt, setSubscriptionExpiredAt] = useState(null);
|
||||
const menu = useRef(null);
|
||||
|
||||
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);
|
||||
@ -58,55 +78,121 @@ const SubscribeModal = ({ visible, onHide }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
label: "Renew Subscription",
|
||||
icon: "pi pi-bolt",
|
||||
command: () => {
|
||||
// Add your renew functionality here
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Schedule 1:1",
|
||||
icon: "pi pi-calendar",
|
||||
command: () => {
|
||||
// Add your schedule functionality here
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Cancel Subscription",
|
||||
icon: "pi pi-trash",
|
||||
command: () => {
|
||||
// Add your cancel functionality here
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const subscriptionCardTitle = (
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span className="text-xl text-900 font-bold text-white">Plebdevs Subscription</span>
|
||||
<i
|
||||
className="pi pi-ellipsis-h text-2xl cursor-pointer hover:opacity-75"
|
||||
onClick={(e) => menu.current.toggle(e)}
|
||||
></i>
|
||||
<Menu model={menuItems} popup ref={menu} />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
header="Subscribe to PlebDevs"
|
||||
visible={visible}
|
||||
onHide={onHide}
|
||||
className="p-fluid pb-0 w-fit"
|
||||
>
|
||||
{isProcessing ? (
|
||||
<div className="w-full flex flex-col mx-auto justify-center items-center mt-4">
|
||||
<ProgressSpinner />
|
||||
<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>
|
||||
<>
|
||||
<Card title={subscriptionCardTitle} className="w-fit m-8 mx-auto">
|
||||
{subscribed && (
|
||||
<div className="flex flex-col">
|
||||
<Message className="w-fit" 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 will renew on {subscribedUntil.toLocaleDateString()}</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div className="flex items-center">
|
||||
<i className="pi pi-book text-2xl text-primary mr-2"></i>
|
||||
<span>Access ALL current and future content</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<i className="pi pi-users text-2xl text-primary mr-2"></i>
|
||||
<span>Join PlebLab Bitcoin Hackerspace Slack</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<i className="pi pi-calendar text-2xl text-primary mr-2"></i>
|
||||
<span>Exclusive 1:1 booking calendar</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<i className="pi pi-star text-2xl text-primary mr-2"></i>
|
||||
<span>Personal mentorship & guidance</span>
|
||||
</div>
|
||||
)}
|
||||
{(!subscribed && !subscriptionExpiredAt) && (
|
||||
<div className="flex flex-col">
|
||||
<Message className="w-fit" severity="info" text="You currently have no active subscription" />
|
||||
<Button
|
||||
label="Subscribe"
|
||||
className="w-auto mt-8 text-[#f8f8ff]"
|
||||
onClick={() => setVisible(true)}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-center mb-4 flex flex-row justify-center">
|
||||
<Badge value="BONUS" severity="success" className="mr-2"></Badge>
|
||||
<span className="text-center font-bold">I WILL MAKE SURE YOU WIN HARD AND LEVEL UP AS A DEV!</span>
|
||||
)}
|
||||
{subscriptionExpiredAt && (
|
||||
<div className="flex flex-col">
|
||||
<Message className="w-fit" severity="warn" text={`Your subscription expired on ${subscriptionExpiredAt.toLocaleDateString()}`} />
|
||||
<Button
|
||||
label="Subscribe"
|
||||
className="w-auto mt-8 text-[#f8f8ff]"
|
||||
onClick={() => setVisible(true)}
|
||||
/>
|
||||
</div>
|
||||
<SubscriptionPaymentButtons
|
||||
onSuccess={handleSubscriptionSuccess}
|
||||
onRecurringSubscriptionSuccess={handleRecurringSubscriptionSuccess}
|
||||
onError={handleSubscriptionError}
|
||||
setIsProcessing={setIsProcessing}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
</Dialog>
|
||||
)}
|
||||
</Card>
|
||||
<Dialog
|
||||
header="Subscribe to PlebDevs"
|
||||
visible={visible}
|
||||
onHide={() => setVisible(false)}
|
||||
className="p-fluid pb-0 w-fit"
|
||||
>
|
||||
{isProcessing ? (
|
||||
<div className="w-full flex flex-col mx-auto justify-center items-center mt-4">
|
||||
<ProgressSpinner />
|
||||
<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="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div className="flex items-center">
|
||||
<i className="pi pi-book text-2xl text-primary mr-2"></i>
|
||||
<span>Access ALL current and future content</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<i className="pi pi-users text-2xl text-primary mr-2"></i>
|
||||
<span>Join PlebLab Bitcoin Hackerspace Slack</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<i className="pi pi-calendar text-2xl text-primary mr-2"></i>
|
||||
<span>Exclusive 1:1 booking calendar</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<i className="pi pi-star text-2xl text-primary mr-2"></i>
|
||||
<span>Personal mentorship & guidance</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-center mb-4 flex flex-row justify-center">
|
||||
<Badge value="BONUS" severity="success" className="mr-2"></Badge>
|
||||
<span className="text-center font-bold">I WILL MAKE SURE YOU WIN HARD AND LEVEL UP AS A DEV!</span>
|
||||
</div>
|
||||
<SubscriptionPaymentButtons
|
||||
onSuccess={handleSubscriptionSuccess}
|
||||
onRecurringSubscriptionSuccess={handleRecurringSubscriptionSuccess}
|
||||
onError={handleSubscriptionError}
|
||||
setIsProcessing={setIsProcessing}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,12 +1,20 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
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 { Button } from "primereact/button";
|
||||
import { Menu } from "primereact/menu";
|
||||
import { Message } from "primereact/message";
|
||||
import { Card } from "primereact/card";
|
||||
import SubscribeModal from "@/components/profile/subscription/SubscribeModal";
|
||||
import { ProgressSpinner } from 'primereact/progressspinner';
|
||||
import SubscriptionPaymentButtons from '@/components/bitcoinConnect/SubscriptionPaymentButton';
|
||||
|
||||
const UserSubscription = ({ user }) => {
|
||||
const [subscribeModalVisible, setSubscribeModalVisible] = useState(false);
|
||||
const { data: session, update } = useSession();
|
||||
const { showToast } = useToast();
|
||||
const router = useRouter();
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [subscribed, setSubscribed] = useState(false);
|
||||
const [subscribedUntil, setSubscribedUntil] = useState(null);
|
||||
const [subscriptionExpiredAt, setSubscriptionExpiredAt] = useState(null);
|
||||
@ -25,34 +33,77 @@ const UserSubscription = ({ user }) => {
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
const handleSubscriptionSuccess = async (paymentResponse) => {
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
const response = await axios.post('/api/subscription/create', {
|
||||
paymentResponse,
|
||||
});
|
||||
if (response.data.success) {
|
||||
showToast('success', 'Subscription successful!');
|
||||
await update();
|
||||
router.push('/dashboard');
|
||||
} else {
|
||||
showToast('error', 'Subscription failed. Please try again.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Subscription error:', error);
|
||||
showToast('error', 'An error occurred. Please try again.');
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubscriptionError = (error) => {
|
||||
console.error('Subscription error:', error);
|
||||
showToast('error', 'An error occurred during subscription. Please try again.');
|
||||
};
|
||||
|
||||
const handleRecurringSubscriptionSuccess = async (paymentResponse) => {
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
const response = await axios.post('/api/subscription/recurring', {
|
||||
paymentResponse,
|
||||
});
|
||||
if (response.data.success) {
|
||||
showToast('success', 'Recurring subscription set up successfully!');
|
||||
await update();
|
||||
router.push('/dashboard');
|
||||
} else {
|
||||
showToast('error', 'Failed to set up recurring subscription. Please try again.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Recurring subscription error:', error);
|
||||
showToast('error', 'An error occurred. Please try again.');
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
label: "Renew Subscription",
|
||||
icon: "pi pi-bolt",
|
||||
command: () => {
|
||||
// Add your edit functionality here
|
||||
// Add your renew functionality here
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Schedule 1:1",
|
||||
icon: "pi pi-calendar",
|
||||
command: () => {
|
||||
// Add your edit functionality here
|
||||
// Add your schedule functionality here
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Cancel Subscription",
|
||||
icon: "pi pi-trash",
|
||||
command: () => {
|
||||
// Add your delete functionality here
|
||||
// Add your cancel functionality here
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const openSubscribeModal = () => {
|
||||
setSubscribeModalVisible(true);
|
||||
};
|
||||
|
||||
const subscriptionCardTitle = (
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span className="text-xl text-900 font-bold text-white">Plebdevs Subscription</span>
|
||||
@ -65,41 +116,90 @@ const UserSubscription = ({ user }) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card title={subscriptionCardTitle} className="w-fit m-8 mx-auto">
|
||||
<div className="p-4">
|
||||
<h1 className="text-3xl font-bold mb-6">Subscription Management</h1>
|
||||
<Card title={subscriptionCardTitle} className="mb-6">
|
||||
{subscribed && (
|
||||
<div className="flex flex-col">
|
||||
<Message className="w-fit" severity="success" text="Subscribed!" />
|
||||
<p className="mt-8">Thank you for your support 🎉</p>
|
||||
<p className="mt-4">Thank you for your support 🎉</p>
|
||||
<p className="text-sm text-gray-400">Pay-as-you-go subscription will 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" />
|
||||
<Button
|
||||
label="Subscribe"
|
||||
className="w-auto mt-8 text-[#f8f8ff]"
|
||||
onClick={openSubscribeModal}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{subscriptionExpiredAt && (
|
||||
<div className="flex flex-col">
|
||||
<Message className="w-fit" severity="warn" text={`Your subscription expired on ${subscriptionExpiredAt.toLocaleDateString()}`} />
|
||||
<Button
|
||||
label="Subscribe"
|
||||
className="w-auto mt-8 text-[#f8f8ff]"
|
||||
onClick={openSubscribeModal}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
<SubscribeModal
|
||||
visible={subscribeModalVisible}
|
||||
onHide={() => setSubscribeModalVisible(false)}
|
||||
/>
|
||||
</>
|
||||
|
||||
<Card title="Subscribe to PlebDevs" className="mb-6">
|
||||
{isProcessing ? (
|
||||
<div className="w-full flex flex-col mx-auto justify-center items-center mt-4">
|
||||
<ProgressSpinner />
|
||||
<span className="ml-2">Processing subscription...</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col">
|
||||
<h2 className="text-2xl font-semibold mb-4">Choose your subscription plan:</h2>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Card className='bg-gray-900 w-fit'>
|
||||
<h3 className="text-xl font-semibold mb-2">Monthly Subscription</h3>
|
||||
<p className="mb-4">Get access to all PlebDevs features / content one month at a time.</p>
|
||||
<SubscriptionPaymentButtons
|
||||
onSuccess={handleSubscriptionSuccess}
|
||||
onError={handleSubscriptionError}
|
||||
amount={10}
|
||||
currency="USD"
|
||||
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>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
<Card title="Subscription Benefits" className="mb-6">
|
||||
<ul className="list-disc pl-6">
|
||||
<li>Access to exclusive content</li>
|
||||
<li>Priority support</li>
|
||||
<li>Early access to new features</li>
|
||||
<li>Community forums</li>
|
||||
</ul>
|
||||
</Card>
|
||||
|
||||
<Card title="Frequently Asked Questions" className="mb-6">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<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>
|
||||
</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>
|
||||
{/* Add more FAQ items as needed */}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Accordion, AccordionTab } from 'primereact/accordion';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useSession, signOut } from 'next-auth/react';
|
||||
import 'primeicons/primeicons.css';
|
||||
import styles from "./sidebar.module.css";
|
||||
|
||||
@ -13,40 +14,59 @@ const Sidebar = () => {
|
||||
return pathWithQuery === path;
|
||||
};
|
||||
|
||||
const { data: session } = useSession();
|
||||
|
||||
return (
|
||||
<div className='max-sidebar:hidden w-[13vw] bg-gray-800 p-2 fixed h-[100%]'>
|
||||
<div onClick={() => router.push('/')} className={`w-full flex flex-row items-center cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/') ? 'bg-gray-700' : ''}`}>
|
||||
<i className="pi pi-home pl-5" /> <p className="pl-2 rounded-md font-bold">Home</p>
|
||||
<div className='max-sidebar:hidden w-[13vw] bg-gray-800 p-2 fixed h-[100%] flex flex-col'>
|
||||
<div className="flex-grow overflow-y-auto">
|
||||
<div onClick={() => router.push('/')} className={`w-full flex flex-row items-center cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/') ? 'bg-gray-700' : ''}`}>
|
||||
<i className="pi pi-home pl-5" /> <p className="pl-2 rounded-md font-bold">Home</p>
|
||||
</div>
|
||||
<div onClick={() => router.push('/content')} className={`w-full flex flex-row items-center cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/content') ? 'bg-gray-700' : ''}`}>
|
||||
<i className="pi pi-video pl-5" /> <p className="pl-2 rounded-md font-bold">Content</p>
|
||||
</div>
|
||||
<div onClick={() => router.push('/create')} className={`w-full flex flex-row items-center cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/create') ? 'bg-gray-700' : ''}`}>
|
||||
<i className="pi pi-plus pl-5" /> <p className="pl-2 rounded-md font-bold">Create</p>
|
||||
</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' : ''}`}>
|
||||
<i className="pi pi-star pl-5" /> <p className="pl-2 rounded-md font-bold">Subscribe</p>
|
||||
</div>
|
||||
<Accordion activeIndex={0} className={styles['p-accordion']}>
|
||||
<AccordionTab
|
||||
pt={{
|
||||
headerAction: ({ context }) => ({
|
||||
className: `hover:bg-gray-700 rounded-lg ${isActive('/feed') ? 'bg-gray-700' : ''} ${styles['p-accordion-header-link']}`
|
||||
}),
|
||||
content: styles['p-accordion-content']
|
||||
}}
|
||||
header={"Community"}>
|
||||
<div onClick={() => router.push('/feed?channel=global')} className={`w-full cursor-pointer py-2 hover:bg-gray-700 rounded-lg ${isActive('/feed?channel=global') ? 'bg-gray-700' : ''}`}>
|
||||
<p className="pl-3 rounded-md font-bold"><i className="pi pi-hashtag text-sm"></i> global</p>
|
||||
</div>
|
||||
<div onClick={() => router.push('/feed?channel=nostr')} className={`w-full cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/feed?channel=nostr') ? 'bg-gray-700' : ''}`}>
|
||||
<p className="pl-3 rounded-md font-bold"><i className="pi pi-hashtag text-sm"></i> nostr</p>
|
||||
</div>
|
||||
<div onClick={() => router.push('/feed?channel=discord')} className={`w-full cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/feed?channel=discord') ? 'bg-gray-700' : ''}`}>
|
||||
<p className="pl-3 rounded-md font-bold"><i className="pi pi-hashtag text-sm"></i> discord</p>
|
||||
</div>
|
||||
<div onClick={() => router.push('/feed?channel=stackernews')} className={`w-full cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/feed?channel=stackernews') ? 'bg-gray-700' : ''}`}>
|
||||
<p className="pl-3 rounded-md font-bold"><i className="pi pi-hashtag text-sm"></i> stackernews</p>
|
||||
</div>
|
||||
</AccordionTab>
|
||||
</Accordion>
|
||||
</div>
|
||||
<div onClick={() => router.push('/content')} className={`w-full flex flex-row items-center cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/content') ? 'bg-gray-700' : ''}`}>
|
||||
<i className="pi pi-video pl-5" /> <p className="pl-2 rounded-md font-bold">Content</p>
|
||||
<div className='mt-auto'>
|
||||
<div onClick={() => router.push('/profile?tab=settings')} className={`w-full flex flex-row items-center cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/profile?tab=settings') ? 'bg-gray-700' : ''}`}>
|
||||
<i className="pi pi-cog pl-5" /> <p className="pl-2 rounded-md font-bold">Settings</p>
|
||||
</div>
|
||||
<div onClick={() => session ? signOut() : router.push('/auth/signin')} className={`w-full flex flex-row items-center cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg`}>
|
||||
<i className={`pi ${session ? 'pi-sign-out' : 'pi-sign-in'} pl-5`} /> <p className="pl-2 rounded-md font-bold">{session ? 'Logout' : 'Login'}</p>
|
||||
</div>
|
||||
{/* todo: have to add this extra button to push the sidebar to the right space but it doesnt seem to cause any negative side effects? */}
|
||||
<div onClick={signOut} className={`w-full flex flex-row items-center cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg`}>
|
||||
<i className="pi pi-sign-out pl-5" /> <p className="pl-2 rounded-md font-bold">Logout</p>
|
||||
</div>
|
||||
</div>
|
||||
<div onClick={() => router.push('/create')} className={`w-full flex flex-row items-center cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/create') ? 'bg-gray-700' : ''}`}>
|
||||
<i className="pi pi-plus pl-5" /> <p className="pl-2 rounded-md font-bold">Create</p>
|
||||
</div>
|
||||
<Accordion activeIndex={0} className={styles['p-accordion']}>
|
||||
<AccordionTab
|
||||
pt={{
|
||||
headerAction: ({ context }) => ({
|
||||
className: `hover:bg-gray-700 rounded-lg ${isActive('/feed') ? 'bg-gray-700' : ''} ${styles['p-accordion-header-link']}`
|
||||
}),
|
||||
content: styles['p-accordion-content']
|
||||
}}
|
||||
header={"Community"}>
|
||||
<div onClick={() => router.push('/feed?channel=global')} className={`w-full cursor-pointer py-2 hover:bg-gray-700 rounded-lg ${isActive('/feed?channel=global') ? 'bg-gray-700' : ''}`}>
|
||||
<p className="pl-3 rounded-md font-bold"><i className="pi pi-hashtag text-sm"></i> global</p>
|
||||
</div>
|
||||
<div onClick={() => router.push('/feed?channel=nostr')} className={`w-full cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/feed?channel=nostr') ? 'bg-gray-700' : ''}`}>
|
||||
<p className="pl-3 rounded-md font-bold"><i className="pi pi-hashtag text-sm"></i> nostr</p>
|
||||
</div>
|
||||
<div onClick={() => router.push('/feed?channel=discord')} className={`w-full cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/feed?channel=discord') ? 'bg-gray-700' : ''}`}>
|
||||
<p className="pl-3 rounded-md font-bold"><i className="pi pi-hashtag text-sm"></i> discord</p>
|
||||
</div>
|
||||
<div onClick={() => router.push('/feed?channel=stackernews')} className={`w-full cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/feed?channel=stackernews') ? 'bg-gray-700' : ''}`}>
|
||||
<p className="pl-3 rounded-md font-bold"><i className="pi pi-hashtag text-sm"></i> stackernews</p>
|
||||
</div>
|
||||
</AccordionTab>
|
||||
</Accordion>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -45,7 +45,7 @@ export default function SignIn() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-[100vw] mx-auto mt-24 flex flex-col justify-center">
|
||||
<div className="w-[100vw] min-bottom-bar:w-[83vw] mx-auto mt-24 flex flex-col justify-center">
|
||||
<h1 className="text-center mb-8">Sign In</h1>
|
||||
<Button
|
||||
label={"login with nostr"}
|
||||
|
@ -18,7 +18,6 @@ const Feed = () => {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [title, setTitle] = useState('Community');
|
||||
const allTopics = ['global', 'nostr', 'discord', 'stackernews'];
|
||||
const [isMessageInputCollapsed, setIsMessageInputCollapsed] = useState(true);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
@ -51,10 +50,6 @@ const Feed = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const toggleMessageInput = (e) => {
|
||||
setIsMessageInputCollapsed(e.value);
|
||||
};
|
||||
|
||||
const handleMessageSent = () => {
|
||||
setIsMessageInputCollapsed(true);
|
||||
};
|
||||
@ -85,16 +80,10 @@ const Feed = () => {
|
||||
icon="pi pi-search"
|
||||
className="w-fit"
|
||||
/>
|
||||
<Button
|
||||
className='text-[#f8f8ff]'
|
||||
icon={isMessageInputCollapsed ? "pi pi-plus" : "pi pi-minus"}
|
||||
onClick={() => setIsMessageInputCollapsed(!isMessageInputCollapsed)}
|
||||
/>
|
||||
</div>
|
||||
<Divider />
|
||||
<MessageInput
|
||||
collapsed={isMessageInputCollapsed}
|
||||
onToggle={toggleMessageInput}
|
||||
collapsed={false}
|
||||
onMessageSent={handleMessageSent}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,130 +1,77 @@
|
||||
import React, { useRef, useState, useEffect } from "react";
|
||||
import { Button } from "primereact/button";
|
||||
import { DataTable } from "primereact/datatable";
|
||||
import { Menu } from "primereact/menu";
|
||||
import { Column } from "primereact/column";
|
||||
import { Message } from "primereact/message";
|
||||
import { Card } from "primereact/card";
|
||||
import { useImageProxy } from "@/hooks/useImageProxy";
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { ProgressSpinner } from "primereact/progressspinner";
|
||||
import PurchasedListItem from "@/components/profile/PurchasedListItem";
|
||||
import { useNDKContext } from "@/context/NDKContext";
|
||||
import { formatDateTime } from "@/utils/time";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { TabView, TabPanel } from "primereact/tabview";
|
||||
import UserProfile from "@/components/profile/UserProfile";
|
||||
import UserSettings from "@/components/profile/UserSettings";
|
||||
import UserContent from "@/components/profile/UserContent";
|
||||
import Image from "next/image";
|
||||
import BitcoinConnectButton from "@/components/bitcoinConnect/BitcoinConnect";
|
||||
import UserSubscription from "@/components/profile/subscription/UserSubscription";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
const Profile = () => {
|
||||
const [user, setUser] = useState(null);
|
||||
const { data: session } = useSession();
|
||||
const { returnImageProxy } = useImageProxy();
|
||||
const { ndk } = useNDKContext();
|
||||
const menu = useRef(null);
|
||||
const router = useRouter();
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
|
||||
const tabs = ["profile", "settings", "content", "subscribe"];
|
||||
|
||||
useEffect(() => {
|
||||
if (session?.user) {
|
||||
setUser(session.user);
|
||||
const { tab } = router.query;
|
||||
if (tab) {
|
||||
const index = tabs.indexOf(tab.toLowerCase());
|
||||
if (index !== -1) {
|
||||
setActiveTab(index);
|
||||
}
|
||||
}
|
||||
}, [session]);
|
||||
}, [router.query]);
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
label: "Edit",
|
||||
icon: "pi pi-pencil",
|
||||
command: () => {
|
||||
// Add your edit functionality here
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Delete",
|
||||
icon: "pi pi-trash",
|
||||
command: () => {
|
||||
// Add your delete functionality here
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const header = (
|
||||
<div className="flex flex-wrap align-items-center justify-content-between gap-2">
|
||||
<span className="text-xl text-900 font-bold text-white">Purchases</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const openSubscribeModal = () => {
|
||||
setSubscribeModalVisible(true);
|
||||
const onTabChange = (e) => {
|
||||
const newIndex = e.index;
|
||||
setActiveTab(newIndex);
|
||||
router.push(`/profile?tab=${tabs[newIndex]}`, undefined, { shallow: true });
|
||||
};
|
||||
|
||||
const subscriptionCardTitle = (
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span className="text-xl text-900 font-bold text-white">Plebdevs Subscription</span>
|
||||
<i
|
||||
className="pi pi-ellipsis-h text-2xlcursor-pointer hover:opacity-75"
|
||||
onClick={(e) => menu.current.toggle(e)}
|
||||
></i>
|
||||
<Menu model={menuItems} popup ref={menu} />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
user && (
|
||||
<div className="h-full w-full min-bottom-bar:w-[87vw] max-sidebar:w-[100vw] mx-auto">
|
||||
<div className="w-full flex flex-col justify-center mx-auto">
|
||||
<div className="relative flex w-full items-center justify-center">
|
||||
<Image
|
||||
alt="user's avatar"
|
||||
src={returnImageProxy(user.avatar, user.pubkey)}
|
||||
width={100}
|
||||
height={100}
|
||||
className="rounded-full my-4"
|
||||
/>
|
||||
<i
|
||||
className="pi pi-ellipsis-h absolute right-24 text-2xl my-4 cursor-pointer hover:opacity-75"
|
||||
onClick={(e) => menu.current.toggle(e)}
|
||||
></i>
|
||||
<Menu model={menuItems} popup ref={menu} />
|
||||
</div>
|
||||
|
||||
<h1 className="text-center text-2xl my-2">
|
||||
{user.username || "Anon"}
|
||||
</h1>
|
||||
<h2 className="text-center text-xl my-2 truncate max-tab:px-4 max-mob:px-4">
|
||||
{user.pubkey}
|
||||
</h2>
|
||||
<div className="flex flex-col w-1/2 mx-auto my-4 justify-between items-center">
|
||||
<h2 className="text-xl my-2">Connect Your Lightning Wallet</h2>
|
||||
<BitcoinConnectButton />
|
||||
</div>
|
||||
{user && (
|
||||
<UserSubscription user={user} />
|
||||
)}
|
||||
</div>
|
||||
{!session || !session?.user || !ndk ? (
|
||||
<ProgressSpinner />
|
||||
) : (
|
||||
<DataTable
|
||||
emptyMessage="No purchases"
|
||||
value={session.user?.purchased}
|
||||
tableStyle={{ minWidth: "100%" }}
|
||||
header={header}
|
||||
>
|
||||
<Column field="amountPaid" header="Cost"></Column>
|
||||
<Column
|
||||
body={(rowData) => {
|
||||
console.log("rowData", rowData);
|
||||
return <PurchasedListItem eventId={rowData?.resource?.noteId || rowData?.course?.noteId} category={rowData?.course ? "courses" : "resources"} />
|
||||
}}
|
||||
header="Name"
|
||||
></Column>
|
||||
<Column body={session.user?.purchased?.some((item) => item.courseId) ? "course" : "resource"} header="Category"></Column>
|
||||
<Column body={rowData => formatDateTime(rowData?.createdAt)} header="Date"></Column>
|
||||
</DataTable>
|
||||
|
||||
)}
|
||||
<UserContent />
|
||||
</div>
|
||||
)
|
||||
<div className="w-full min-h-full min-bottom-bar:w-[87vw] mx-auto">
|
||||
<TabView
|
||||
pt={{
|
||||
root: {
|
||||
className: "bg-transparent",
|
||||
},
|
||||
panelContainer: {
|
||||
className: "bg-transparent m-0 p-0"
|
||||
}
|
||||
}}
|
||||
onTabChange={onTabChange}
|
||||
activeIndex={activeTab}
|
||||
>
|
||||
<TabPanel header="Profile" pt={{
|
||||
headerAction: {
|
||||
className: "bg-transparent"
|
||||
},
|
||||
}}>
|
||||
<UserProfile />
|
||||
</TabPanel>
|
||||
<TabPanel header="Settings" pt={{
|
||||
headerAction: {
|
||||
className: "bg-transparent"
|
||||
},
|
||||
}}>
|
||||
<UserSettings />
|
||||
</TabPanel>
|
||||
<TabPanel header="Content" pt={{
|
||||
headerAction: {
|
||||
className: "bg-transparent"
|
||||
},
|
||||
}}>
|
||||
<UserContent />
|
||||
</TabPanel>
|
||||
<TabPanel header="Subscribe" pt={{
|
||||
headerAction: {
|
||||
className: "bg-transparent"
|
||||
},
|
||||
}}>
|
||||
<UserSubscription />
|
||||
</TabPanel>
|
||||
</TabView>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user