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
@ -1,7 +1,5 @@
|
|||||||
import React, { useState, useEffect, use } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Carousel } from 'primereact/carousel';
|
import { Carousel } from 'primereact/carousel';
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import { useImageProxy } from '@/hooks/useImageProxy';
|
|
||||||
import { parseEvent } from '@/utils/nostr';
|
import { parseEvent } from '@/utils/nostr';
|
||||||
import { useNostr } from '@/hooks/useNostr';
|
import { useNostr } from '@/hooks/useNostr';
|
||||||
import CourseTemplate from '@/components/content/carousels/templates/CourseTemplate';
|
import CourseTemplate from '@/components/content/carousels/templates/CourseTemplate';
|
||||||
@ -27,11 +25,8 @@ const responsiveOptions = [
|
|||||||
|
|
||||||
export default function CoursesCarousel() {
|
export default function CoursesCarousel() {
|
||||||
const [processedCourses, setProcessedCourses] = useState([]);
|
const [processedCourses, setProcessedCourses] = useState([]);
|
||||||
const [screenWidth, setScreenWidth] = useState(null);
|
|
||||||
const [courses, setCourses] = useState([]);
|
const [courses, setCourses] = useState([]);
|
||||||
const router = useRouter();
|
|
||||||
const { fetchCourses, events } = useNostr();
|
const { fetchCourses, events } = useNostr();
|
||||||
const { returnImageProxy } = useImageProxy();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (events && events.courses && events.courses.length > 0) {
|
if (events && events.courses && events.courses.length > 0) {
|
||||||
@ -41,35 +36,6 @@ export default function CoursesCarousel() {
|
|||||||
}
|
}
|
||||||
}, [events]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
const processCourses = courses.map(course => {
|
const processCourses = courses.map(course => {
|
||||||
const { id, content, title, summary, image, published_at } = parseEvent(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 Image from "next/image";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import useResponsiveImageDimensions from "@/hooks/useResponsiveImageDimensions";
|
import useResponsiveImageDimensions from "@/hooks/useResponsiveImageDimensions";
|
||||||
import { formatTimestampToHowLongAgo } from "@/utils/time";
|
import { formatTimestampToHowLongAgo } from "@/utils/time";
|
||||||
import { useImageProxy } from "@/hooks/useImageProxy";
|
import { useImageProxy } from "@/hooks/useImageProxy";
|
||||||
|
import { useNostr } from "@/hooks/useNostr";
|
||||||
|
|
||||||
const CourseTemplate = (course) => {
|
const CourseTemplate = (course) => {
|
||||||
|
const [zaps, setZaps] = useState([]);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { returnImageProxy } = useImageProxy();
|
const { returnImageProxy } = useImageProxy();
|
||||||
const { width, height } = useResponsiveImageDimensions();
|
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 (
|
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={{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">
|
<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 { 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 axios from "axios";
|
||||||
import { useToast } from "./useToast";
|
import { useToast } from "./useToast";
|
||||||
|
|
||||||
@ -20,7 +20,8 @@ export const useNostr = () => {
|
|||||||
resources: [],
|
resources: [],
|
||||||
workshops: [],
|
workshops: [],
|
||||||
courses: [],
|
courses: [],
|
||||||
streams: []
|
streams: [],
|
||||||
|
zaps: []
|
||||||
});
|
});
|
||||||
|
|
||||||
const { showToast } = useToast();
|
const { showToast } = useToast();
|
||||||
@ -53,6 +54,9 @@ export const useNostr = () => {
|
|||||||
try {
|
try {
|
||||||
const sub = pool.current.subscribeMany(relays, filter, {
|
const sub = pool.current.subscribeMany(relays, filter, {
|
||||||
onevent: async (event) => {
|
onevent: async (event) => {
|
||||||
|
if (event.kind === 9735) {
|
||||||
|
console.log('event:', event);
|
||||||
|
}
|
||||||
const shouldInclude = await hasRequiredTags(event.tags);
|
const shouldInclude = await hasRequiredTags(event.tags);
|
||||||
if (shouldInclude) {
|
if (shouldInclude) {
|
||||||
setEvents(prevData => ({
|
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
|
// Fetch resources, workshops, courses, and streams with appropriate filters and update functions
|
||||||
const fetchResources = async () => {
|
const fetchResources = async () => {
|
||||||
const filter = [{ kinds: [30023], authors: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741"] }];
|
const filter = [{ kinds: [30023], authors: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741"] }];
|
||||||
@ -288,6 +370,8 @@ export const useNostr = () => {
|
|||||||
fetchCourses,
|
fetchCourses,
|
||||||
fetchWorkshops,
|
fetchWorkshops,
|
||||||
// fetchStreams,
|
// fetchStreams,
|
||||||
|
zapEvent,
|
||||||
|
fetchZapsForEvent,
|
||||||
getRelayStatuses,
|
getRelayStatuses,
|
||||||
events
|
events
|
||||||
};
|
};
|
||||||
|
@ -27,10 +27,18 @@ export default function Details() {
|
|||||||
const [author, setAuthor] = useState(null);
|
const [author, setAuthor] = useState(null);
|
||||||
|
|
||||||
const { returnImageProxy } = useImageProxy();
|
const { returnImageProxy } = useImageProxy();
|
||||||
const { fetchSingleEvent, fetchKind0 } = useNostr();
|
const { fetchSingleEvent, fetchKind0, zapEvent } = useNostr();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleZapEvent = async () => {
|
||||||
|
if (!event) return;
|
||||||
|
|
||||||
|
const response = await zapEvent(event);
|
||||||
|
|
||||||
|
console.log('zap response:', response);
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (router.isReady) {
|
if (router.isReady) {
|
||||||
const { slug } = router.query;
|
const { slug } = router.query;
|
||||||
@ -112,6 +120,7 @@ export default function Details() {
|
|||||||
label="Zap"
|
label="Zap"
|
||||||
severity="success"
|
severity="success"
|
||||||
outlined
|
outlined
|
||||||
|
onClick={handleZapEvent}
|
||||||
pt={{
|
pt={{
|
||||||
button: {
|
button: {
|
||||||
icon: ({ context }) => ({
|
icon: ({ context }) => ({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user