mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-23 16:05:24 +00:00
Implemented basic zap function, starting on listening for zaps
This commit is contained in:
parent
6eb4edf617
commit
6ef8f2cb88
src
components/content/carousels
hooks
pages/details
@ -1,7 +1,5 @@
|
||||
import React, { useState, useEffect, use } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Carousel } from 'primereact/carousel';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useImageProxy } from '@/hooks/useImageProxy';
|
||||
import { parseEvent } from '@/utils/nostr';
|
||||
import { useNostr } from '@/hooks/useNostr';
|
||||
import CourseTemplate from '@/components/content/carousels/templates/CourseTemplate';
|
||||
@ -27,11 +25,8 @@ const responsiveOptions = [
|
||||
|
||||
export default function CoursesCarousel() {
|
||||
const [processedCourses, setProcessedCourses] = useState([]);
|
||||
const [screenWidth, setScreenWidth] = useState(null);
|
||||
const [courses, setCourses] = useState([]);
|
||||
const router = useRouter();
|
||||
const { fetchCourses, events } = useNostr();
|
||||
const { returnImageProxy } = useImageProxy();
|
||||
|
||||
useEffect(() => {
|
||||
if (events && events.courses && events.courses.length > 0) {
|
||||
@ -41,35 +36,6 @@ export default function CoursesCarousel() {
|
||||
}
|
||||
}, [events]);
|
||||
|
||||
useEffect(() => {
|
||||
// Update the state to the current window width
|
||||
setScreenWidth(window.innerWidth);
|
||||
|
||||
const handleResize = () => {
|
||||
// Update the state to the new window width when it changes
|
||||
setScreenWidth(window.innerWidth);
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
// Remove the event listener on cleanup
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []); // The empty array ensures this effect only runs once, similar to componentDidMount
|
||||
|
||||
|
||||
const calculateImageDimensions = () => {
|
||||
if (screenWidth >= 1200) {
|
||||
// Large screens
|
||||
return { width: 426, height: 240 };
|
||||
} else if (screenWidth >= 768 && screenWidth < 1200) {
|
||||
// Medium screens
|
||||
return { width: 344, height: 194 };
|
||||
} else {
|
||||
// Small screens
|
||||
return { width: screenWidth - 120, height: (screenWidth - 120) * (9 / 16) };
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const processCourses = courses.map(course => {
|
||||
const { id, content, title, summary, image, published_at } = parseEvent(course);
|
||||
|
@ -1,14 +1,27 @@
|
||||
import React from "react";
|
||||
import React, {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";
|
||||
|
||||
const CourseTemplate = (course) => {
|
||||
const [zaps, setZaps] = useState([]);
|
||||
const router = useRouter();
|
||||
const { returnImageProxy } = useImageProxy();
|
||||
const { width, height } = useResponsiveImageDimensions();
|
||||
const {events, fetchZapsForEvent} = useNostr();
|
||||
|
||||
useEffect(() => {
|
||||
if (events && events.zaps) {
|
||||
console.log('zaps:', events.zaps);
|
||||
setZaps(events.zaps);
|
||||
} else {
|
||||
fetchZapsForEvent(course.id);
|
||||
}
|
||||
}, [events]);
|
||||
|
||||
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">
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { SimplePool, nip19, verifyEvent } from "nostr-tools";
|
||||
import { SimplePool, nip19, verifyEvent, nip57 } from "nostr-tools";
|
||||
import axios from "axios";
|
||||
import { useToast } from "./useToast";
|
||||
|
||||
@ -20,7 +20,8 @@ export const useNostr = () => {
|
||||
resources: [],
|
||||
workshops: [],
|
||||
courses: [],
|
||||
streams: []
|
||||
streams: [],
|
||||
zaps: []
|
||||
});
|
||||
|
||||
const { showToast } = useToast();
|
||||
@ -53,6 +54,9 @@ 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 => ({
|
||||
@ -75,6 +79,84 @@ export const useNostr = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// zaps
|
||||
// 1. get the author from the content
|
||||
// 2. get the author's kind0
|
||||
// 3. get the author's lud16 if available
|
||||
// 4. Make a get request to the lud16 endpoint and ensure that allowNostr is true
|
||||
// 5. Create zap request event and sign it
|
||||
// 6. Send to the callback url as a get req with the nostr event as a query param
|
||||
// 7. get the invoice back and pay it with webln
|
||||
// 8. listen for the zap receipt event and update the UI
|
||||
|
||||
const zapEvent = async (event) => {
|
||||
const kind0 = await fetchKind0([{ authors: [event.pubkey], kinds: [0] }], {});
|
||||
|
||||
if (Object.keys(kind0).length === 0) {
|
||||
console.error('Error fetching kind0');
|
||||
return;
|
||||
}
|
||||
|
||||
if (kind0?.lud16) {
|
||||
const lud16Username = kind0.lud16.split('@')[0];
|
||||
const lud16Domain = kind0.lud16.split('@')[1];
|
||||
|
||||
const lud16Url = `https://${lud16Domain}/.well-known/lnurlp/${lud16Username}`;
|
||||
|
||||
const response = await axios.get(lud16Url);
|
||||
|
||||
if (response.data.allowsNostr) {
|
||||
const zapReq = nip57.makeZapRequest({
|
||||
profile: event.pubkey,
|
||||
event: event.id,
|
||||
amount: 1000,
|
||||
relays: relays,
|
||||
comment: 'Plebdevs Zap'
|
||||
});
|
||||
|
||||
console.log('zapReq:', zapReq);
|
||||
|
||||
const signedEvent = await window?.nostr.signEvent(zapReq);
|
||||
|
||||
const callbackUrl = response.data.callback;
|
||||
|
||||
const zapRequestAPICall = `${callbackUrl}?amount=${1000}&nostr=${encodeURI(JSON.stringify(signedEvent))}`;
|
||||
|
||||
const invoiceResponse = await axios.get(zapRequestAPICall);
|
||||
|
||||
if (invoiceResponse?.data?.pr) {
|
||||
const invoice = invoiceResponse.data.pr;
|
||||
|
||||
const enabled = await window?.webln?.enable();
|
||||
|
||||
console.log('webln enabled:', enabled);
|
||||
|
||||
const payInvoiceResponse = await window?.webln?.sendPayment(invoice);
|
||||
|
||||
console.log('payInvoiceResponse:', payInvoiceResponse);
|
||||
} else {
|
||||
console.error('Error fetching invoice');
|
||||
showToast('error', 'Error', 'Error fetching invoice');
|
||||
}
|
||||
}
|
||||
} else if (kind0?.lud06) {
|
||||
// handle lnurlpay
|
||||
} else {
|
||||
showToast('error', 'Error', 'User has no Lightning Address or LNURL');
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Fetch resources, workshops, courses, and streams with appropriate filters and update functions
|
||||
const fetchResources = async () => {
|
||||
const filter = [{ kinds: [30023], authors: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741"] }];
|
||||
@ -288,6 +370,8 @@ export const useNostr = () => {
|
||||
fetchCourses,
|
||||
fetchWorkshops,
|
||||
// fetchStreams,
|
||||
zapEvent,
|
||||
fetchZapsForEvent,
|
||||
getRelayStatuses,
|
||||
events
|
||||
};
|
||||
|
@ -27,10 +27,18 @@ export default function Details() {
|
||||
const [author, setAuthor] = useState(null);
|
||||
|
||||
const { returnImageProxy } = useImageProxy();
|
||||
const { fetchSingleEvent, fetchKind0 } = useNostr();
|
||||
const { fetchSingleEvent, fetchKind0, zapEvent } = useNostr();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const handleZapEvent = async () => {
|
||||
if (!event) return;
|
||||
|
||||
const response = await zapEvent(event);
|
||||
|
||||
console.log('zap response:', response);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (router.isReady) {
|
||||
const { slug } = router.query;
|
||||
@ -112,6 +120,7 @@ export default function Details() {
|
||||
label="Zap"
|
||||
severity="success"
|
||||
outlined
|
||||
onClick={handleZapEvent}
|
||||
pt={{
|
||||
button: {
|
||||
icon: ({ context }) => ({
|
||||
|
Loading…
x
Reference in New Issue
Block a user