mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-04-22 16:51:34 +00:00
custom nip05 and lightning address creation, updates, and deletes fully working. Still need to add to lnurlp endpoints
This commit is contained in:
parent
3a18a41404
commit
d13ddfcaa9
@ -8,27 +8,29 @@ generator client {
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
pubkey String? @unique
|
||||
privkey String?
|
||||
name String?
|
||||
email String? @unique
|
||||
emailVerified DateTime?
|
||||
image String?
|
||||
username String? @unique
|
||||
avatar String?
|
||||
purchased Purchase[]
|
||||
courses Course[]
|
||||
resources Resource[]
|
||||
courseDrafts CourseDraft[]
|
||||
drafts Draft[]
|
||||
role Role?
|
||||
accounts Account[]
|
||||
sessions Session[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
userLessons UserLesson[]
|
||||
userCourses UserCourse[]
|
||||
id String @id @default(uuid())
|
||||
pubkey String? @unique
|
||||
privkey String?
|
||||
name String?
|
||||
email String? @unique
|
||||
emailVerified DateTime?
|
||||
image String?
|
||||
username String? @unique
|
||||
avatar String?
|
||||
purchased Purchase[]
|
||||
courses Course[]
|
||||
resources Resource[]
|
||||
courseDrafts CourseDraft[]
|
||||
drafts Draft[]
|
||||
role Role?
|
||||
accounts Account[]
|
||||
sessions Session[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
userLessons UserLesson[]
|
||||
userCourses UserCourse[]
|
||||
nip05 Nip05?
|
||||
lightningAddress LightningAddress?
|
||||
}
|
||||
|
||||
model Session {
|
||||
@ -69,32 +71,31 @@ model Account {
|
||||
}
|
||||
|
||||
model Role {
|
||||
id String @id @default(uuid())
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId String @unique
|
||||
subscribed Boolean @default(false)
|
||||
admin Boolean @default(false)
|
||||
id String @id @default(uuid())
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId String @unique
|
||||
subscribed Boolean @default(false)
|
||||
admin Boolean @default(false)
|
||||
subscriptionStartDate DateTime?
|
||||
lastPaymentAt DateTime?
|
||||
subscriptionExpiredAt DateTime?
|
||||
nwc String?
|
||||
nwc String?
|
||||
}
|
||||
|
||||
model Resource {
|
||||
id String @id // Client generates UUID
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
lessons Lesson[]
|
||||
id String @id // Client generates UUID
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
lessons Lesson[]
|
||||
draftLessons DraftLesson[]
|
||||
price Int @default(0)
|
||||
purchases Purchase[]
|
||||
noteId String? @unique
|
||||
videoId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
price Int @default(0)
|
||||
purchases Purchase[]
|
||||
noteId String? @unique
|
||||
videoId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
|
||||
model Draft {
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
@ -114,15 +115,15 @@ model Draft {
|
||||
}
|
||||
|
||||
model Course {
|
||||
id String @id
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
price Int @default(0)
|
||||
lessons Lesson[]
|
||||
purchases Purchase[]
|
||||
noteId String? @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
id String @id
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
price Int @default(0)
|
||||
lessons Lesson[]
|
||||
purchases Purchase[]
|
||||
noteId String? @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
userCourses UserCourse[]
|
||||
}
|
||||
|
||||
@ -141,16 +142,16 @@ model CourseDraft {
|
||||
}
|
||||
|
||||
model Lesson {
|
||||
id String @id @default(uuid())
|
||||
courseId String?
|
||||
course Course? @relation(fields: [courseId], references: [id])
|
||||
resourceId String?
|
||||
resource Resource? @relation(fields: [resourceId], references: [id])
|
||||
draftId String?
|
||||
draft Draft? @relation(fields: [draftId], references: [id])
|
||||
index Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
id String @id @default(uuid())
|
||||
courseId String?
|
||||
course Course? @relation(fields: [courseId], references: [id])
|
||||
resourceId String?
|
||||
resource Resource? @relation(fields: [resourceId], references: [id])
|
||||
draftId String?
|
||||
draft Draft? @relation(fields: [draftId], references: [id])
|
||||
index Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
userLessons UserLesson[]
|
||||
}
|
||||
|
||||
@ -168,48 +169,74 @@ model DraftLesson {
|
||||
}
|
||||
|
||||
model UserLesson {
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
lessonId String
|
||||
lesson Lesson @relation(fields: [lessonId], references: [id])
|
||||
opened Boolean @default(false)
|
||||
completed Boolean @default(false)
|
||||
openedAt DateTime?
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
lessonId String
|
||||
lesson Lesson @relation(fields: [lessonId], references: [id])
|
||||
opened Boolean @default(false)
|
||||
completed Boolean @default(false)
|
||||
openedAt DateTime?
|
||||
completedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([userId, lessonId])
|
||||
}
|
||||
|
||||
model Purchase {
|
||||
id String @id @default(uuid())
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
courseId String?
|
||||
resourceId String?
|
||||
amountPaid Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
course Course? @relation(fields: [courseId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
course Course? @relation(fields: [courseId], references: [id])
|
||||
resource Resource? @relation(fields: [resourceId], references: [id])
|
||||
|
||||
@@unique([userId, courseId])
|
||||
}
|
||||
|
||||
model UserCourse {
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
courseId String
|
||||
course Course @relation(fields: [courseId], references: [id])
|
||||
started Boolean @default(false)
|
||||
completed Boolean @default(false)
|
||||
startedAt DateTime?
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
courseId String
|
||||
course Course @relation(fields: [courseId], references: [id])
|
||||
started Boolean @default(false)
|
||||
completed Boolean @default(false)
|
||||
startedAt DateTime?
|
||||
completedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([userId, courseId])
|
||||
}
|
||||
}
|
||||
|
||||
model Nip05 {
|
||||
id String @id @default(uuid())
|
||||
userId String @unique
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
pubkey String
|
||||
name String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model LightningAddress {
|
||||
id String @id @default(uuid())
|
||||
userId String @unique
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
name String
|
||||
allowsNostr Boolean @default(true)
|
||||
description String?
|
||||
// todo: change to BigInt to support native millisats
|
||||
maxSendable Int @default(10000000)
|
||||
minSendable Int @default(1)
|
||||
invoiceMacaroon String
|
||||
lndCert String?
|
||||
lndHost String
|
||||
lndPort String @default("8080")
|
||||
}
|
||||
|
@ -94,6 +94,12 @@ const UserProfile = () => {
|
||||
<Tooltip target=".pubkey-tooltip" content={"this is your nostr npub"} />
|
||||
{nip19.npubEncode(user.pubkey)} <i className="pi pi-question-circle text-xl pubkey-tooltip" />
|
||||
</h2>
|
||||
<h3 className="text-center text-xl my-2">
|
||||
<span className="font-bold">Lightning Address:</span> {user.lightningAddress.name}@plebdevs.com
|
||||
</h3>
|
||||
<h3 className="text-center text-xl my-2">
|
||||
<span className="font-bold">NIP-05:</span> {user.nip05.name}@plebdevs.com
|
||||
</h3>
|
||||
{/* <GithubContributionChart username={"austinkelsay"} /> */}
|
||||
<GithubContributionChartDisabled username={"austinkelsay"} />
|
||||
<UserProgress />
|
||||
|
@ -72,7 +72,7 @@ const UserSettings = () => {
|
||||
useEffect(() => {
|
||||
const intervalId = setInterval(() => {
|
||||
setUpdateTrigger(prev => prev + 1);
|
||||
}, 3000); // Poll every 3 seconds
|
||||
}, 7000); // Poll every 7 seconds
|
||||
|
||||
return () => clearInterval(intervalId); // Cleanup on unmount
|
||||
}, []);
|
||||
@ -181,6 +181,12 @@ const UserSettings = () => {
|
||||
<Tooltip target=".pubkey-tooltip" content={"this is your nostr npub"} />
|
||||
{nip19.npubEncode(user.pubkey)} <i className="pi pi-question-circle text-xl pubkey-tooltip" />
|
||||
</h2>
|
||||
<h3 className="text-center text-xl my-2">
|
||||
<span className="font-bold">Lightning Address:</span> {user.lightningAddress.name}@plebdevs.com
|
||||
</h3>
|
||||
<h3 className="text-center text-xl my-2">
|
||||
<span className="font-bold">NIP-05:</span> {user.nip05.name}@plebdevs.com
|
||||
</h3>
|
||||
<div className="flex flex-col w-1/2 mx-auto justify-between items-center">
|
||||
<h2 className="text-xl my-2 max-mob:text-base max-tab:text-base">Connect Your Lightning Wallet</h2>
|
||||
<BitcoinConnectButton />
|
||||
|
127
src/components/profile/subscription/LightningAddressForm.js
Normal file
127
src/components/profile/subscription/LightningAddressForm.js
Normal file
@ -0,0 +1,127 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import axios from 'axios';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { InputText } from 'primereact/inputtext';
|
||||
import { ProgressSpinner } from 'primereact/progressspinner';
|
||||
import GenericButton from '@/components/buttons/GenericButton';
|
||||
|
||||
const LightningAddressForm = ({ visible, onHide }) => {
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [existingLightningAddress, setExistingLightningAddress] = useState(null);
|
||||
const [name, setName] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [maxSendable, setMaxSendable] = useState(10000000);
|
||||
const [minSendable, setMinSendable] = useState(1);
|
||||
const [invoiceMacaroon, setInvoiceMacaroon] = useState('');
|
||||
const [lndCert, setLndCert] = useState('');
|
||||
const [lndHost, setLndHost] = useState('');
|
||||
const [lndPort, setLndPort] = useState('8080');
|
||||
|
||||
const { data: session, update } = useSession();
|
||||
const { showToast } = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
if (session && session?.user && !session?.user?.lightningAddress) {
|
||||
setName(session.user.name || '');
|
||||
} else if (session && session?.user && session?.user?.lightningAddress) {
|
||||
setExistingLightningAddress(session.user.lightningAddress);
|
||||
setName(session.user.lightningAddress.name || '');
|
||||
setDescription(session.user.lightningAddress.description || '');
|
||||
setMaxSendable(session.user.lightningAddress.maxSendable || 10000000);
|
||||
setMinSendable(session.user.lightningAddress.minSendable || 1);
|
||||
setInvoiceMacaroon(session.user.lightningAddress.invoiceMacaroon || '');
|
||||
setLndCert(session.user.lightningAddress.lndCert || '');
|
||||
setLndHost(session.user.lightningAddress.lndHost || '');
|
||||
setLndPort(session.user.lightningAddress.lndPort || '8080');
|
||||
}
|
||||
}, [session]);
|
||||
|
||||
const handleLightningAddress = async () => {
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
let response;
|
||||
const lowercaseName = name.toLowerCase();
|
||||
if (existingLightningAddress) {
|
||||
response = await axios.put(`/api/users/${session.user.id}/lightning-address`, { name: lowercaseName, description, maxSendable, minSendable, invoiceMacaroon, lndCert, lndHost, lndPort });
|
||||
} else {
|
||||
response = await axios.post(`/api/users/${session.user.id}/lightning-address`, { name: lowercaseName, description, maxSendable, minSendable, invoiceMacaroon, lndCert, lndHost, lndPort });
|
||||
}
|
||||
if (!existingLightningAddress && response.status === 201) {
|
||||
showToast('success', 'Lightning Address Claimed', 'Your Lightning Address has been claimed');
|
||||
update();
|
||||
onHide();
|
||||
} else if (existingLightningAddress && response.status === 200) {
|
||||
showToast('success', 'Lightning Address updated', 'Your Lightning Address has been updated');
|
||||
update();
|
||||
onHide();
|
||||
} else {
|
||||
showToast('error', 'Error updating Lightning Address', response.data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error claiming Lightning Address:', error);
|
||||
showToast('error', 'Error claiming Lightning Address', error.message);
|
||||
setIsProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteLightningAddress = async () => {
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
const response = await axios.delete(`/api/users/${session.user.id}/lightning-address`);
|
||||
if (response.status === 204) {
|
||||
showToast('success', 'Lightning Address deleted', 'Your Lightning Address has been deleted');
|
||||
update();
|
||||
onHide();
|
||||
} else {
|
||||
showToast('error', 'Error deleting Lightning Address', response.data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting Lightning Address:', error);
|
||||
showToast('error', 'Error deleting Lightning Address', error.message);
|
||||
setIsProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog header="Lightning Address" visible={visible} onHide={onHide}>
|
||||
{existingLightningAddress ? (
|
||||
<p>Update your Lightning Address details</p>
|
||||
) : (
|
||||
<p>Confirm your Lightning Address details</p>
|
||||
)}
|
||||
<div className="flex flex-col gap-2 max-mob:min-w-[80vw] max-tab:min-w-[60vw] min-w-[40vw]">
|
||||
<label>Name</label>
|
||||
<InputText placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} />
|
||||
<label>Description</label>
|
||||
<InputText placeholder="Description" value={description} onChange={(e) => setDescription(e.target.value)} />
|
||||
<label>Max Sendable</label>
|
||||
<InputText placeholder="Max Sendable" value={maxSendable} onChange={(e) => setMaxSendable(e.target.value)} />
|
||||
<label>Min Sendable</label>
|
||||
<InputText placeholder="Min Sendable" value={minSendable} onChange={(e) => setMinSendable(e.target.value)} />
|
||||
<label>Invoice Macaroon</label>
|
||||
<InputText placeholder="Invoice Macaroon" value={invoiceMacaroon} onChange={(e) => setInvoiceMacaroon(e.target.value)} />
|
||||
<label>LND Cert</label>
|
||||
<InputText placeholder="LND Cert" value={lndCert} onChange={(e) => setLndCert(e.target.value)} />
|
||||
<label>LND Host</label>
|
||||
<InputText placeholder="LND Host" value={lndHost} onChange={(e) => setLndHost(e.target.value)} />
|
||||
<label>LND Port</label>
|
||||
<InputText placeholder="LND Port" value={lndPort} onChange={(e) => setLndPort(e.target.value)} />
|
||||
</div>
|
||||
{!existingLightningAddress && (
|
||||
<div className="flex flex-row justify-center mt-6">
|
||||
{isProcessing ? <ProgressSpinner /> : <GenericButton severity="success" outlined className="mx-auto" label='Confirm' onClick={handleLightningAddress} />}
|
||||
</div>
|
||||
)}
|
||||
{existingLightningAddress && (
|
||||
<div className="flex flex-row justify-center w-full mt-6 gap-4">
|
||||
<GenericButton severity="success" outlined className="mx-auto" label="Update" onClick={handleLightningAddress} />
|
||||
<GenericButton severity="danger" outlined className="mx-auto" label="Delete" onClick={deleteLightningAddress} />
|
||||
</div>
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default LightningAddressForm;
|
102
src/components/profile/subscription/Nip05Form.js
Normal file
102
src/components/profile/subscription/Nip05Form.js
Normal file
@ -0,0 +1,102 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import axios from 'axios';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { InputText } from 'primereact/inputtext';
|
||||
import { ProgressSpinner } from 'primereact/progressspinner';
|
||||
import GenericButton from '@/components/buttons/GenericButton';
|
||||
|
||||
const Nip05Form = ({ visible, onHide }) => {
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [existingNip05, setExistingNip05] = useState(null);
|
||||
const [pubkey, setPubkey] = useState('');
|
||||
const [name, setName] = useState('');
|
||||
|
||||
const { data: session, update } = useSession();
|
||||
const { showToast } = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
if (session && session?.user && !session?.user?.nip05) {
|
||||
setPubkey(session.user.pubkey || '');
|
||||
setName(session.user.name || '');
|
||||
} else if (session && session?.user && session?.user?.nip05) {
|
||||
setExistingNip05(session.user.nip05);
|
||||
setPubkey(session.user.nip05.pubkey || '');
|
||||
setName(session.user.nip05.name || '');
|
||||
}
|
||||
}, [session]);
|
||||
|
||||
const handleNip05 = async () => {
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
let response;
|
||||
const lowercaseName = name.toLowerCase();
|
||||
if (existingNip05) {
|
||||
response = await axios.put(`/api/users/${session.user.id}/nip05`, { pubkey, name: lowercaseName });
|
||||
} else {
|
||||
response = await axios.post(`/api/users/${session.user.id}/nip05`, { pubkey, name: lowercaseName });
|
||||
}
|
||||
if (existingNip05 && response.status === 201) {
|
||||
showToast('success', 'NIP-05 Claimed', 'Your NIP-05 has been claimed');
|
||||
update();
|
||||
onHide();
|
||||
} else if (response.status === 200) {
|
||||
showToast('success', 'NIP-05 updated', 'Your NIP-05 has been updated');
|
||||
update();
|
||||
onHide();
|
||||
} else {
|
||||
showToast('error', 'Error updating NIP-05', response.data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error claiming NIP-05:', error);
|
||||
showToast('error', 'Error claiming NIP-05', error.message);
|
||||
setIsProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteNip05 = async () => {
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
const response = await axios.delete(`/api/users/${session.user.id}/nip05`);
|
||||
if (response.status === 204) {
|
||||
showToast('success', 'NIP-05 deleted', 'Your NIP-05 has been deleted');
|
||||
update();
|
||||
onHide();
|
||||
} else {
|
||||
showToast('error', 'Error deleting NIP-05', response.data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting NIP-05:', error);
|
||||
showToast('error', 'Error deleting NIP-05', error.message);
|
||||
setIsProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog header="NIP-05" visible={visible} onHide={onHide}>
|
||||
{existingNip05 ? (
|
||||
<p>Update your Pubkey and Name</p>
|
||||
) : (
|
||||
<p>Confirm your Pubkey and Name</p>
|
||||
)}
|
||||
<div className="flex flex-col gap-2 max-mob:min-w-[80vw] max-tab:min-w-[60vw] min-w-[40vw]">
|
||||
<InputText placeholder="Pubkey" value={pubkey} onChange={(e) => setPubkey(e.target.value)} />
|
||||
<InputText placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} />
|
||||
</div>
|
||||
{!existingNip05 && (
|
||||
<div className="flex flex-row justify-center mt-6">
|
||||
{isProcessing ? <ProgressSpinner /> : <GenericButton severity="success" outlined className="mx-auto" label='Confirm' onClick={handleNip05} />}
|
||||
</div>
|
||||
)}
|
||||
{existingNip05 && (
|
||||
<div className="flex flex-row justify-center w-full mt-6 gap-4">
|
||||
<GenericButton severity="success" outlined className="mx-auto" label="Update" onClick={handleNip05} />
|
||||
<GenericButton severity="danger" outlined className="mx-auto" label="Delete" onClick={deleteNip05} />
|
||||
</div>
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default Nip05Form;
|
@ -13,6 +13,8 @@ import { Menu } from "primereact/menu";
|
||||
import { Message } from "primereact/message";
|
||||
import CancelSubscription from '@/components/profile/subscription/CancelSubscription';
|
||||
import CalendlyEmbed from '@/components/profile/subscription/CalendlyEmbed';
|
||||
import Nip05Form from '@/components/profile/subscription/Nip05Form';
|
||||
import LightningAddressForm from '@/components/profile/subscription/LightningAddressForm';
|
||||
import NostrIcon from '../../../../public/images/nostr.png';
|
||||
import Image from 'next/image';
|
||||
import RenewSubscription from '@/components/profile/subscription/RenewSubscription';
|
||||
@ -27,6 +29,8 @@ const SubscribeModal = ({ user }) => {
|
||||
const [subscribedUntil, setSubscribedUntil] = useState(null);
|
||||
const [subscriptionExpiredAt, setSubscriptionExpiredAt] = useState(null);
|
||||
const [calendlyVisible, setCalendlyVisible] = useState(false);
|
||||
const [lightningAddressVisible, setLightningAddressVisible] = useState(false);
|
||||
const [nip05Visible, setNip05Visible] = useState(false);
|
||||
const menu = useRef(null);
|
||||
const [cancelSubscriptionVisible, setCancelSubscriptionVisible] = useState(false);
|
||||
const [renewSubscriptionVisible, setRenewSubscriptionVisible] = useState(false);
|
||||
@ -92,13 +96,6 @@ const SubscribeModal = ({ user }) => {
|
||||
}
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
label: "Renew Subscription",
|
||||
icon: "pi pi-bolt",
|
||||
command: () => {
|
||||
setRenewSubscriptionVisible(true);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Schedule 1:1",
|
||||
icon: "pi pi-calendar",
|
||||
@ -106,6 +103,27 @@ const SubscribeModal = ({ user }) => {
|
||||
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",
|
||||
@ -121,10 +139,10 @@ const SubscribeModal = ({ user }) => {
|
||||
{subscribed && (
|
||||
<i
|
||||
className="pi pi-ellipsis-h text-2xl cursor-pointer hover:opacity-75"
|
||||
onClick={(e) => menu.current.toggle(e)}
|
||||
onClick={(e) => menu.current.toggle(e)}
|
||||
></i>
|
||||
)}
|
||||
<Menu model={menuItems} popup ref={menu} />
|
||||
<Menu model={menuItems} popup ref={menu} className="w-fit" />
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -190,7 +208,7 @@ const SubscribeModal = ({ user }) => {
|
||||
<span>Claim your own personal plebdevs.com Lightning Address</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Image src={NostrIcon} alt="Nostr" width={26 } height={26} className='mr-2' />
|
||||
<Image src={NostrIcon} alt="Nostr" width={26} height={26} className='mr-2' />
|
||||
<span>Claim your own personal plebdevs.com Nostr NIP-05 identity</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -207,12 +225,10 @@ const SubscribeModal = ({ user }) => {
|
||||
</Card>
|
||||
)}
|
||||
</Dialog>
|
||||
{calendlyVisible && (
|
||||
<CalendlyEmbed
|
||||
visible={calendlyVisible}
|
||||
onHide={() => setCalendlyVisible(false)}
|
||||
/>
|
||||
)}
|
||||
<CalendlyEmbed
|
||||
visible={calendlyVisible}
|
||||
onHide={() => setCalendlyVisible(false)}
|
||||
/>
|
||||
<CancelSubscription
|
||||
visible={cancelSubscriptionVisible}
|
||||
onHide={() => setCancelSubscriptionVisible(false)}
|
||||
@ -222,6 +238,14 @@ const SubscribeModal = ({ user }) => {
|
||||
onHide={() => setRenewSubscriptionVisible(false)}
|
||||
subscribedUntil={subscribedUntil}
|
||||
/>
|
||||
<Nip05Form
|
||||
visible={nip05Visible}
|
||||
onHide={() => setNip05Visible(false)}
|
||||
/>
|
||||
<LightningAddressForm
|
||||
visible={lightningAddressVisible}
|
||||
onHide={() => setLightningAddressVisible(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
51
src/db/models/lightningAddressModels.js
Normal file
51
src/db/models/lightningAddressModels.js
Normal file
@ -0,0 +1,51 @@
|
||||
import prisma from "@/db/prisma";
|
||||
|
||||
export const getAllLightningAddresses = async () => {
|
||||
return await prisma.lightningAddress.findMany();
|
||||
};
|
||||
|
||||
export const getLightningAddressByName = async (name) => {
|
||||
return await prisma.lightningAddress.findFirst({
|
||||
where: { name },
|
||||
});
|
||||
};
|
||||
|
||||
export const getLightningAddress = async (userId) => {
|
||||
return await prisma.lightningAddress.findUnique({
|
||||
where: { userId },
|
||||
});
|
||||
};
|
||||
|
||||
export const createLightningAddress = async (userId, name, description, maxSendable, minSendable, invoiceMacaroon, lndCert, lndHost, lndPort) => {
|
||||
try {
|
||||
return await prisma.lightningAddress.create({
|
||||
data: {
|
||||
userId,
|
||||
name,
|
||||
description,
|
||||
maxSendable: maxSendable,
|
||||
minSendable: minSendable,
|
||||
invoiceMacaroon,
|
||||
lndCert,
|
||||
lndHost,
|
||||
lndPort
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in createLightningAddress:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const updateLightningAddress = async (userId, data) => {
|
||||
return await prisma.lightningAddress.update({
|
||||
where: { userId },
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteLightningAddress = async (userId) => {
|
||||
return await prisma.lightningAddress.delete({
|
||||
where: { userId },
|
||||
});
|
||||
};
|
37
src/db/models/nip05Models.js
Normal file
37
src/db/models/nip05Models.js
Normal file
@ -0,0 +1,37 @@
|
||||
import prisma from "@/db/prisma";
|
||||
|
||||
export const getAllNip05s = async () => {
|
||||
return await prisma.nip05.findMany();
|
||||
};
|
||||
|
||||
export const getNip05ByName = async (name) => {
|
||||
return await prisma.nip05.findFirst({
|
||||
where: { name },
|
||||
});
|
||||
};
|
||||
|
||||
export const getNip05 = async (userId) => {
|
||||
return await prisma.nip05.findUnique({
|
||||
where: { userId },
|
||||
});
|
||||
};
|
||||
|
||||
export const createNip05 = async (userId, pubkey, name) => {
|
||||
return await prisma.nip05.create({
|
||||
data: { userId, pubkey, name },
|
||||
});
|
||||
};
|
||||
|
||||
export const updateNip05 = async (userId, data) => {
|
||||
return await prisma.nip05.update({
|
||||
where: { userId },
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteNip05 = async (userId) => {
|
||||
return await prisma.nip05.delete({
|
||||
where: { userId },
|
||||
});
|
||||
};
|
||||
|
@ -49,6 +49,8 @@ export const getUserById = async (id) => {
|
||||
lesson: true,
|
||||
},
|
||||
},
|
||||
nip05: true,
|
||||
lightningAddress: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -74,6 +76,8 @@ export const getUserByPubkey = async (pubkey) => {
|
||||
lesson: true,
|
||||
},
|
||||
},
|
||||
nip05: true,
|
||||
lightningAddress: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -1,10 +1,20 @@
|
||||
const nostrData = {
|
||||
names: {
|
||||
plebdevs:
|
||||
"f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741",
|
||||
},
|
||||
};
|
||||
import { getNip05ByName } from "@/db/models/nip05Models";
|
||||
|
||||
export default async function Nip05(req, res) {
|
||||
return res.status(200).json(nostrData);
|
||||
}
|
||||
const name = req.query.name;
|
||||
if (!name) {
|
||||
return res.status(400).json({ error: "Name is required" });
|
||||
}
|
||||
|
||||
const nip05 = await getNip05ByName(name);
|
||||
|
||||
if (!nip05) {
|
||||
return res.status(404).json({ error: "NIP-05 not found" });
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
names: {
|
||||
[nip05.name.toLowerCase()]: nip05.pubkey,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
56
src/pages/api/users/[slug]/lightning-address.js
Normal file
56
src/pages/api/users/[slug]/lightning-address.js
Normal file
@ -0,0 +1,56 @@
|
||||
import { getLightningAddress, createLightningAddress, updateLightningAddress, deleteLightningAddress } from "@/db/models/lightningAddressModels"
|
||||
|
||||
export default async function handler(req, res) {
|
||||
const { slug } = req.query;
|
||||
const userId = slug;
|
||||
|
||||
switch (req.method) {
|
||||
case 'GET':
|
||||
try {
|
||||
const lightningAddress = await getLightningAddress(userId);
|
||||
if (lightningAddress) {
|
||||
res.status(200).json(lightningAddress);
|
||||
} else {
|
||||
res.status(404).json({ error: 'Lightning Address not found' });
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Error fetching Lightning Address' });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
try {
|
||||
const { name, description, maxSendable, minSendable, invoiceMacaroon, lndCert, lndHost, lndPort } = req.body;
|
||||
const lightningAddress = await createLightningAddress(userId, name, description, maxSendable, minSendable, invoiceMacaroon, lndCert, lndHost, lndPort);
|
||||
|
||||
res.status(201).json(lightningAddress);
|
||||
} catch (error) {
|
||||
console.error('Error creating Lightning Address:', error);
|
||||
res.status(500).json({ error: 'Error creating Lightning Address', errorMessage: error.message });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'PUT':
|
||||
try {
|
||||
const data = req.body;
|
||||
const lightningAddress = await updateLightningAddress(userId, data);
|
||||
res.status(200).json(lightningAddress);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Error updating Lightning Address' });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'DELETE':
|
||||
try {
|
||||
await deleteLightningAddress(userId);
|
||||
res.status(204).end();
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Error deleting Lightning Address' });
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
res.setHeader('Allow', ['GET', 'POST', 'PUT', 'DELETE']);
|
||||
res.status(405).end(`Method ${req.method} Not Allowed`);
|
||||
}
|
||||
}
|
54
src/pages/api/users/[slug]/nip05.js
Normal file
54
src/pages/api/users/[slug]/nip05.js
Normal file
@ -0,0 +1,54 @@
|
||||
import { getNip05, createNip05, updateNip05, deleteNip05 } from '@/db/models/nip05Models';
|
||||
|
||||
export default async function handler(req, res) {
|
||||
const { slug } = req.query;
|
||||
const userId = slug;
|
||||
|
||||
switch (req.method) {
|
||||
case 'GET':
|
||||
try {
|
||||
const nip05 = await getNip05(userId);
|
||||
if (nip05) {
|
||||
res.status(200).json(nip05);
|
||||
} else {
|
||||
res.status(404).json({ error: 'NIP-05 not found' });
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Error fetching NIP-05' });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
try {
|
||||
const { pubkey, name } = req.body;
|
||||
const nip05 = await createNip05(userId, pubkey, name.toLowerCase());
|
||||
res.status(201).json(nip05);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Error creating NIP-05' });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'PUT':
|
||||
try {
|
||||
const { pubkey, name } = req.body;
|
||||
const nip05 = await updateNip05(userId, { pubkey, name: name.toLowerCase() });
|
||||
res.status(200).json(nip05);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Error updating NIP-05' });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'DELETE':
|
||||
try {
|
||||
await deleteNip05(userId);
|
||||
res.status(204).end();
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Error deleting NIP-05' });
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
res.setHeader('Allow', ['GET', 'POST', 'PUT', 'DELETE']);
|
||||
res.status(405).end(`Method ${req.method} Not Allowed`);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user