A bunch of good stuff

This commit is contained in:
austinkelsay 2024-08-08 16:29:16 -05:00
parent fea5a7b76c
commit 80edbf0905
12 changed files with 410 additions and 169 deletions

View File

@ -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>

View File

@ -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'}
/>
);

View File

@ -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,

View File

@ -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 };
}

View File

@ -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

View File

@ -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 };
}

View File

@ -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);

View File

@ -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`);
}

View 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>
);
}

View File

@ -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

View File

@ -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;

View File

@ -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;