improvements to details page ui

This commit is contained in:
austinkelsay 2024-03-16 16:37:47 -05:00
parent be6bb02db0
commit 0ab56b009c
5 changed files with 149 additions and 22 deletions

View File

@ -1,7 +1,7 @@
import { useState, useEffect, useRef } from "react";
import { SimplePool, relayInit, nip19 } from "nostr-tools";
import { useDispatch } from "react-redux";
import { addResource, addCourse } from "@/redux/reducers/eventsReducer";
import { addResource, addCourse, addWorkshop, addStream } from "@/redux/reducers/eventsReducer";
import { initialRelays } from "@/redux/reducers/userReducer";
export const useNostr = () => {
@ -88,6 +88,32 @@ export const useNostr = () => {
});
}
const fetchWorkshops = async () => {
const filter = [{kinds: [30023], authors: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741"]}];
const params = {seenOnEnabled: true};
const hasRequiredTags = (eventData) => {
return eventData.some(([tag, value]) => tag === "t" && value === "plebdevs") && eventData.some(([tag, value]) => tag === "t" && value === "workshop");
};
const sub = pool.current.subscribeMany(relays, filter, {
...params,
onevent: (event) => {
if (hasRequiredTags(event.tags)) {
dispatch(addWorkshop(event));
}
},
onerror: (error) => {
console.error("Error fetching workshops:", error);
},
oneose: () => {
console.log("Subscription closed");
sub.close();
}
});
}
const fetchCourses = async () => {
const filter = [{kinds: [30023], authors: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741"]}];
@ -114,6 +140,32 @@ export const useNostr = () => {
});
}
const fetchStreams = async () => {
const filter = [{kinds: [30311], authors: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741"]}];
const params = {seenOnEnabled: true};
const hasRequiredTags = (eventData) => {
return eventData.some(([tag, value]) => tag === "t" && value === "plebdevs");
};
const sub = pool.current.subscribeMany(relays, filter, {
...params,
onevent: (event) => {
if (hasRequiredTags(event.tags)) {
dispatch(addStream(event));
}
},
onerror: (error) => {
console.error("Error fetching streams:", error);
},
oneose: () => {
console.log("Subscription closed");
sub.close();
}
});
}
const fetchSingleEvent = async (id) => {
return new Promise((resolve, reject) => {
const sub = pool.current.subscribeMany(relays, [{ ids: [id] }], {
@ -159,6 +211,7 @@ export const useNostr = () => {
fetchKind0,
fetchResources,
fetchCourses,
fetchWorkshops,
getRelayStatuses,
};
};

View File

@ -1,17 +1,20 @@
import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import { useNostr } from '@/hooks/useNostr';
import { parseEvent } from '@/utils/nostr';
import { parseEvent, findKind0Fields, hexToNpub } from '@/utils/nostr';
import { useImageProxy } from '@/hooks/useImageProxy';
import { Button } from 'primereact/button';
import { Tag } from 'primereact/tag';
import Image from 'next/image';
import 'primeicons/primeicons.css';
export default function Details() {
const [event, setEvent] = useState(null);
const [processedEvent, setProcessedEvent] = useState({});
const [author, setAuthor] = useState(null);
const { returnImageProxy } = useImageProxy();
const { fetchSingleEvent } = useNostr();
const { fetchSingleEvent, fetchKind0 } = useNostr();
const router = useRouter();
@ -31,31 +34,80 @@ export default function Details() {
}, [router.isReady, router.query]);
useEffect(() => {
const fetchAuthor = async (pubkey) => {
const author = await fetchKind0([{ authors: [pubkey], kinds: [0] }], {});
const fields = await findKind0Fields(author);
console.log('fields:', fields);
if (fields) {
setAuthor(fields);
}
}
if (event) {
const { id, content, title, summary, image, published_at } = parseEvent(event);
setProcessedEvent({ id, content, title, summary, image, published_at });
fetchAuthor(event.pubkey);
}
}, [event]);
useEffect(() => {
if (event) {
const { id, pubkey, content, title, summary, image, published_at } = parseEvent(event);
setProcessedEvent({ id, pubkey, content, title, summary, image, published_at });
}
}, [event]);
return (
<div className='flex flex-row justify-between m-4'>
<i className='pi pi-arrow-left cursor-pointer hover:opacity-75' onClick={() => router.push('/')} />
<div className='flex flex-col items-start'>
<div className='flex flex-row justify-start w-full'>
<Tag className='mr-2' value="Primary"></Tag>
<Tag className='mr-2' severity="success" value="Success"></Tag>
<Tag className='mr-2' severity="info" value="Info"></Tag>
<Tag className='mr-2' severity="warning" value="Warning"></Tag>
<Tag className='mr-2' severity="danger" value="Danger"></Tag>
</div>
<h1 className='text-4xl mt-6'>{processedEvent?.title}</h1>
<p className='text-xl mt-6'>{processedEvent?.summary}</p>
<div className='flex flex-row w-full mt-6 items-center'>
<Image
alt="resource thumbnail"
src={returnImageProxy(author?.avatar)}
width={50}
height={50}
className="rounded-full mr-4"
/>
<p className='text-lg'>
Created by{' '}
<a rel='noreferrer noopener' target='_blank' className='text-blue-500 hover:underline'>
{author?.username}
</a>
</p>
</div>
</div>
<div className='flex flex-col'>
{
processedEvent && (
<>
<Image
alt="resource thumbnail"
src={returnImageProxy(processedEvent.image)}
width={344}
height={194}
className="w-full h-full object-cover object-center rounded-lg"
/>
<h2>{processedEvent.title}</h2>
<p>{processedEvent.summary}</p>
</>
)
}
{processedEvent && (
<div className='flex flex-col items-center justify-between rounded-lg h-72 p-4 bg-gray-700 drop-shadow-md'>
<Image
alt="resource thumbnail"
src={returnImageProxy(processedEvent.image)}
width={344}
height={194}
className="object-cover object-center rounded-lg"
/>
<Button
icon="pi pi-bolt"
label="100 sats"
severity="success"
outlined
pt={{
button: {
icon: ({ context }) => ({
className: 'bg-yellow-500'
})
}
}}
/>
</div>
)}
</div>
</div>
);

View File

@ -23,6 +23,8 @@ export const eventsSlice = createSlice({
initialState: {
resources: [],
courses: [],
workshops: [],
streams: [],
},
reducers: {
addResource: (state, action) => {
@ -31,6 +33,12 @@ export const eventsSlice = createSlice({
addCourse: (state, action) => {
addItems(state, action, 'courses');
},
addWorkshop: (state, action) => {
addItems(state, action, 'workshops');
},
addStream: (state, action) => {
addItems(state, action, 'streams');
},
setResources: (state, action) => {
state.resources = action.payload;
},
@ -40,5 +48,5 @@ export const eventsSlice = createSlice({
},
});
export const { addResource, addCourse, setResources, setCourses } = eventsSlice.actions;
export const { addResource, addCourse, setResources, setCourses, addWorkshop, addStream } = eventsSlice.actions;
export default eventsSlice.reducer;

View File

@ -23,3 +23,6 @@
display: none !important;
}
.p-button .pi.pi-bolt {
color: yellow;
}

View File

@ -1,3 +1,5 @@
import { nip19 } from "nostr-tools";
export const findKind0Fields = async (kind0) => {
let fields = {}
@ -31,6 +33,7 @@ export const parseEvent = (event) => {
// Initialize an object to store the extracted data
const eventData = {
id: event.id,
pubkey: event.pubkey || '',
content: event.content || '',
title: '',
summary: '',
@ -53,9 +56,17 @@ export const parseEvent = (event) => {
case 'published_at':
eventData.published_at = tag[1];
break;
// Add cases for any other data you need to extract
case 'author':
eventData.author = tag[1];
break;
default:
break;
}
});
return eventData;
};
export const hexToNpub = (hex) => {
return nip19.npubEncode(hex);
}