mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-04-19 19:01:19 +00:00
Added zap amounts onto course template, implemented bitcoin connect connect button
This commit is contained in:
parent
8a5d90fd53
commit
fabf714cdc
157
package-lock.json
generated
157
package-lock.json
generated
@ -8,10 +8,12 @@
|
||||
"name": "plebdevs-new",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@getalby/bitcoin-connect-react": "^3.2.2",
|
||||
"@prisma/client": "^5.9.1",
|
||||
"@reduxjs/toolkit": "^2.1.0",
|
||||
"axios": "^1.6.7",
|
||||
"classnames": "^2.5.1",
|
||||
"light-bolt11-decoder": "^3.1.1",
|
||||
"next": "14.0.4",
|
||||
"next-auth": "^4.24.5",
|
||||
"nostr-tools": "^2.1.5",
|
||||
@ -125,6 +127,102 @@
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@getalby/bitcoin-connect": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@getalby/bitcoin-connect/-/bitcoin-connect-3.2.2.tgz",
|
||||
"integrity": "sha512-hqsnTVNojTmLDXhhkJvs1RDFKZIFEQDD4UWNlB/hIV3Bx4INxRZzeOi8SOG7idjGF3ix7I8hpG3zeeJCZWNByQ==",
|
||||
"dependencies": {
|
||||
"@getalby/lightning-tools": "^5.0.1",
|
||||
"@getalby/sdk": "^3.2.3",
|
||||
"@lightninglabs/lnc-web": "^0.3.1-alpha",
|
||||
"qrcode-generator": "^1.4.4",
|
||||
"zustand": "^4.4.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@getalby/bitcoin-connect-react": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@getalby/bitcoin-connect-react/-/bitcoin-connect-react-3.2.2.tgz",
|
||||
"integrity": "sha512-JLJBJgYySr7LDIfycDum/pNdqKxKr81Rw+iidO6CW45N3XGyVLEHLMDZI1ZfwV1i/nsOiqL65BUICGXrBSBnHA==",
|
||||
"dependencies": {
|
||||
"@getalby/bitcoin-connect": "^3.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@getalby/lightning-tools": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@getalby/lightning-tools/-/lightning-tools-5.0.2.tgz",
|
||||
"integrity": "sha512-XS08p4JcUre4L1ROkIGQv2DidasPtFLlFVW6oJlqBi12ClRJnURnAV3h3yCi/r7sFixONjz+2IGwirTZUu5fPA==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "lightning",
|
||||
"url": "lightning:hello@getalby.com"
|
||||
}
|
||||
},
|
||||
"node_modules/@getalby/sdk": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@getalby/sdk/-/sdk-3.4.3.tgz",
|
||||
"integrity": "sha512-K0F8Sj3aGmsBV87jfYbMBCAYbb8d9JrLA5jUYn+LuE59IF1flw4pQSb7irQBJYmiFHtvHA8+bpg38WRJr7hpeg==",
|
||||
"dependencies": {
|
||||
"eventemitter3": "^5.0.1",
|
||||
"nostr-tools": "^1.17.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "lightning",
|
||||
"url": "lightning:hello@getalby.com"
|
||||
}
|
||||
},
|
||||
"node_modules/@getalby/sdk/node_modules/@noble/ciphers": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.2.0.tgz",
|
||||
"integrity": "sha512-6YBxJDAapHSdd3bLDv6x2wRPwq4QFMUaB3HvljNBUTThDd12eSm7/3F+2lnfzx2jvM+S6Nsy0jEt9QbPqSwqRw==",
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@getalby/sdk/node_modules/@noble/curves": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz",
|
||||
"integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.3.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@getalby/sdk/node_modules/eventemitter3": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
|
||||
},
|
||||
"node_modules/@getalby/sdk/node_modules/nostr-tools": {
|
||||
"version": "1.17.0",
|
||||
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-1.17.0.tgz",
|
||||
"integrity": "sha512-LZmR8GEWKZeElbFV5Xte75dOeE9EFUW/QLI1Ncn3JKn0kFddDKEfBbFN8Mu4TMs+L4HR/WTPha2l+PPuRnJcMw==",
|
||||
"dependencies": {
|
||||
"@noble/ciphers": "0.2.0",
|
||||
"@noble/curves": "1.1.0",
|
||||
"@noble/hashes": "1.3.1",
|
||||
"@scure/base": "1.1.1",
|
||||
"@scure/bip32": "1.3.1",
|
||||
"@scure/bip39": "1.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.11.14",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
|
||||
@ -250,6 +348,20 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@lightninglabs/lnc-core": {
|
||||
"version": "0.3.1-alpha",
|
||||
"resolved": "https://registry.npmjs.org/@lightninglabs/lnc-core/-/lnc-core-0.3.1-alpha.tgz",
|
||||
"integrity": "sha512-I/hThdItLWJ6RU8Z27ZIXhpBS2JJuD3+TjtaQXX2CabaUYXlcN4sk+Kx8N/zG/fk8qZvjlRWum4vHu4ZX554Fg=="
|
||||
},
|
||||
"node_modules/@lightninglabs/lnc-web": {
|
||||
"version": "0.3.1-alpha",
|
||||
"resolved": "https://registry.npmjs.org/@lightninglabs/lnc-web/-/lnc-web-0.3.1-alpha.tgz",
|
||||
"integrity": "sha512-yL5SgBkl6kd6ISzJHGlSN7TXbiDoo1pfGvTOIdVWYVyXtEeW8PT+x6YGOmyQXGFT2OOf7fC7PfP9VnskDPuFaA==",
|
||||
"dependencies": {
|
||||
"@lightninglabs/lnc-core": "0.3.1-alpha",
|
||||
"crypto-js": "4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "14.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.4.tgz",
|
||||
@ -1585,6 +1697,11 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/crypto-js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
|
||||
},
|
||||
"node_modules/cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
@ -3683,6 +3800,14 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/light-bolt11-decoder": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/light-bolt11-decoder/-/light-bolt11-decoder-3.1.1.tgz",
|
||||
"integrity": "sha512-sLg/KCwYkgsHWkefWd6KqpCHrLFWWaXTOX3cf6yD2hAzL0SLpX+lFcaFK2spkjbgzG6hhijKfORDc9WoUHwX0A==",
|
||||
"dependencies": {
|
||||
"@scure/base": "1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
|
||||
@ -5233,6 +5358,11 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode-generator": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.4.4.tgz",
|
||||
"integrity": "sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw=="
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
@ -6787,6 +6917,33 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/zustand": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.2.tgz",
|
||||
"integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==",
|
||||
"dependencies": {
|
||||
"use-sync-external-store": "1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.7.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=16.8",
|
||||
"immer": ">=9.0.6",
|
||||
"react": ">=16.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"immer": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/zwitch": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
|
||||
|
@ -9,10 +9,12 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@getalby/bitcoin-connect-react": "^3.2.2",
|
||||
"@prisma/client": "^5.9.1",
|
||||
"@reduxjs/toolkit": "^2.1.0",
|
||||
"axios": "^1.6.7",
|
||||
"classnames": "^2.5.1",
|
||||
"light-bolt11-decoder": "^3.1.1",
|
||||
"next": "14.0.4",
|
||||
"next-auth": "^4.24.5",
|
||||
"nostr-tools": "^2.1.5",
|
||||
|
@ -1,27 +1,42 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import React, {use, useEffect, useState} from "react";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import useResponsiveImageDimensions from "@/hooks/useResponsiveImageDimensions";
|
||||
import { formatTimestampToHowLongAgo } from "@/utils/time";
|
||||
import { useImageProxy } from "@/hooks/useImageProxy";
|
||||
import { useNostr } from "@/hooks/useNostr";
|
||||
import {getSatAmountFromInvoice} from "@/utils/lightning";
|
||||
|
||||
const CourseTemplate = (course) => {
|
||||
const [zaps, setZaps] = useState([]);
|
||||
const [zapAmount, setZapAmount] = useState(null);
|
||||
const router = useRouter();
|
||||
const { returnImageProxy } = useImageProxy();
|
||||
const { width, height } = useResponsiveImageDimensions();
|
||||
const {events, fetchZapsForEvent} = useNostr();
|
||||
|
||||
useEffect(() => {
|
||||
if (events && events.zaps) {
|
||||
console.log('zaps:', events.zaps);
|
||||
if (events && events.zaps.length > 0) {
|
||||
setZaps(events.zaps);
|
||||
} else {
|
||||
fetchZapsForEvent(course.id);
|
||||
}
|
||||
}, [events]);
|
||||
|
||||
useEffect(() => {
|
||||
if (zaps.length > 0) {
|
||||
zaps.map((zap) => {
|
||||
const bolt11Tag = zap.tags.find(tag => tag[0] === 'bolt11');
|
||||
const invoice = bolt11Tag ? bolt11Tag[1] : null;
|
||||
|
||||
if (invoice) {
|
||||
const amount = getSatAmountFromInvoice(invoice);
|
||||
setZapAmount(zapAmount + amount);
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [zaps]);
|
||||
|
||||
return (
|
||||
<div style={{width: width < 768 ? "auto" : width}} onClick={() => router.push(`/details/${course.id}`)} className="flex flex-col items-center mx-auto px-4 cursor-pointer mt-8 rounded-md shadow-lg">
|
||||
<div style={{maxWidth: width, minWidth: width}} className="max-tab:h-auto max-mob:h-auto">
|
||||
@ -46,7 +61,10 @@ const CourseTemplate = (course) => {
|
||||
}}>
|
||||
{course.summary}
|
||||
</p>
|
||||
<p className="text-sm mt-1 text-gray-400">Published: {formatTimestampToHowLongAgo(course.published_at)}</p>
|
||||
<div className="flex flex-row justify-between w-full">
|
||||
<p className="text-sm mt-1 text-gray-400">Published: {formatTimestampToHowLongAgo(course.published_at)}</p>
|
||||
<p className="pr-2"><i className="pi pi-bolt"></i> {zapAmount}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
35
src/components/profile/BitcoinConnect.js
Normal file
35
src/components/profile/BitcoinConnect.js
Normal file
@ -0,0 +1,35 @@
|
||||
"use client";
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const Button = dynamic(
|
||||
() => import('@getalby/bitcoin-connect-react').then((mod) => mod.Button),
|
||||
{
|
||||
ssr: false,
|
||||
}
|
||||
);
|
||||
|
||||
const BitcoinConnectButton = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const initializeBitcoinConnect = async () => {
|
||||
// Initialize Bitcoin Connect
|
||||
const { init } = await import('@getalby/bitcoin-connect-react');
|
||||
init({
|
||||
appName: "PlebDevs",
|
||||
filters: ["nwc"],
|
||||
showBalance: false
|
||||
});
|
||||
};
|
||||
|
||||
initializeBitcoinConnect();
|
||||
}, []); // Empty dependency array to run only once on component mount
|
||||
|
||||
return (
|
||||
<Button onConnect={(provider) => {
|
||||
console.log('provider:', provider);
|
||||
}} />
|
||||
);
|
||||
}
|
||||
|
||||
export default BitcoinConnectButton;
|
@ -54,9 +54,6 @@ export const useNostr = () => {
|
||||
try {
|
||||
const sub = pool.current.subscribeMany(relays, filter, {
|
||||
onevent: async (event) => {
|
||||
if (event.kind === 9735) {
|
||||
console.log('event:', event);
|
||||
}
|
||||
const shouldInclude = await hasRequiredTags(event.tags);
|
||||
if (shouldInclude) {
|
||||
setEvents(prevData => ({
|
||||
@ -149,12 +146,8 @@ export const useNostr = () => {
|
||||
}
|
||||
|
||||
const fetchZapsForEvent = async (eventId) => {
|
||||
const filter = [{ kinds: [9735] }];
|
||||
const hasRequiredTags = async (eventData) => {
|
||||
const hasEtag = eventData.some(([tag, value]) => tag === "e" && value === eventId);
|
||||
return hasEtag;
|
||||
};
|
||||
fetchEvents(filter, 'zaps', hasRequiredTags);
|
||||
const filter = [{ kinds: [9735], "#e": [eventId] }];
|
||||
fetchEvents(filter, 'zaps', () => true);
|
||||
}
|
||||
|
||||
// Fetch resources, workshops, courses, and streams with appropriate filters and update functions
|
||||
|
@ -1,3 +1,4 @@
|
||||
"use client";
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useNostr } from '@/hooks/useNostr';
|
||||
@ -6,6 +7,7 @@ import { useImageProxy } from '@/hooks/useImageProxy';
|
||||
import { Button } from 'primereact/button';
|
||||
import { Tag } from 'primereact/tag';
|
||||
import Image from 'next/image';
|
||||
import dynamic from 'next/dynamic';
|
||||
import 'primeicons/primeicons.css';
|
||||
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
@ -21,10 +23,18 @@ const MarkdownContent = ({ content }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const BitcoinConnectPayButton = dynamic(
|
||||
() => import('@getalby/bitcoin-connect-react').then((mod) => mod.PayButton),
|
||||
{
|
||||
ssr: false,
|
||||
}
|
||||
);
|
||||
|
||||
export default function Details() {
|
||||
const [event, setEvent] = useState(null);
|
||||
const [processedEvent, setProcessedEvent] = useState({});
|
||||
const [author, setAuthor] = useState(null);
|
||||
const [bitcoinConnect, setBitcoinConnect] = useState(false);
|
||||
|
||||
const { returnImageProxy } = useImageProxy();
|
||||
const { fetchSingleEvent, fetchKind0, zapEvent } = useNostr();
|
||||
@ -39,6 +49,16 @@ export default function Details() {
|
||||
console.log('zap response:', response);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const bitcoinConnectConfig = window.localStorage.getItem('bc:config');
|
||||
|
||||
if (bitcoinConnectConfig) {
|
||||
setBitcoinConnect(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (router.isReady) {
|
||||
const { slug } = router.query;
|
||||
@ -115,20 +135,25 @@ export default function Details() {
|
||||
height={194}
|
||||
className="object-cover object-center rounded-lg"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-bolt"
|
||||
label="Zap"
|
||||
severity="success"
|
||||
outlined
|
||||
onClick={handleZapEvent}
|
||||
pt={{
|
||||
button: {
|
||||
icon: ({ context }) => ({
|
||||
className: 'bg-yellow-500'
|
||||
})
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{bitcoinConnect ? (
|
||||
<BitcoinConnectPayButton onClick={handleZapEvent} />
|
||||
) : (
|
||||
|
||||
<Button
|
||||
icon="pi pi-bolt"
|
||||
label="Zap"
|
||||
severity="success"
|
||||
outlined
|
||||
onClick={handleZapEvent}
|
||||
pt={{
|
||||
button: {
|
||||
icon: ({ context }) => ({
|
||||
className: 'bg-yellow-500'
|
||||
})
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -8,6 +8,7 @@ import { useRouter } from "next/router";
|
||||
import { useLocalStorageWithEffect } from "@/hooks/useLocalStorage";
|
||||
import UserContent from '@/components/profile/UserContent';
|
||||
import Image from "next/image";
|
||||
import BitcoinConnectButton from '@/components/profile/BitcoinConnect';
|
||||
|
||||
const Profile = () => {
|
||||
const [user, setUser] = useLocalStorageWithEffect('user', {});
|
||||
@ -85,6 +86,10 @@ const Profile = () => {
|
||||
|
||||
<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>Connect Your Lightning Wallet</h2>
|
||||
<BitcoinConnectButton />
|
||||
</div>
|
||||
<div className="flex flex-col w-1/2 mx-auto my-4 justify-between items-center">
|
||||
<h2>Subscription</h2>
|
||||
<p className="text-center">You currently have no active subscription</p>
|
||||
|
6
src/utils/lightning.js
Normal file
6
src/utils/lightning.js
Normal file
@ -0,0 +1,6 @@
|
||||
import Bolt11Decoder from "light-bolt11-decoder"
|
||||
|
||||
export const getSatAmountFromInvoice = (invoice) => {
|
||||
const decoded = Bolt11Decoder.decode(invoice)
|
||||
return decoded.sections[2].value / 1000;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user