Update ndk context to not initiate signer, add purchasedListItem for /profile data table

This commit is contained in:
austinkelsay 2024-08-13 16:28:25 -05:00
parent b937dd2507
commit 5a882bc06b
25 changed files with 154 additions and 64 deletions

View File

@ -38,7 +38,7 @@ export default function CourseDetails({ processedEvent }) {
const { returnImageProxy } = useImageProxy();
const { data: session, status } = useSession();
const router = useRouter();
const ndk = useNDKContext();
const {ndk, addSigner} = useNDKContext();
useEffect(() => {
if (session) {

View File

@ -29,7 +29,7 @@ export default function DraftCourseDetails({ processedEvent, lessons }) {
const { returnImageProxy } = useImageProxy();
const { data: session, status } = useSession();
const router = useRouter();
const ndk = useNDKContext();
const { ndk, addSigner } = useNDKContext();
const fetchAuthor = useCallback(async (pubkey) => {
if (!pubkey) return;
@ -64,6 +64,10 @@ export default function DraftCourseDetails({ processedEvent, lessons }) {
const processedLessons = [];
try {
// Step 0: Add signer if not already added
if (!ndk.signer) {
await addSigner();
}
// Step 1: Process lessons
for (const lesson of lessons) {
processedLessons.push({

View File

@ -34,7 +34,7 @@ const CourseForm = () => {
const { drafts, draftsLoading, draftsError } = useDraftsQuery();
const { data: session, status } = useSession();
const [user, setUser] = useState(null);
const ndk = useNDKContext();
const { ndk, addSigner } = useNDKContext();
const router = useRouter();
const { showToast } = useToast();
@ -51,6 +51,10 @@ const CourseForm = () => {
const handleDraftSubmit = async (e) => {
e.preventDefault();
if (!ndk.signer) {
await addSigner();
}
// Prepare the lessons from selected lessons
const resources = await Promise.all(selectedLessons.map(async (lesson) => {
// if .type is present than this lesson is a draft we need to publish

View File

@ -31,7 +31,7 @@ const ResourceForm = ({ draft = null, isPublished = false }) => {
const { data: session, status } = useSession();
const { showToast } = useToast();
const router = useRouter();
const ndk = useNDKContext();
const { ndk, addSigner } = useNDKContext();
useEffect(() => {
console.log('isPublished', isPublished);
@ -108,6 +108,10 @@ const ResourceForm = ({ draft = null, isPublished = false }) => {
console.log('event', event);
try {
if (!ndk.signer) {
await addSigner();
}
await ndk.connect();
const published = await ndk.publish(event);

View File

@ -73,7 +73,7 @@ const Navbar = () => {
onClick={(e) => menu.current.toggle(e)}></i>
<Menu model={menuItems} popup ref={menu} />
</div> */}
<div onClick={() => router.push('/')} className="flex flex-row items-center justify-center cursor-pointer">
<div onClick={() => router.push('/').then(() => window.location.reload())} className="flex flex-row items-center justify-center cursor-pointer">
<Image
alt="logo"
src="/plebdevs-guy.jpg"

View File

@ -28,8 +28,8 @@ const UserAvatar = () => {
const menu = useRef(null);
const handleLogout = () => {
signOut();
const handleLogout = async () => {
await signOut({ redirect: false }); // Wait for the sign-out to complete
router.push('/').then(() => window.location.reload());
}

View File

@ -0,0 +1,30 @@
import React, { useEffect, useState } from "react";
import { useNDKContext } from "@/context/NDKContext";
import { parseEvent } from "@/utils/nostr";
import { ProgressSpinner } from "primereact/progressspinner";
const PurchasedListItem = ({ eventId, category }) => {
const { ndk } = useNDKContext();
const [event, setEvent] = useState(null);
useEffect(() => {
const fetchEvent = async () => {
if (!eventId) return;
try {
await ndk.connect();
const event = await ndk.fetchEvent(eventId);
if (event) {
setEvent(parseEvent(event));
}
} catch (error) {
console.error("Error fetching event:", error);
}
}
fetchEvent();
}, [eventId, ndk]);
return !event || !ndk ? <ProgressSpinner className="w-[40px] h-[40px]" /> : (<a className="text-blue-500 underline hover:text-blue-600" href={category === "courses" ? `/courses/${event.id}` : `/details/${event.id}`}>{event.title}</a>);
}
export default PurchasedListItem;

View File

@ -25,7 +25,7 @@ const UserContent = () => {
const [user, setUser] = useState(null);
const router = useRouter();
const { showToast } = useToast();
const ndk = useNDKContext();
const {ndk, addSigner} = useNDKContext();
const { courses, coursesLoading, coursesError } = useCoursesQuery();
const { resources, resourcesLoading, resourcesError } = useResourcesQuery();
const { workshops, workshopsLoading, workshopsError } = useWorkshopsQuery();

View File

@ -19,8 +19,8 @@ const ZapDisplay = ({ zapAmount, event, zapsLoading }) => {
return (
<>
<span className="text-xs cursor-pointer flex items-center relative hover:opacity-80" onClick={(e) => op.current.toggle(e)}>
<i className="pi pi-bolt text-yellow-300"></i>
<span className="relative flex items-center min-w-[20px] min-h-[20px]">
<i className="pi pi-bolt text-yellow-300 text-lg"></i>
<span className="relative flex items-center min-w-[20px] min-h-[20px] text-sm">
{zapsLoading || zapAmount === null || extraLoading ? (
<ProgressSpinner className="absolute top-0 left-0 w-[20px] h-[20px]" strokeWidth="8" animationDuration=".5s" />
) : (

View File

@ -4,31 +4,38 @@ import NDK, { NDKNip07Signer } from "@nostr-dev-kit/ndk";
const NDKContext = createContext(null);
const relayUrls = [
"wss://nos.lol/",
"wss://relay.damus.io/",
"wss://relay.snort.social/",
"wss://relay.nostr.band/",
"wss://nostr.mutinywallet.com/",
"wss://relay.mutinywallet.com/",
"wss://relay.primal.net/"
"wss://nos.lol/",
"wss://relay.damus.io/",
"wss://relay.snort.social/",
"wss://relay.nostr.band/",
"wss://nostr.mutinywallet.com/",
"wss://relay.mutinywallet.com/",
"wss://relay.primal.net/"
];
export const NDKProvider = ({ children }) => {
const [ndk, setNdk] = useState(null);
const [ndk, setNdk] = useState(null);
useEffect(() => {
const nip07signer = new NDKNip07Signer();
const instance = new NDK({ explicitRelayUrls: relayUrls, signer: nip07signer });
setNdk(instance);
}, []);
useEffect(() => {
const instance = new NDK({ explicitRelayUrls: relayUrls });
setNdk(instance);
}, []);
return (
<NDKContext.Provider value={ndk}>
{children}
</NDKContext.Provider>
);
const addSigner = async () => {
if (ndk) {
const nip07signer = new NDKNip07Signer();
await ndk.signer?.user();
ndk.signer = nip07signer;
}
};
return (
<NDKContext.Provider value={{ ndk, addSigner }}>
{children}
</NDKContext.Provider>
);
};
export const useNDKContext = () => {
return useContext(NDKContext);
};
return useContext(NDKContext);
};

View File

@ -4,7 +4,7 @@ import { useNDKContext } from '@/context/NDKContext';
export function useAllContentQuery({ids}) {
const [isClient, setIsClient] = useState(false);
const ndk = useNDKContext();
const {ndk, addSigner} = useNDKContext();
useEffect(() => {
setIsClient(true);

View File

@ -7,7 +7,7 @@ const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY;
export function useCoursesQuery() {
const [isClient, setIsClient] = useState(false);
const ndk = useNDKContext();
const {ndk, addSigner} = useNDKContext();
useEffect(() => {
setIsClient(true);

View File

@ -7,7 +7,7 @@ const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY;
export function useResourcesQuery() {
const [isClient, setIsClient] = useState(false);
const ndk = useNDKContext();
const {ndk, addSigner} = useNDKContext();
useEffect(() => {
setIsClient(true);

View File

@ -7,7 +7,7 @@ const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY;
export function useWorkshopsQuery() {
const [isClient, setIsClient] = useState(false);
const ndk = useNDKContext();
const {ndk, addSigner} = useNDKContext();
useEffect(() => {
setIsClient(true);

View File

@ -4,7 +4,7 @@ import { useNDKContext } from '@/context/NDKContext';
export function useZapsQuery({ event, type }) {
const [isClient, setIsClient] = useState(false);
const ndk = useNDKContext();
const {ndk, addSigner} = useNDKContext();
useEffect(() => {
setIsClient(true);

View File

@ -5,7 +5,7 @@ export function useZapsSubscription({event}) {
const [zaps, setZaps] = useState([]);
const [zapsLoading, setZapsLoading] = useState(true);
const [zapsError, setZapsError] = useState(null);
const ndk = useNDKContext();
const {ndk, addSigner} = useNDKContext();
useEffect(() => {
let subscription;
@ -19,12 +19,9 @@ export function useZapsSubscription({event}) {
{ kinds: [9735], "#a": [`${event.kind}:${event.id}:${event.d}`] }
];
await ndk.connect();
console.log("filters", filters);
subscription = ndk.subscribe(filters);
subscription.on('event', (zapEvent) => {
console.log("event", zapEvent);
subscription.on('event', (zapEvent) => {
// Check if we've already seen this zap
if (!zapIds.has(zapEvent.id)) {
zapIds.add(zapEvent.id);

View File

@ -75,6 +75,7 @@ export default NextAuth({
],
callbacks: {
async jwt({ token, trigger, user }) {
console.log('TRIGGER', trigger);
if (trigger === "update") {
// if we trigger an update call the authorize function again
const newUser = await authorize(token.user.pubkey);
@ -95,6 +96,12 @@ export default NextAuth({
async redirect({ url, baseUrl }) {
return baseUrl;
},
async signOut({ token, session }) {
console.log('signOut', token, session);
token = {}
session = {}
return true
},
},
secret: process.env.NEXTAUTH_SECRET,
session: { strategy: "jwt" },

View File

@ -2,13 +2,14 @@ import { signIn, useSession } from "next-auth/react"
import { useState, useEffect } from "react"
import { useNDKContext } from "@/context/NDKContext";
import { Button } from 'primereact/button';
import NDK, { NDKEvent, NDKNip07Signer } from "@nostr-dev-kit/ndk";
export default function SignIn() {
const [email, setEmail] = useState("")
const [nostrPubkey, setNostrPubkey] = useState("")
const [nostrPrivkey, setNostrPrivkey] = useState("")
const {ndk, addSigner} = useNDKContext();
const { data: session, status } = useSession(); // Get the current session's data and status
useEffect(() => {
@ -22,14 +23,14 @@ export default function SignIn() {
const handleNostrSignIn = async (e) => {
e.preventDefault()
const nip07signer = new NDKNip07Signer();
const ndk = new NDK({ signer: nip07signer });
await ndk.connect()
if (!ndk.signer) {
await addSigner();
}
try {
const user = await nip07signer.user()
const user = await ndk.signer.user()
const pubkey = user?._pubkey
signIn("nostr", { pubkey })

View File

@ -20,7 +20,7 @@ const DraftCourse = () => {
const [lessonsWithAuthors, setLessonsWithAuthors] = useState([]);
const router = useRouter();
const ndk = useNDKContext();
const {ndk, addSigner} = useNDKContext();
const fetchAuthor = useCallback(async (pubkey) => {
if (!pubkey) return;

View File

@ -19,7 +19,7 @@ const Course = () => {
const [lessons, setLessons] = useState([]);
const router = useRouter();
const ndk = useNDKContext();
const {ndk, addSigner} = useNDKContext();
const fetchAuthor = useCallback(async (pubkey) => {
const author = await ndk.getUser({ pubkey });

View File

@ -10,7 +10,7 @@ import { useToast } from "@/hooks/useToast";
export default function Edit() {
const [event, setEvent] = useState(null);
const ndk = useNDKContext();
const {ndk, addSigner} = useNDKContext();
const router = useRouter();
const { showToast } = useToast();

View File

@ -31,7 +31,7 @@ export default function Details() {
const [decryptedContent, setDecryptedContent] = useState(null);
const [authorView, setAuthorView] = useState(false);
const ndk = useNDKContext();
const {ndk, addSigner} = useNDKContext();
const { data: session, update } = useSession();
const [user, setUser] = useState(null);
const { showToast } = useToast();

View File

@ -49,7 +49,7 @@ export default function Draft() {
const { width, height } = useResponsiveImageDimensions();
const router = useRouter();
const { showToast } = useToast();
const ndk = useNDKContext();
const { ndk, addSigner } = useNDKContext();
useEffect(() => {
if (session) {
@ -74,6 +74,10 @@ export default function Draft() {
const handleSubmit = async () => {
try {
if (!ndk.signer) {
await addSigner();
}
if (draft) {
const { unsignedEvent, type } = await buildEvent(draft);

View File

@ -5,6 +5,10 @@ import { Menu } from "primereact/menu";
import { Column } from "primereact/column";
import { useImageProxy } from "@/hooks/useImageProxy";
import { useSession } from 'next-auth/react';
import { ProgressSpinner } from "primereact/progressspinner";
import PurchasedListItem from "@/components/profile/PurchasedListItem";
import { useNDKContext } from "@/context/NDKContext";
import { formatDateTime } from "@/utils/time";
import UserContent from "@/components/profile/UserContent";
import Image from "next/image";
import BitcoinConnectButton from "@/components/bitcoinConnect/BitcoinConnect";
@ -15,6 +19,7 @@ const Profile = () => {
const { data: session, status } = useSession();
const { returnImageProxy } = useImageProxy();
const { ndk } = useNDKContext();
const menu = useRef(null);
useEffect(() => {
@ -33,8 +38,6 @@ const Profile = () => {
}
}, [session]);
const purchases = [];
const menuItems = [
{
label: "Edit",
@ -96,17 +99,28 @@ const Profile = () => {
/>
</div>
</div>
<DataTable
emptyMessage="No purchases"
value={purchases}
tableStyle={{ minWidth: "100%" }}
header={header}
>
<Column field="cost" header="Cost"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="date" header="Date"></Column>
</DataTable>
{!session || !session?.user || !ndk ? (
<ProgressSpinner />
) : (
<DataTable
emptyMessage="No purchases"
value={session.user?.purchased}
tableStyle={{ minWidth: "100%" }}
header={header}
>
<Column field="amountPaid" header="Cost"></Column>
<Column
body={(rowData) => {
console.log("rowData", rowData);
return <PurchasedListItem eventId={rowData?.resource?.noteId || rowData?.course?.noteId} category={rowData?.course ? "courses" : "resources"} />
}}
header="Name"
></Column>
<Column body={session.user?.purchased?.some((item) => item.courseId) ? "course" : "resource"} header="Category"></Column>
<Column body={rowData => formatDateTime(rowData?.createdAt)} header="Date"></Column>
</DataTable>
)}
<UserContent />
</div>
)

View File

@ -3,6 +3,24 @@ export const formatUnixTimestamp = (time) => {
return date.toDateString();
}
export const formatDateTime = (isoDate) => {
const date = new Date(isoDate);
// Example: Format to a more readable string
const formattedDate = date.toLocaleString("en-US", {
timeZone: "UTC", // Optional: You can change this to the user's time zone if needed
weekday: "long", // "long" for full name, "short" for abbreviated
year: "numeric",
month: "long", // "long" for full name, "short" for abbreviated
day: "numeric",
hour: "numeric",
minute: "numeric",
second: "numeric",
});
return formattedDate;
}
export const formatTimestampToHowLongAgo = (time) => {
const date = new Date(time * 1000);
const now = new Date();