naddress slugs for all published details pages, also view nostr note buttons on all content

This commit is contained in:
austinkelsay 2024-09-14 17:05:05 -05:00
parent 3b077b542a
commit 8b782ffc60
8 changed files with 74 additions and 18 deletions

View File

@ -1,22 +1,34 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import ZapDisplay from "@/components/zaps/ZapDisplay"; import ZapDisplay from "@/components/zaps/ZapDisplay";
import { FileText } from "lucide-react"
import Image from "next/image" import Image from "next/image"
import { useZapsSubscription } from "@/hooks/nostrQueries/zaps/useZapsSubscription"; import { useZapsSubscription } from "@/hooks/nostrQueries/zaps/useZapsSubscription";
import { getTotalFromZaps } from "@/utils/lightning"; import { getTotalFromZaps } from "@/utils/lightning";
import { useImageProxy } from "@/hooks/useImageProxy"; import { useImageProxy } from "@/hooks/useImageProxy";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { formatTimestampToHowLongAgo } from "@/utils/time"; import { formatTimestampToHowLongAgo } from "@/utils/time";
import { nip19 } from "nostr-tools";
import { Tag } from "primereact/tag"; import { Tag } from "primereact/tag";
import GenericButton from "@/components/buttons/GenericButton"; import GenericButton from "@/components/buttons/GenericButton";
export function DocumentTemplate({ document }) { export function DocumentTemplate({ document }) {
const { zaps, zapsLoading, zapsError } = useZapsSubscription({ event: document }); const { zaps, zapsLoading, zapsError } = useZapsSubscription({ event: document });
const [nAddress, setNAddress] = useState(null);
const [zapAmount, setZapAmount] = useState(0); const [zapAmount, setZapAmount] = useState(0);
const router = useRouter(); const router = useRouter();
const { returnImageProxy } = useImageProxy(); const { returnImageProxy } = useImageProxy();
useEffect(() => {
if (document && document?.id) {
const nAddress = nip19.naddrEncode({
pubkey: document.pubkey,
kind: document.kind,
identifier: document.id,
});
setNAddress(nAddress);
}
}, [document]);
useEffect(() => { useEffect(() => {
if (zaps.length > 0) { if (zaps.length > 0) {
const total = getTotalFromZaps(zaps, document); const total = getTotalFromZaps(zaps, document);
@ -73,7 +85,7 @@ export function DocumentTemplate({ document }) {
) : ( ) : (
formatTimestampToHowLongAgo(document.created_at) formatTimestampToHowLongAgo(document.created_at)
)}</p> )}</p>
<GenericButton onClick={() => router.push(`/details/${document.id}`)} size="small" label="Read" icon="pi pi-chevron-right" iconPos="right" outlined className="items-center py-2" /> <GenericButton onClick={() => router.push(`/details/${nAddress}`)} size="small" label="Read" icon="pi pi-chevron-right" iconPos="right" outlined className="items-center py-2" />
</CardFooter> </CardFooter>
</Card> </Card>
) )

View File

@ -7,6 +7,7 @@ import { useZapsSubscription } from "@/hooks/nostrQueries/zaps/useZapsSubscripti
import { getTotalFromZaps } from "@/utils/lightning"; import { getTotalFromZaps } from "@/utils/lightning";
import { useImageProxy } from "@/hooks/useImageProxy"; import { useImageProxy } from "@/hooks/useImageProxy";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { nip19 } from "nostr-tools";
import { formatTimestampToHowLongAgo } from "@/utils/time"; import { formatTimestampToHowLongAgo } from "@/utils/time";
import { Tag } from "primereact/tag"; import { Tag } from "primereact/tag";
import GenericButton from "@/components/buttons/GenericButton"; import GenericButton from "@/components/buttons/GenericButton";
@ -14,9 +15,19 @@ import GenericButton from "@/components/buttons/GenericButton";
export function VideoTemplate({ video }) { export function VideoTemplate({ video }) {
const { zaps, zapsLoading, zapsError } = useZapsSubscription({ event: video }); const { zaps, zapsLoading, zapsError } = useZapsSubscription({ event: video });
const [zapAmount, setZapAmount] = useState(0); const [zapAmount, setZapAmount] = useState(0);
const [nAddress, setNAddress] = useState(null);
const router = useRouter(); const router = useRouter();
const { returnImageProxy } = useImageProxy(); const { returnImageProxy } = useImageProxy();
useEffect(() => {
const addr = nip19.naddrEncode({
pubkey: video.pubkey,
kind: video.kind,
identifier: video.id
})
setNAddress(addr);
}, [video]);
useEffect(() => { useEffect(() => {
if (zaps.length > 0) { if (zaps.length > 0) {
const total = getTotalFromZaps(zaps, video); const total = getTotalFromZaps(zaps, video);
@ -73,7 +84,7 @@ export function VideoTemplate({ video }) {
) : ( ) : (
formatTimestampToHowLongAgo(video.created_at) formatTimestampToHowLongAgo(video.created_at)
)}</p> )}</p>
<GenericButton onClick={() => router.push(`/details/${video.id}`)} size="small" label="Watch" icon="pi pi-chevron-right" iconPos="right" outlined className="items-center py-2" /> <GenericButton onClick={() => router.push(`/details/${nAddress}`)} size="small" label="Watch" icon="pi pi-chevron-right" iconPos="right" outlined className="items-center py-2" />
</CardFooter> </CardFooter>
</Card> </Card>
) )

View File

@ -164,9 +164,7 @@ export default function CourseDetailsNew({ processedEvent, paidCourse, lessons,
</div> </div>
)} )}
{nAddress && ( {nAddress && (
<div className='w-full flex flex-row justify-end'> <GenericButton outlined icon="pi pi-external-link" onClick={() => window.open(`https://nostr.band/${nAddress}`, '_blank')} tooltip="View Nostr Event" tooltipOptions={{ position: paidCourse ? 'left' : 'right' }} />
<GenericButton outlined icon="pi pi-external-link" onClick={() => window.open(`https://nostr.band/${nAddress}`, '_blank')} tooltip="View Nostr Event" tooltipOptions={{ position: 'left' }} />
</div>
)} )}
</div> </div>
</div> </div>

View File

@ -86,7 +86,7 @@ const VideoLesson = ({ lesson, course, decryptionPerformed, isPaid }) => {
<Divider /> <Divider />
<div className="bg-gray-800/90 rounded-lg p-4 m-4"> <div className="bg-gray-800/90 rounded-lg p-4 m-4">
<div className="w-full flex flex-col items-start justify-start mt-2 px-2"> <div className="w-full flex flex-col items-start justify-start mt-2 px-2">
<div className="flex flex-row items-center justify-between w-full"> <div className="flex flex-row items-center gap-2 w-full">
<h1 className='text-3xl text-white'>{lesson.title}</h1> <h1 className='text-3xl text-white'>{lesson.title}</h1>
{lesson.topics && lesson.topics.length > 0 && ( {lesson.topics && lesson.topics.length > 0 && (
lesson.topics.map((topic, index) => ( lesson.topics.map((topic, index) => (

View File

@ -23,7 +23,7 @@ const MDDisplay = dynamic(
const lnAddress = process.env.NEXT_PUBLIC_LIGHTNING_ADDRESS; const lnAddress = process.env.NEXT_PUBLIC_LIGHTNING_ADDRESS;
const DocumentDetails = ({ processedEvent, topics, title, summary, image, price, author, paidResource, decryptedContent, handlePaymentSuccess, handlePaymentError, authorView }) => { const DocumentDetails = ({ processedEvent, topics, title, summary, image, price, author, paidResource, decryptedContent, nAddress, handlePaymentSuccess, handlePaymentError, authorView }) => {
const [zapAmount, setZapAmount] = useState(0); const [zapAmount, setZapAmount] = useState(0);
const router = useRouter(); const router = useRouter();
const { returnImageProxy } = useImageProxy(); const { returnImageProxy } = useImageProxy();
@ -151,6 +151,17 @@ const DocumentDetails = ({ processedEvent, topics, title, summary, image, price,
zapsLoading={zapsLoading && zapAmount === 0} zapsLoading={zapsLoading && zapAmount === 0}
/> />
</div> </div>
<div className="w-full flex flex-row justify-end">
<GenericButton
tooltip={`View Nostr Note`}
tooltipOptions={{ position: 'left' }}
icon="pi pi-external-link"
outlined
onClick={() => {
window.open(`https://nostr.com/${nAddress}`, '_blank');
}}
/>
</div>
</div> </div>
</div> </div>
{renderContent()} {renderContent()}

View File

@ -23,7 +23,7 @@ const MDDisplay = dynamic(
const lnAddress = process.env.NEXT_PUBLIC_LIGHTNING_ADDRESS; const lnAddress = process.env.NEXT_PUBLIC_LIGHTNING_ADDRESS;
const VideoDetails = ({ processedEvent, topics, title, summary, image, price, author, paidResource, decryptedContent, handlePaymentSuccess, handlePaymentError, authorView }) => { const VideoDetails = ({ processedEvent, topics, title, summary, image, price, author, paidResource, decryptedContent, nAddress, handlePaymentSuccess, handlePaymentError, authorView }) => {
const [zapAmount, setZapAmount] = useState(0); const [zapAmount, setZapAmount] = useState(0);
const router = useRouter(); const router = useRouter();
const { returnImageProxy } = useImageProxy(); const { returnImageProxy } = useImageProxy();
@ -119,7 +119,7 @@ const VideoDetails = ({ processedEvent, topics, title, summary, image, price, au
{renderContent()} {renderContent()}
<div className="bg-gray-800/90 rounded-lg p-4 m-4"> <div className="bg-gray-800/90 rounded-lg p-4 m-4">
<div className="w-full flex flex-col items-start justify-start mt-2 px-2"> <div className="w-full flex flex-col items-start justify-start mt-2 px-2">
<div className="flex flex-row items-center justify-between w-full"> <div className="flex flex-row items-center gap-2 w-full">
<h1 className='text-4xl'>{title}</h1> <h1 className='text-4xl'>{title}</h1>
{topics && topics.length > 0 && ( {topics && topics.length > 0 && (
topics.map((topic, index) => ( topics.map((topic, index) => (
@ -154,6 +154,15 @@ const VideoDetails = ({ processedEvent, topics, title, summary, image, price, au
</a> </a>
</p> </p>
</div> </div>
<GenericButton
tooltip={`View Nostr Note`}
tooltipOptions={{ position: 'left' }}
icon="pi pi-external-link"
outlined
onClick={() => {
window.open(`https://nostr.com/${nAddress}`, '_blank');
}}
/>
{authorView && ( {authorView && (
<div className='flex flex-row justify-center items-center space-x-2'> <div className='flex flex-row justify-center items-center space-x-2'>
<GenericButton onClick={() => router.push(`/details/${processedEvent.id}/edit`)} label="Edit" severity='warning' outlined /> <GenericButton onClick={() => router.push(`/details/${processedEvent.id}/edit`)} label="Edit" severity='warning' outlined />

View File

@ -40,7 +40,7 @@ const Sidebar = ({ course = false }) => {
if (router.isReady) { if (router.isReady) {
const { slug } = router.query; const { slug } = router.query;
if (slug) { if (slug && course) {
const { data } = nip19.decode(slug) const { data } = nip19.decode(slug)
if (!data) { if (!data) {

View File

@ -68,14 +68,27 @@ export default function Details() {
if (router.isReady) { if (router.isReady) {
const { slug } = router.query; const { slug } = router.query;
const fetchEvent = async (slug, retryCount = 0) => { if (!slug) {
return;
}
const { data } = nip19.decode(slug)
if (!data) {
showToast('error', 'Error', 'Resource not found');
return;
}
const id = data?.identifier;
const fetchEvent = async (id, retryCount = 0) => {
setLoading(true); setLoading(true);
setError(null); setError(null);
try { try {
await ndk.connect(); await ndk.connect();
const filter = { const filter = {
ids: [slug] ids: [id]
} }
const event = await ndk.fetchEvent(filter); const event = await ndk.fetchEvent(filter);
@ -93,7 +106,7 @@ export default function Details() {
if (retryCount < 1) { if (retryCount < 1) {
// Wait for 2 seconds before retrying // Wait for 2 seconds before retrying
await new Promise(resolve => setTimeout(resolve, 3000)); await new Promise(resolve => setTimeout(resolve, 3000));
return fetchEvent(slug, retryCount + 1); return fetchEvent(id, retryCount + 1);
} else { } else {
setError("Event not found"); setError("Event not found");
} }
@ -103,7 +116,7 @@ export default function Details() {
if (retryCount < 1) { if (retryCount < 1) {
// Wait for 2 seconds before retrying // Wait for 2 seconds before retrying
await new Promise(resolve => setTimeout(resolve, 3000)); await new Promise(resolve => setTimeout(resolve, 3000));
return fetchEvent(slug, retryCount + 1); return fetchEvent(id, retryCount + 1);
} else { } else {
setError("Failed to fetch event. Please try again."); setError("Failed to fetch event. Please try again.");
} }
@ -112,8 +125,8 @@ export default function Details() {
} }
}; };
if (ndk) { if (ndk && id) {
fetchEvent(slug); fetchEvent(id);
} }
} }
}, [router.isReady, router.query, ndk, user]); }, [router.isReady, router.query, ndk, user]);
@ -200,6 +213,7 @@ export default function Details() {
price={processedEvent.price} price={processedEvent.price}
author={author} author={author}
paidResource={paidResource} paidResource={paidResource}
nAddress={nAddress}
decryptedContent={decryptedContent} decryptedContent={decryptedContent}
handlePaymentSuccess={handlePaymentSuccess} handlePaymentSuccess={handlePaymentSuccess}
handlePaymentError={handlePaymentError} handlePaymentError={handlePaymentError}
@ -216,13 +230,14 @@ export default function Details() {
author={author} author={author}
paidResource={paidResource} paidResource={paidResource}
decryptedContent={decryptedContent} decryptedContent={decryptedContent}
nAddress={nAddress}
handlePaymentSuccess={handlePaymentSuccess} handlePaymentSuccess={handlePaymentSuccess}
handlePaymentError={handlePaymentError} handlePaymentError={handlePaymentError}
authorView={authorView} authorView={authorView}
/> />
)} )}
{typeof window !== 'undefined' && nAddress !== null && ( {typeof window !== 'undefined' && nAddress !== null && (
<div className='max-tab:px-4'> <div className='px-4'>
<ZapThreadsWrapper <ZapThreadsWrapper
anchor={nAddress} anchor={nAddress}
user={user?.pubkey || null} user={user?.pubkey || null}