Added zap amounts onto course template, implemented bitcoin connect connect button

This commit is contained in:
austinkelsay 2024-04-01 18:13:38 -05:00
parent 8a5d90fd53
commit fabf714cdc
8 changed files with 268 additions and 27 deletions

157
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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>

View 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;

View File

@ -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

View File

@ -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>

View File

@ -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
View 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;
}