diff --git a/package-lock.json b/package-lock.json
index b34ea48..8eca486 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,7 @@
"@getalby/bitcoin-connect-react": "^3.5.3",
"@getalby/lightning-tools": "^5.0.3",
"@nostr-dev-kit/ndk": "^2.10.0",
+ "@nostr-dev-kit/ndk-cache-dexie": "^2.5.1",
"@prisma/client": "^5.17.0",
"@tanstack/react-query": "^5.51.21",
"@uiw/react-markdown-preview": "^5.1.2",
@@ -1061,6 +1062,19 @@
"node": ">=16"
}
},
+ "node_modules/@nostr-dev-kit/ndk-cache-dexie": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk-cache-dexie/-/ndk-cache-dexie-2.5.1.tgz",
+ "integrity": "sha512-tUwEy68bd9GL5JVuZIjcpdwuDEBnaXen3WJ64/GRDtbyE1RB01Y6hHC7IQC9bcQ6SC7XBGyPd+2nuTyR7+Mffg==",
+ "license": "MIT",
+ "dependencies": {
+ "@nostr-dev-kit/ndk": "2.10.0",
+ "debug": "^4.3.4",
+ "dexie": "^4.0.2",
+ "nostr-tools": "^2.4.0",
+ "typescript-lru-cache": "^2.0.0"
+ }
+ },
"node_modules/@nostr-dev-kit/ndk/node_modules/@noble/curves": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz",
@@ -3517,9 +3531,9 @@
}
},
"node_modules/axios": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz",
- "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==",
+ "version": "1.7.5",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz",
+ "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
@@ -4245,6 +4259,12 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/dexie": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.0.8.tgz",
+ "integrity": "sha512-1G6cJevS17KMDK847V3OHvK2zei899GwpDiqfEXHP1ASvme6eWJmAp9AU4s1son2TeGkWmC0g3y8ezOBPnalgQ==",
+ "license": "Apache-2.0"
+ },
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -8279,9 +8299,9 @@
"license": "MIT"
},
"node_modules/micromatch": {
- "version": "4.0.7",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
- "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 6f1a5fd..ae1698a 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"@getalby/bitcoin-connect-react": "^3.5.3",
"@getalby/lightning-tools": "^5.0.3",
"@nostr-dev-kit/ndk": "^2.10.0",
+ "@nostr-dev-kit/ndk-cache-dexie": "^2.5.1",
"@prisma/client": "^5.17.0",
"@tanstack/react-query": "^5.51.21",
"@uiw/react-markdown-preview": "^5.1.2",
diff --git a/src/components/content/carousels/templates/CourseTemplate.js b/src/components/content/carousels/templates/CourseTemplate.js
index 4ef9d5e..114337d 100644
--- a/src/components/content/carousels/templates/CourseTemplate.js
+++ b/src/components/content/carousels/templates/CourseTemplate.js
@@ -9,18 +9,17 @@ import { Tag } from "primereact/tag";
import { useZapsSubscription } from "@/hooks/nostrQueries/zaps/useZapsSubscription";
const CourseTemplate = ({ course }) => {
- const [zapAmount, setZapAmount] = useState(null);
+ const { zaps, zapsLoading, zapsError } = useZapsSubscription({ event: course });
+ const [zapAmount, setZapAmount] = useState(0);
const router = useRouter();
const { returnImageProxy } = useImageProxy();
- const { zaps, zapsLoading, zapsError } = useZapsSubscription({ event: course });
useEffect(() => {
- if (!zaps || zapsLoading || zapsError) return;
-
- const total = getTotalFromZaps(zaps, course);
-
- setZapAmount(total);
- }, [course, zaps, zapsLoading, zapsError]);
+ if (zaps.length > 0) {
+ const total = getTotalFromZaps(zaps, course);
+ setZapAmount(total);
+ }
+ }, [zaps, course]);
if (zapsError) return
Error: {zapsError}
;
@@ -30,7 +29,7 @@ const CourseTemplate = ({ course }) => {
>
{/* Wrap the image in a div with a relative class with a padding-bottom of 56.25% representing the aspect ratio of 16:9 */}
router.push(`/course/${course.id}`)}
+ onClick={() => router.replace(`/course/${course.id}`)}
className="relative w-full h-0 hover:opacity-80 transition-opacity duration-300 cursor-pointer"
style={{ paddingBottom: "56.25%" }}
>
@@ -61,7 +60,11 @@ const CourseTemplate = ({ course }) => {
formatTimestampToHowLongAgo(course.created_at)
)}
-
+
{course?.topics && course?.topics.length > 0 && (
@@ -75,4 +78,4 @@ const CourseTemplate = ({ course }) => {
);
};
-export default CourseTemplate;
+export default CourseTemplate;
\ No newline at end of file
diff --git a/src/components/content/carousels/templates/ResourceTemplate.js b/src/components/content/carousels/templates/ResourceTemplate.js
index 9babfbb..cda143c 100644
--- a/src/components/content/carousels/templates/ResourceTemplate.js
+++ b/src/components/content/carousels/templates/ResourceTemplate.js
@@ -9,19 +9,18 @@ import ZapDisplay from "@/components/zaps/ZapDisplay";
import { useZapsSubscription } from "@/hooks/nostrQueries/zaps/useZapsSubscription";
const ResourceTemplate = ({ resource }) => {
- const [zapAmount, setZapAmount] = useState(null);
const { zaps, zapsLoading, zapsError } = useZapsSubscription({ event: resource });
+ const [zapAmount, setZapAmount] = useState(0);
const router = useRouter();
const { returnImageProxy } = useImageProxy();
useEffect(() => {
- if (!zaps || zapsLoading || zapsError) return;
-
- const total = getTotalFromZaps(zaps, resource);
-
- setZapAmount(total);
- }, [resource, zaps, zapsLoading, zapsError]);
+ if (zaps.length > 0) {
+ const total = getTotalFromZaps(zaps, resource);
+ setZapAmount(total);
+ }
+ }, [zaps, resource]);
if (zapsError) return
Error: {zapsError}
;
@@ -31,7 +30,7 @@ const ResourceTemplate = ({ resource }) => {
>
{/* Wrap the image in a div with a relative class with a padding-bottom of 56.25% representing the aspect ratio of 16:9 */}
router.push(`/details/${resource.id}`)}
+ onClick={() => router.replace(`/details/${resource.id}`)}
className="relative w-full h-0 hover:opacity-80 transition-opacity duration-300 cursor-pointer"
style={{ paddingBottom: "56.25%" }}
>
@@ -58,7 +57,11 @@ const ResourceTemplate = ({ resource }) => {
{formatTimestampToHowLongAgo(resource.published_at)}
-
+
{resource?.topics && resource?.topics.length > 0 && (
@@ -72,4 +75,4 @@ const ResourceTemplate = ({ resource }) => {
);
};
-export default ResourceTemplate;
+export default ResourceTemplate;
\ No newline at end of file
diff --git a/src/components/content/carousels/templates/WorkshopTemplate.js b/src/components/content/carousels/templates/WorkshopTemplate.js
index 5a133a1..f645a3b 100644
--- a/src/components/content/carousels/templates/WorkshopTemplate.js
+++ b/src/components/content/carousels/templates/WorkshopTemplate.js
@@ -9,26 +9,24 @@ import ZapDisplay from "@/components/zaps/ZapDisplay";
import { Tag } from "primereact/tag";
const WorkshopTemplate = ({ workshop }) => {
- const [zapAmount, setZapAmount] = useState(null);
+ const { zaps, zapsLoading, zapsError } = useZapsSubscription({ event: workshop });
+ const [zapAmount, setZapAmount] = useState(0);
const router = useRouter();
const { returnImageProxy } = useImageProxy();
- const { zaps, zapsLoading, zapsError } = useZapsSubscription({ event: workshop });
useEffect(() => {
- if (zapsLoading || !zaps) return;
-
- const total = getTotalFromZaps(zaps, workshop);
-
- setZapAmount(total);
- }, [zaps, workshop, zapsLoading, zapsError]);
+ if (zaps.length > 0) {
+ const total = getTotalFromZaps(zaps, workshop);
+ setZapAmount(total);
+ }
+ }, [zaps, workshop]);
if (zapsError) return
Error: {zapsError}
;
return (
- {/* Wrap the image in a div with a relative class with a padding-bottom of 56.25% representing the aspect ratio of 16:9 */}
router.push(`/details/${workshop.id}`)}
+ onClick={() => router.replace(`/details/${workshop.id}`)}
className="relative w-full h-0 hover:opacity-80 transition-opacity duration-300 cursor-pointer"
style={{ paddingBottom: "56.25%" }}
>
@@ -55,7 +53,11 @@ const WorkshopTemplate = ({ workshop }) => {
{formatTimestampToHowLongAgo(workshop.published_at)}
-
+
{workshop?.topics && workshop?.topics.length > 0 && (
@@ -69,4 +71,4 @@ const WorkshopTemplate = ({ workshop }) => {
);
};
-export default WorkshopTemplate;
+export default WorkshopTemplate;
\ No newline at end of file
diff --git a/src/components/content/courses/CourseDetails.js b/src/components/content/courses/CourseDetails.js
index 5867d33..2312a8f 100644
--- a/src/components/content/courses/CourseDetails.js
+++ b/src/components/content/courses/CourseDetails.js
@@ -88,11 +88,10 @@ export default function CourseDetails({ processedEvent, paidCourse, lessons, dec
}, [processedEvent]);
useEffect(() => {
- if (!zaps || zaps.length === 0) return;
-
- const total = getTotalFromZaps(zaps, processedEvent);
-
- setZapAmount(total);
+ if (zaps.length > 0) {
+ const total = getTotalFromZaps(zaps, processedEvent);
+ setZapAmount(total);
+ }
}, [zaps, processedEvent]);
if (!processedEvent || !author) {
@@ -160,7 +159,11 @@ export default function CourseDetails({ processedEvent, paidCourse, lessons, dec
{paidCourse && author && processedEvent?.pubkey === session?.user?.pubkey && (
Price {processedEvent.price} sats
)}
-
+
)}
diff --git a/src/components/content/resources/ResourceDetails.js b/src/components/content/resources/ResourceDetails.js
index 54c680e..b3aa727 100644
--- a/src/components/content/resources/ResourceDetails.js
+++ b/src/components/content/resources/ResourceDetails.js
@@ -10,7 +10,7 @@ import { getTotalFromZaps } from "@/utils/lightning";
import { useSession } from "next-auth/react";
const ResourceDetails = ({processedEvent, topics, title, summary, image, price, author, paidResource, decryptedContent, handlePaymentSuccess, handlePaymentError}) => {
- const [zapAmount, setZapAmount] = useState(null);
+ const [zapAmount, setZapAmount] = useState(0);
const router = useRouter();
const { returnImageProxy } = useImageProxy();
@@ -18,11 +18,10 @@ const ResourceDetails = ({processedEvent, topics, title, summary, image, price,
const { data: session, status } = useSession();
useEffect(() => {
- if (!zaps) return;
-
- const total = getTotalFromZaps(zaps, processedEvent);
-
- setZapAmount(total);
+ if (zaps.length > 0) {
+ const total = getTotalFromZaps(zaps, processedEvent);
+ setZapAmount(total);
+ }
}, [zaps, processedEvent]);
return (
@@ -81,7 +80,11 @@ const ResourceDetails = ({processedEvent, topics, title, summary, image, price,
{/* if this is the author of the resource show a zap button */}
{paidResource && author && processedEvent?.pubkey === session?.user?.pubkey &&
Price {processedEvent.price} sats
}
-
+
)}
diff --git a/src/components/navbar/Navbar.js b/src/components/navbar/Navbar.js
index f4c2539..313b4d3 100644
--- a/src/components/navbar/Navbar.js
+++ b/src/components/navbar/Navbar.js
@@ -1,16 +1,18 @@
-import React, { useRef } from 'react';
+import React, { useRef, useState } from 'react';
import Image from 'next/image';
import UserAvatar from './user/UserAvatar';
import MenuTab from '../menutab/MenuTab';
import { Menubar } from 'primereact/menubar';
import { Menu } from 'primereact/menu';
import { useRouter } from 'next/router';
+import { Button } from 'primereact/button';
+import { Dialog } from 'primereact/dialog';
import 'primereact/resources/primereact.min.css';
import 'primeicons/primeicons.css';
const Navbar = () => {
const router = useRouter();
-
+ const [visible, setVisible] = useState(false);
const menu = useRef(null);
const navbarHeight = '60px';
@@ -73,7 +75,7 @@ const Navbar = () => {
onClick={(e) => menu.current.toggle(e)}>
*/}
- router.push('/').then(() => window.location.reload())} className="flex flex-row items-center justify-center cursor-pointer">
+
router.push('/')} className="flex flex-row items-center justify-center cursor-pointer">
{
const router = useRouter();
const [isClient, setIsClient] = useState(false);
const [user, setUser] = useState(null);
+ const [visible, setVisible] = useState(false);
const { returnImageProxy } = useImageProxy();
const windowWidth = useWindowWidth();
@@ -84,6 +86,16 @@ const UserAvatar = () => {
);
} else {
userAvatar = (
+
+
setVisible(true)} />
+ {if (!visible) return; setVisible(false); }}>
+
+ 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.
+
+
{
rounded
onClick={() => router.push('/auth/signin')}
size={windowWidth < 768 ? 'small' : 'normal'}
- />
+ />
+
);
}
diff --git a/src/context/NDKContext.js b/src/context/NDKContext.js
index 4b83678..b2a2898 100644
--- a/src/context/NDKContext.js
+++ b/src/context/NDKContext.js
@@ -1,5 +1,6 @@
import React, { createContext, useContext, useEffect, useState } from 'react';
import NDK, { NDKNip07Signer } from "@nostr-dev-kit/ndk";
+import NDKCacheAdapterDexie from "@nostr-dev-kit/ndk-cache-dexie";
const NDKContext = createContext(null);
@@ -17,7 +18,7 @@ export const NDKProvider = ({ children }) => {
const [ndk, setNdk] = useState(null);
useEffect(() => {
- const instance = new NDK({ explicitRelayUrls: relayUrls });
+ const instance = new NDK({ explicitRelayUrls: relayUrls, enableOutboxModel: true, outboxRelayUrls: ["wss://nos.lol/"], cacheAdapter: new NDKCacheAdapterDexie({ dbName: 'ndk-cache' }) });
setNdk(instance);
}, []);
diff --git a/src/hooks/nostrQueries/zaps/useZapsSubscription.js b/src/hooks/nostrQueries/zaps/useZapsSubscription.js
index 3cf5c06..b7a01c5 100644
--- a/src/hooks/nostrQueries/zaps/useZapsSubscription.js
+++ b/src/hooks/nostrQueries/zaps/useZapsSubscription.js
@@ -1,44 +1,48 @@
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useCallback } from 'react';
import { useNDKContext } from "@/context/NDKContext";
+import NDK, { NDKEvent, NDKSubscriptionCacheUsage } from "@nostr-dev-kit/ndk";
-export function useZapsSubscription({event}) {
+export function useZapsSubscription({ event }) {
const [zaps, setZaps] = useState([]);
const [zapsLoading, setZapsLoading] = useState(true);
const [zapsError, setZapsError] = useState(null);
- const {ndk, addSigner} = useNDKContext();
+ const { ndk } = useNDKContext();
+
+ const addZap = useCallback((zapEvent) => {
+ setZaps((prevZaps) => {
+ if (prevZaps.some(zap => zap.id === zapEvent.id)) return prevZaps;
+ return [...prevZaps, zapEvent];
+ });
+ }, []);
useEffect(() => {
let subscription;
- let isFirstZap = true;
- const zapIds = new Set(); // To keep track of zap IDs we've already seen
+ const zapIds = new Set();
async function subscribeToZaps() {
+ if (!event || !ndk) return;
+
try {
const filters = [
{ kinds: [9735], "#e": [event.id] },
- { kinds: [9735], "#a": [`${event.kind}:${event.id}:${event.d}`] }
+ { kinds: [9735], "#a": [`${event.kind}:${event.pubkey}:${event.id}`] }
];
- await ndk.connect();
- subscription = ndk.subscribe(filters);
- subscription.on('event', (zapEvent) => {
- // Check if we've already seen this zap
+ subscription = ndk.subscribe(filters, {
+ closeOnEose: false,
+ cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST
+ });
+
+ subscription.on('event', (zapEvent) => {
if (!zapIds.has(zapEvent.id)) {
zapIds.add(zapEvent.id);
- setZaps((prevZaps) => [...prevZaps, zapEvent]);
-
- if (isFirstZap) {
- setZapsLoading(false);
- isFirstZap = false;
- }
+ addZap(zapEvent);
+ setZapsLoading(false);
}
});
subscription.on('eose', () => {
- // Only set loading to false if no zaps have been received yet
- if (isFirstZap) {
- setZapsLoading(false);
- }
+ setZapsLoading(false);
});
await subscription.start();
@@ -49,16 +53,17 @@ export function useZapsSubscription({event}) {
}
}
- if (event && Object.keys(event).length > 0) {
- subscribeToZaps();
- }
+ setZaps([]);
+ setZapsLoading(true);
+ setZapsError(null);
+ subscribeToZaps();
return () => {
if (subscription) {
subscription.stop();
}
};
- }, [event, ndk]);
+ }, [event, ndk, addZap]);
return { zaps, zapsLoading, zapsError };
}
\ No newline at end of file