mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-04 08:12:02 +00:00
A bunch of good stuff
This commit is contained in:
parent
fea5a7b76c
commit
80edbf0905
@ -7,6 +7,8 @@ import { Button } from "primereact/button";
|
||||
import { useRouter } from "next/router";;
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useToast } from "@/hooks/useToast";
|
||||
import { useNDKContext } from "@/context/NDKContext";
|
||||
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
||||
import dynamic from 'next/dynamic';
|
||||
const MDEditor = dynamic(
|
||||
() => import("@uiw/react-md-editor"),
|
||||
@ -16,7 +18,7 @@ const MDEditor = dynamic(
|
||||
);
|
||||
import 'primeicons/primeicons.css';
|
||||
|
||||
const ResourceForm = ({ draft = null }) => {
|
||||
const ResourceForm = ({ draft = null, isPublished = false }) => {
|
||||
const [title, setTitle] = useState(draft?.title || '');
|
||||
const [summary, setSummary] = useState(draft?.summary || '');
|
||||
const [isPaidResource, setIsPaidResource] = useState(draft?.price ? true : false);
|
||||
@ -29,6 +31,12 @@ const ResourceForm = ({ draft = null }) => {
|
||||
const { data: session, status } = useSession();
|
||||
const { showToast } = useToast();
|
||||
const router = useRouter();
|
||||
const ndk = useNDKContext();
|
||||
|
||||
useEffect(() => {
|
||||
console.log('isPublished', isPublished);
|
||||
console.log('draft', draft);
|
||||
}, [isPublished, draft]);
|
||||
|
||||
useEffect(() => {
|
||||
if (session) {
|
||||
@ -52,6 +60,69 @@ const ResourceForm = ({ draft = null }) => {
|
||||
}
|
||||
}, [draft]);
|
||||
|
||||
const buildEvent = async (draft) => {
|
||||
const dTag = draft.d
|
||||
const event = new NDKEvent(ndk);
|
||||
let encryptedContent;
|
||||
|
||||
if (draft?.price) {
|
||||
// encrypt the content with NEXT_PUBLIC_APP_PRIV_KEY to NEXT_PUBLIC_APP_PUBLIC_KEY
|
||||
encryptedContent = await nip04.encrypt(process.env.NEXT_PUBLIC_APP_PRIV_KEY, process.env.NEXT_PUBLIC_APP_PUBLIC_KEY, draft.content);
|
||||
}
|
||||
|
||||
event.kind = draft?.price ? 30402 : 30023; // Determine kind based on if price is present
|
||||
event.content = draft?.price ? encryptedContent : draft.content;
|
||||
event.created_at = Math.floor(Date.now() / 1000);
|
||||
event.pubkey = user.pubkey;
|
||||
event.tags = [
|
||||
['d', dTag],
|
||||
['title', draft.title],
|
||||
['summary', draft.summary],
|
||||
['image', draft.image],
|
||||
...draft.topics.map(topic => ['t', topic]),
|
||||
['published_at', Math.floor(Date.now() / 1000).toString()],
|
||||
...(draft?.price ? [['price', draft.price.toString()], ['location', `https://plebdevs.com/details/${draft.id}`]] : []),
|
||||
];
|
||||
|
||||
return event;
|
||||
};
|
||||
|
||||
const handlePublishedResource = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// create new object with state fields
|
||||
const updatedDraft = {
|
||||
title,
|
||||
summary,
|
||||
price,
|
||||
content,
|
||||
image: coverImage,
|
||||
topics: [...topics.map(topic => topic.trim().toLowerCase()), 'plebdevs', 'resource']
|
||||
}
|
||||
|
||||
console.log('handlePublishedResource', updatedDraft);
|
||||
|
||||
const event = await buildEvent(updatedDraft);
|
||||
|
||||
console.log('event', event);
|
||||
|
||||
try {
|
||||
await ndk.connect();
|
||||
|
||||
const published = await ndk.publish(event);
|
||||
|
||||
if (published) {
|
||||
showToast('success', 'Success', 'Resource published successfully.');
|
||||
router.push(`/resource/${event.id}`);
|
||||
} else {
|
||||
showToast('error', 'Error', 'Failed to publish resource. Please try again.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
showToast('error', 'Error', 'Failed to publish resource. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
@ -115,7 +186,7 @@ const ResourceForm = ({ draft = null }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<form onSubmit={isPublished && draft ? handlePublishedResource : handleSubmit}>
|
||||
<div className="p-inputgroup flex-1">
|
||||
<InputText value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Title" />
|
||||
</div>
|
||||
|
@ -89,7 +89,7 @@ const UserAvatar = () => {
|
||||
icon="pi pi-user"
|
||||
className="text-[#f8f8ff]"
|
||||
rounded
|
||||
onClick={() => router.push('/login')}
|
||||
onClick={() => router.push('/auth/signin')}
|
||||
size={windowWidth < 768 ? 'small' : 'normal'}
|
||||
/>
|
||||
);
|
||||
|
@ -19,6 +19,19 @@ export const getResourceById = async (id) => {
|
||||
});
|
||||
};
|
||||
|
||||
export async function isResourcePartOfAnyCourse(resourceId) {
|
||||
const courses = await prisma.course.findMany({
|
||||
where: {
|
||||
resources: {
|
||||
some: {
|
||||
id: resourceId
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return courses.length > 0;
|
||||
}
|
||||
|
||||
export const createResource = async (data) => {
|
||||
return await prisma.resource.create({
|
||||
data,
|
||||
|
@ -1,46 +1,71 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useNDKContext } from '@/context/NDKContext';
|
||||
import { useContentIdsQuery } from '@/hooks/apiQueries/useContentIdsQuery';
|
||||
|
||||
const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY
|
||||
const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY;
|
||||
|
||||
export function useCoursesQuery() {
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
|
||||
const { contentIds, contentIdsLoading, contentIdsError, refetchContentIds } = useContentIdsQuery();
|
||||
const ndk = useNDKContext();
|
||||
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
refetchContentIds();
|
||||
}, [refetchContentIds]);
|
||||
|
||||
const hasRequiredProperties = (event) => {
|
||||
if (contentIdsLoading) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasCourseTag = event.tags.some(([tag, value]) => tag === "t" && value === "course");
|
||||
const hasId = contentIds.includes(event.id);
|
||||
return hasCourseTag && hasId;
|
||||
};
|
||||
|
||||
const fetchCoursesFromNDK = async () => {
|
||||
try {
|
||||
console.log('Fetching courses from NDK');
|
||||
if (contentIdsLoading) {
|
||||
return []; // or a loading state indication
|
||||
}
|
||||
if (contentIdsError) {
|
||||
console.error('Error fetching content IDs:', contentIdsError);
|
||||
return [];
|
||||
}
|
||||
if (!contentIds) {
|
||||
return [];
|
||||
}
|
||||
|
||||
await ndk.connect();
|
||||
|
||||
|
||||
const filter = { kinds: [30004], authors: [AUTHOR_PUBKEY] };
|
||||
const events = await ndk.fetchEvents(filter);
|
||||
|
||||
|
||||
if (events && events.size > 0) {
|
||||
const eventsArray = Array.from(events);
|
||||
console.log('eventsArray', eventsArray)
|
||||
// const resources = eventsArray.filter(event => hasRequiredTags(event.tags));
|
||||
// return resources;
|
||||
return eventsArray;
|
||||
const courses = eventsArray.filter(event => hasRequiredProperties(event));
|
||||
return courses;
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error('Error fetching workshops from NDK:', error);
|
||||
console.error('Error fetching courses from NDK:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const { data: courses, isLoading: coursesLoading, error: coursesError, refetch: refetchCourses } = useQuery({
|
||||
queryKey: ['courses', isClient],
|
||||
queryFn: fetchCoursesFromNDK,
|
||||
staleTime: 1000 * 60 * 30, // 30 minutes
|
||||
refetchInterval: 1000 * 60 * 30, // 30 minutes
|
||||
enabled: isClient,
|
||||
})
|
||||
const { data: courses, isLoading: coursesLoading, error: coursesError, refetch: refetchCourses } = useQuery({
|
||||
queryKey: ['courses', isClient],
|
||||
queryFn: fetchCoursesFromNDK,
|
||||
staleTime: 1000 * 60 * 30, // 30 minutes
|
||||
refetchInterval: 1000 * 60 * 30, // 30 minutes
|
||||
enabled: isClient,
|
||||
});
|
||||
|
||||
return { courses, coursesLoading, coursesError, refetchCourses }
|
||||
}
|
||||
return { courses, coursesLoading, coursesError, refetchCourses };
|
||||
}
|
||||
|
@ -1,25 +1,47 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useNDKContext } from '@/context/NDKContext';
|
||||
import { useContentIdsQuery } from '@/hooks/apiQueries/useContentIdsQuery';
|
||||
|
||||
const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY
|
||||
|
||||
export function useResourcesQuery() {
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
const ndk = useNDKContext();
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
}, []);
|
||||
const { contentIds, contentIdsLoading, contentIdsError, refetchContentIds } = useContentIdsQuery();
|
||||
const ndk = useNDKContext();
|
||||
|
||||
const hasRequiredTags = (tags) => {
|
||||
const hasPlebDevs = tags.some(([tag, value]) => tag === "t" && value === "plebdevs");
|
||||
const hasWorkshop = tags.some(([tag, value]) => tag === "t" && value === "resource");
|
||||
return hasPlebDevs && hasWorkshop;
|
||||
};
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
}, []);
|
||||
|
||||
const fetchResourcesFromNDK = async () => {
|
||||
try {
|
||||
useEffect(() => {
|
||||
refetchContentIds();
|
||||
}, [refetchContentIds]);
|
||||
|
||||
const hasRequiredProperties = (event) => {
|
||||
if (!contentIds) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasPlebDevs = event.tags.some(([tag, value]) => tag === "t" && value === "plebdevs");
|
||||
const hasWorkshop = event.tags.some(([tag, value]) => tag === "t" && value === "resource");
|
||||
const hasId = contentIds.includes(event.id);
|
||||
return hasPlebDevs && hasWorkshop && hasId;
|
||||
};
|
||||
|
||||
const fetchResourcesFromNDK = async () => {
|
||||
try {
|
||||
if (contentIdsLoading) {
|
||||
return []; // or a loading state indication
|
||||
}
|
||||
if (contentIdsError) {
|
||||
console.error('Error fetching content IDs:', contentIdsError);
|
||||
return [];
|
||||
}
|
||||
if (!contentIds) {
|
||||
return [];
|
||||
}
|
||||
console.log('Fetching workshops from NDK');
|
||||
await ndk.connect();
|
||||
|
||||
@ -27,19 +49,19 @@ const fetchResourcesFromNDK = async () => {
|
||||
const events = await ndk.fetchEvents(filter);
|
||||
|
||||
if (events && events.size > 0) {
|
||||
const eventsArray = Array.from(events);
|
||||
console.log('eventsArray', eventsArray)
|
||||
const resources = eventsArray.filter(event => hasRequiredTags(event.tags));
|
||||
return resources;
|
||||
const eventsArray = Array.from(events);
|
||||
console.log('eventsArray', eventsArray)
|
||||
const resources = eventsArray.filter(event => hasRequiredProperties(event));
|
||||
return resources;
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
} catch (error) {
|
||||
console.error('Error fetching workshops from NDK:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const { data: resources, isLoading: resourcesLoading, error: resourcesError, refetch: refetchResources } = useQuery({
|
||||
const { data: resources, isLoading: resourcesLoading, error: resourcesError, refetch: refetchResources } = useQuery({
|
||||
queryKey: ['resources', isClient],
|
||||
queryFn: fetchResourcesFromNDK,
|
||||
staleTime: 1000 * 60 * 30, // 30 minutes
|
||||
|
@ -1,51 +1,72 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useNDKContext } from '@/context/NDKContext';
|
||||
import { useContentIdsQuery } from '@/hooks/apiQueries/useContentIdsQuery';
|
||||
|
||||
const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY
|
||||
const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY;
|
||||
|
||||
export function useWorkshopsQuery() {
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
|
||||
const { contentIds, contentIdsLoading, contentIdsError, refetchContentIds } = useContentIdsQuery();
|
||||
const ndk = useNDKContext();
|
||||
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
}, []);
|
||||
|
||||
const hasRequiredTags = (tags) => {
|
||||
const hasPlebDevs = tags.some(([tag, value]) => tag === "t" && value === "plebdevs");
|
||||
const hasWorkshop = tags.some(([tag, value]) => tag === "t" && value === "workshop");
|
||||
return hasPlebDevs && hasWorkshop;
|
||||
};
|
||||
useEffect(() => {
|
||||
refetchContentIds();
|
||||
}, [refetchContentIds]);
|
||||
|
||||
const fetchWorkshopsFromNDK = async () => {
|
||||
try {
|
||||
console.log('Fetching workshops from NDK');
|
||||
await ndk.connect();
|
||||
|
||||
const filter = { kinds: [30023, 30402], authors: [AUTHOR_PUBKEY] };
|
||||
const events = await ndk.fetchEvents(filter);
|
||||
|
||||
if (events && events.size > 0) {
|
||||
const eventsArray = Array.from(events);
|
||||
console.log('eventsArray', eventsArray)
|
||||
const resources = eventsArray.filter(event => hasRequiredTags(event.tags));
|
||||
return resources;
|
||||
const hasRequiredProperties = (event) => {
|
||||
if (contentIdsLoading) {
|
||||
return false;
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error('Error fetching workshops from NDK:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
const hasPlebDevs = event.tags.some(([tag, value]) => tag === "t" && value === "plebdevs");
|
||||
const hasWorkshop = event.tags.some(([tag, value]) => tag === "t" && value === "workshop");
|
||||
const hasId = contentIds.includes(event.id);
|
||||
return hasPlebDevs && hasWorkshop && hasId;
|
||||
};
|
||||
|
||||
const { data: workshops, isLoading: workshopsLoading, error: workshopsError, refetch: refetchWorkshops } = useQuery({
|
||||
queryKey: ['workshops', isClient],
|
||||
queryFn: fetchWorkshopsFromNDK,
|
||||
staleTime: 1000 * 60 * 30, // 30 minutes
|
||||
refetchInterval: 1000 * 60 * 30, // 30 minutes
|
||||
enabled: isClient,
|
||||
})
|
||||
const fetchWorkshopsFromNDK = async () => {
|
||||
try {
|
||||
if (contentIdsLoading) {
|
||||
return []; // or a loading state indication
|
||||
}
|
||||
if (contentIdsError) {
|
||||
console.error('Error fetching content IDs:', contentIdsError);
|
||||
return [];
|
||||
}
|
||||
if (!contentIds) {
|
||||
return [];
|
||||
}
|
||||
console.log('Fetching workshops from NDK');
|
||||
await ndk.connect();
|
||||
|
||||
return { workshops, workshopsLoading, workshopsError, refetchWorkshops }
|
||||
}
|
||||
const filter = { kinds: [30023, 30402], authors: [AUTHOR_PUBKEY] };
|
||||
const events = await ndk.fetchEvents(filter);
|
||||
|
||||
if (events && events.size > 0) {
|
||||
const eventsArray = Array.from(events);
|
||||
console.log('eventsArray', eventsArray);
|
||||
const workshops = eventsArray.filter(event => hasRequiredProperties(event));
|
||||
return workshops;
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error('Error fetching workshops from NDK:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const { data: workshops, isLoading: workshopsLoading, error: workshopsError, refetch: refetchWorkshops } = useQuery({
|
||||
queryKey: ['workshops', isClient],
|
||||
queryFn: fetchWorkshopsFromNDK,
|
||||
staleTime: 1000 * 60 * 30, // 30 minutes
|
||||
refetchInterval: 1000 * 60 * 30, // 30 minutes
|
||||
enabled: isClient,
|
||||
});
|
||||
|
||||
return { workshops, workshopsLoading, workshopsError, refetchWorkshops };
|
||||
}
|
||||
|
@ -40,11 +40,13 @@ export default NextAuth({
|
||||
// Check if user exists, create if not
|
||||
const response = await axios.get(`${BASE_URL}/api/users/${credentials.pubkey}`);
|
||||
if (response.status === 200 && response.data) {
|
||||
return response.data;
|
||||
const fields = await findKind0Fields(profile);
|
||||
return { pubkey: credentials.pubkey, ...fields };
|
||||
} else if (response.status === 204) {
|
||||
// Create user
|
||||
if (profile) {
|
||||
const fields = await findKind0Fields(profile);
|
||||
console.log('FEEEEELDS', fields);
|
||||
const payload = { pubkey: credentials.pubkey, ...fields };
|
||||
|
||||
const createUserResponse = await axios.post(`${BASE_URL}/api/users`, payload);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { getResourceById, updateResource, deleteResource } from "@/db/models/resourceModels";
|
||||
import { getResourceById, updateResource, deleteResource, isResourcePartOfAnyCourse } from "@/db/models/resourceModels";
|
||||
|
||||
export default async function handler(req, res) {
|
||||
const { slug } = req.query;
|
||||
@ -23,13 +23,17 @@ export default async function handler(req, res) {
|
||||
}
|
||||
} else if (req.method === 'DELETE') {
|
||||
try {
|
||||
await deleteResource(slug);
|
||||
res.status(204).end();
|
||||
const isPartOfAnyCourse = await isResourcePartOfAnyCourse(slug);
|
||||
if (isPartOfAnyCourse) {
|
||||
res.status(400).json({ error: 'Resource is part of one or more courses' });
|
||||
} else {
|
||||
await deleteResource(slug);
|
||||
res.status(204).end();
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
} else {
|
||||
// Handle any other HTTP method
|
||||
res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
|
||||
res.status(405).end(`Method ${req.method} Not Allowed`);
|
||||
}
|
||||
|
47
src/pages/details/[slug]/edit.js
Normal file
47
src/pages/details/[slug]/edit.js
Normal file
@ -0,0 +1,47 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { parseEvent } from "@/utils/nostr";
|
||||
import ResourceForm from "@/components/forms/ResourceForm";
|
||||
import WorkshopForm from "@/components/forms/WorkshopForm";
|
||||
import CourseForm from "@/components/forms/CourseForm";
|
||||
import { useNDKContext } from "@/context/NDKContext";
|
||||
import { useToast } from "@/hooks/useToast";
|
||||
|
||||
export default function Edit() {
|
||||
const [event, setEvent] = useState(null);
|
||||
|
||||
const ndk = useNDKContext();
|
||||
const router = useRouter();
|
||||
const { showToast } = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
if (router.isReady) {
|
||||
const { slug } = router.query;
|
||||
|
||||
const fetchEvent = async () => {
|
||||
await ndk.connect();
|
||||
|
||||
const fetchedEvent = await ndk.fetchEvent(slug);
|
||||
|
||||
if (fetchedEvent) {
|
||||
const parsedEvent = parseEvent(fetchedEvent);
|
||||
console.log('parsedEvent:', parsedEvent);
|
||||
setEvent(parsedEvent);
|
||||
} else {
|
||||
showToast('error', 'Error', 'Event not found.');
|
||||
}
|
||||
}
|
||||
|
||||
fetchEvent();
|
||||
}
|
||||
}, [router.isReady, router.query, ndk, showToast]);
|
||||
|
||||
return (
|
||||
<div className="w-[80vw] max-w-[80vw] mx-auto my-8 flex flex-col justify-center">
|
||||
<h2 className="text-center mb-8">Edit Published Event</h2>
|
||||
{event?.topics.includes('course') && <CourseForm draft={event} isPublished />}
|
||||
{event?.topics.includes('workshop') && <WorkshopForm draft={event} isPublished />}
|
||||
{event?.topics.includes('resource') && <ResourceForm draft={event} isPublished />}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,15 +1,18 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import { useRouter } from 'next/router';
|
||||
import { parseEvent, findKind0Fields, hexToNpub } from '@/utils/nostr';
|
||||
import { parseEvent, findKind0Fields } from '@/utils/nostr';
|
||||
import { useImageProxy } from '@/hooks/useImageProxy';
|
||||
import { getSatAmountFromInvoice } from '@/utils/lightning';
|
||||
import ZapDisplay from '@/components/zaps/ZapDisplay';
|
||||
import { Tag } from 'primereact/tag';
|
||||
import { Button } from 'primereact/button';
|
||||
import { nip19, nip04 } from 'nostr-tools';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import Image from 'next/image';
|
||||
import dynamic from 'next/dynamic';
|
||||
import ZapThreadsWrapper from '@/components/ZapThreadsWrapper';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { useNDKContext } from '@/context/NDKContext';
|
||||
import { useZapsSubscription } from '@/hooks/nostrQueries/zaps/useZapsSubscription';
|
||||
import 'primeicons/primeicons.css';
|
||||
@ -38,11 +41,13 @@ export default function Details() {
|
||||
const [zapAmount, setZapAmount] = useState(null);
|
||||
const [paidResource, setPaidResource] = useState(false);
|
||||
const [decryptedContent, setDecryptedContent] = useState(null);
|
||||
const [authorView, setAuthorView] = useState(false);
|
||||
|
||||
const ndk = useNDKContext();
|
||||
const { data: session, status } = useSession();
|
||||
const [user, setUser] = useState(null);
|
||||
const { returnImageProxy } = useImageProxy();
|
||||
const { showToast } = useToast();
|
||||
const { zaps, zapsLoading, zapsError } = useZapsSubscription({ event: processedEvent });
|
||||
|
||||
const router = useRouter();
|
||||
@ -102,6 +107,9 @@ export default function Details() {
|
||||
|
||||
if (event) {
|
||||
setEvent(event);
|
||||
if (user && user.pubkey === event.pubkey) {
|
||||
setAuthorView(true);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching event:', error);
|
||||
@ -111,7 +119,7 @@ export default function Details() {
|
||||
fetchEvent(slug);
|
||||
}
|
||||
}
|
||||
}, [router.isReady, router.query, ndk]);
|
||||
}, [router.isReady, router.query, ndk, user]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAuthor = async (pubkey) => {
|
||||
@ -171,6 +179,25 @@ export default function Details() {
|
||||
setZapAmount(total);
|
||||
}, [zaps]);
|
||||
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
const response = await axios.delete(`/api/resources/${processedEvent.id}`);
|
||||
if (response.status === 204) {
|
||||
showToast('success', 'Success', 'Resource deleted successfully.');
|
||||
router.push('/');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data && error.response.data.error.includes("Invalid `prisma.resource.delete()`")) {
|
||||
showToast('error', 'Error', 'Resource cannot be deleted because it is part of a course, delete the course first.');
|
||||
}
|
||||
else if (error.response && error.response.data && error.response.data.error) {
|
||||
showToast('error', 'Error', error.response.data.error);
|
||||
} else {
|
||||
showToast('error', 'Error', 'Failed to delete resource. Please try again.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='w-full px-24 pt-12 mx-auto mt-4 max-tab:px-0 max-mob:px-0 max-tab:pt-2 max-mob:pt-2'>
|
||||
<div className='w-full flex flex-row justify-between max-tab:flex-col max-mob:flex-col'>
|
||||
@ -227,6 +254,14 @@ export default function Details() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{authorView && (
|
||||
<div className='w-[75vw] mx-auto flex flex-row justify-end mt-12'>
|
||||
<div className='w-fit flex flex-row justify-between'>
|
||||
<Button onClick={() => router.push(`/details/${processedEvent.id}/edit`)} label="Edit" severity='warning' outlined className="w-auto m-2" />
|
||||
<Button onClick={handleDelete} label="Delete" severity='danger' outlined className="w-auto m-2 mr-0" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{typeof window !== 'undefined' && nAddress !== null && (
|
||||
<div className='px-24'>
|
||||
<ZapThreadsWrapper
|
@ -1,105 +1,105 @@
|
||||
import React, { useRef, useState, useEffect, use } from "react";
|
||||
import React, { useRef, useState, useEffect } from "react";
|
||||
import { Button } from "primereact/button";
|
||||
import { DataTable } from "primereact/datatable";
|
||||
import { Menu } from "primereact/menu";
|
||||
import { Column } from "primereact/column";
|
||||
import { useImageProxy } from "@/hooks/useImageProxy";
|
||||
import { useRouter } from "next/router";
|
||||
import { useSession } from 'next-auth/react';
|
||||
import UserContent from "@/components/profile/UserContent";
|
||||
import Image from "next/image";
|
||||
import BitcoinConnectButton from "@/components/profile/BitcoinConnect";
|
||||
|
||||
const Profile = () => {
|
||||
const { data: session, status } = useSession();
|
||||
const [user, setUser] = useState(null);
|
||||
const { returnImageProxy } = useImageProxy();
|
||||
const menu = useRef(null);
|
||||
const [user, setUser] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (session) {
|
||||
setUser(session.user);
|
||||
}
|
||||
}, [session]);
|
||||
const { data: session, status } = useSession();
|
||||
const { returnImageProxy } = useImageProxy();
|
||||
const menu = useRef(null);
|
||||
|
||||
const purchases = [];
|
||||
useEffect(() => {
|
||||
if (session) {
|
||||
setUser(session.user);
|
||||
}
|
||||
}, [session]);
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
label: "Edit",
|
||||
icon: "pi pi-pencil",
|
||||
command: () => {
|
||||
// Add your edit functionality here
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Delete",
|
||||
icon: "pi pi-trash",
|
||||
command: () => {
|
||||
// Add your delete functionality here
|
||||
},
|
||||
},
|
||||
];
|
||||
const purchases = [];
|
||||
|
||||
const header = (
|
||||
<div className="flex flex-wrap align-items-center justify-content-between gap-2">
|
||||
<span className="text-xl text-900 font-bold text-white">Purchases</span>
|
||||
</div>
|
||||
);
|
||||
const menuItems = [
|
||||
{
|
||||
label: "Edit",
|
||||
icon: "pi pi-pencil",
|
||||
command: () => {
|
||||
// Add your edit functionality here
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Delete",
|
||||
icon: "pi pi-trash",
|
||||
command: () => {
|
||||
// Add your delete functionality here
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
user && (
|
||||
<div className="w-[90vw] mx-auto max-tab:w-[100vw] max-mob:w-[100vw]">
|
||||
<div className="w-[85vw] flex flex-col justify-center mx-auto max-tab:w-[100vw] max-mob:w-[100vw]">
|
||||
<div className="relative flex w-full items-center justify-center">
|
||||
<Image
|
||||
alt="user's avatar"
|
||||
src={returnImageProxy(user.avatar, user.pubkey)}
|
||||
width={100}
|
||||
height={100}
|
||||
className="rounded-full my-4"
|
||||
/>
|
||||
<i
|
||||
className="pi pi-ellipsis-h absolute right-24 text-2xl my-4 cursor-pointer hover:opacity-75"
|
||||
onClick={(e) => menu.current.toggle(e)}
|
||||
></i>
|
||||
<Menu model={menuItems} popup ref={menu} />
|
||||
</div>
|
||||
|
||||
<h1 className="text-center text-2xl my-2">
|
||||
{user.username || "Anon"}
|
||||
</h1>
|
||||
<h2 className="text-center text-xl my-2 truncate max-tab:px-4 max-mob:px-4">
|
||||
{user.pubkey}
|
||||
</h2>
|
||||
<div className="flex flex-col w-1/2 mx-auto my-4 justify-between items-center">
|
||||
<h2>Connect Your Lightning Wallet</h2>
|
||||
<BitcoinConnectButton />
|
||||
</div>
|
||||
<div className="flex flex-col w-1/2 mx-auto my-4 justify-between items-center">
|
||||
<h2>Subscription</h2>
|
||||
<p className="text-center">You currently have no active subscription</p>
|
||||
<Button
|
||||
label="Subscribe"
|
||||
className="p-button-raised p-button-success w-auto my-2 text-[#f8f8ff]"
|
||||
/>
|
||||
</div>
|
||||
const header = (
|
||||
<div className="flex flex-wrap align-items-center justify-content-between gap-2">
|
||||
<span className="text-xl text-900 font-bold text-white">Purchases</span>
|
||||
</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>
|
||||
<UserContent />
|
||||
</div>
|
||||
)
|
||||
);
|
||||
);
|
||||
|
||||
return (
|
||||
user && (
|
||||
<div className="w-[90vw] mx-auto max-tab:w-[100vw] max-mob:w-[100vw]">
|
||||
<div className="w-[85vw] flex flex-col justify-center mx-auto max-tab:w-[100vw] max-mob:w-[100vw]">
|
||||
<div className="relative flex w-full items-center justify-center">
|
||||
<Image
|
||||
alt="user's avatar"
|
||||
src={returnImageProxy(user.avatar, user.pubkey)}
|
||||
width={100}
|
||||
height={100}
|
||||
className="rounded-full my-4"
|
||||
/>
|
||||
<i
|
||||
className="pi pi-ellipsis-h absolute right-24 text-2xl my-4 cursor-pointer hover:opacity-75"
|
||||
onClick={(e) => menu.current.toggle(e)}
|
||||
></i>
|
||||
<Menu model={menuItems} popup ref={menu} />
|
||||
</div>
|
||||
|
||||
<h1 className="text-center text-2xl my-2">
|
||||
{user.username || "Anon"}
|
||||
</h1>
|
||||
<h2 className="text-center text-xl my-2 truncate max-tab:px-4 max-mob:px-4">
|
||||
{user.pubkey}
|
||||
</h2>
|
||||
<div className="flex flex-col w-1/2 mx-auto my-4 justify-between items-center">
|
||||
<h2>Connect Your Lightning Wallet</h2>
|
||||
<BitcoinConnectButton />
|
||||
</div>
|
||||
<div className="flex flex-col w-1/2 mx-auto my-4 justify-between items-center">
|
||||
<h2>Subscription</h2>
|
||||
<p className="text-center">You currently have no active subscription</p>
|
||||
<Button
|
||||
label="Subscribe"
|
||||
className="p-button-raised p-button-success w-auto my-2 text-[#f8f8ff]"
|
||||
/>
|
||||
</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>
|
||||
<UserContent />
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default Profile;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { nip19 } from "nostr-tools";
|
||||
|
||||
export const findKind0Fields = async (kind0) => {
|
||||
console.log('kind0', kind0);
|
||||
let fields = {}
|
||||
|
||||
const usernameProperties = ['name', 'displayName', 'display_name', 'username', 'handle', 'alias'];
|
||||
@ -20,7 +21,7 @@ export const findKind0Fields = async (kind0) => {
|
||||
fields.username = username;
|
||||
}
|
||||
|
||||
const avatar = findTruthyPropertyValue(kind0, ['picture', 'avatar', 'profilePicture', 'profile_picture']);
|
||||
const avatar = findTruthyPropertyValue(kind0, ['picture', 'avatar', 'profilePicture', 'profile_picture', 'image']);
|
||||
|
||||
if (avatar) {
|
||||
fields.avatar = avatar;
|
||||
|
Loading…
x
Reference in New Issue
Block a user