Added generic button

This commit is contained in:
austinkelsay 2024-09-10 15:44:08 -05:00
parent 046a130efa
commit 7c8273d663
37 changed files with 368 additions and 427 deletions

View File

@ -6,6 +6,7 @@ import { initializeBitcoinConnect } from './BitcoinConnect';
import { LightningAddress } from '@getalby/lightning-tools'; import { LightningAddress } from '@getalby/lightning-tools';
import { useToast } from '@/hooks/useToast'; import { useToast } from '@/hooks/useToast';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import GenericButton from '@/components/buttons/GenericButton';
import axios from 'axios'; // Import axios for API calls import axios from 'axios'; // Import axios for API calls
const Payment = dynamic( const Payment = dynamic(
@ -75,7 +76,7 @@ const CoursePaymentButton = ({ lnAddress, amount, onSuccess, onError, courseId }
return ( return (
<> <>
<Button <GenericButton
label={`${amount} sats`} label={`${amount} sats`}
onClick={() => setDialogVisible(true)} onClick={() => setDialogVisible(true)}
disabled={!invoice} disabled={!invoice}

View File

@ -1,12 +1,12 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { Button } from 'primereact/button';
import { Dialog } from 'primereact/dialog'; import { Dialog } from 'primereact/dialog';
import { initializeBitcoinConnect } from './BitcoinConnect'; import { initializeBitcoinConnect } from './BitcoinConnect';
import { LightningAddress } from '@getalby/lightning-tools'; import { LightningAddress } from '@getalby/lightning-tools';
import { useToast } from '@/hooks/useToast'; import { useToast } from '@/hooks/useToast';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import axios from 'axios'; import axios from 'axios';
import GenericButton from '@/components/buttons/GenericButton';
const Payment = dynamic( const Payment = dynamic(
() => import('@getalby/bitcoin-connect-react').then((mod) => mod.Payment), () => import('@getalby/bitcoin-connect-react').then((mod) => mod.Payment),
@ -73,7 +73,7 @@ const ResourcePaymentButton = ({ lnAddress, amount, onSuccess, onError, resource
return ( return (
<> <>
<Button <GenericButton
label={`${amount} sats`} label={`${amount} sats`}
icon="pi pi-wallet" icon="pi pi-wallet"
onClick={() => setDialogVisible(true)} onClick={() => setDialogVisible(true)}

View File

@ -10,6 +10,7 @@ import { useRouter } from 'next/router';
import { Divider } from 'primereact/divider'; import { Divider } from 'primereact/divider';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import AlbyButton from '@/components/buttons/AlbyButton'; import AlbyButton from '@/components/buttons/AlbyButton';
import GenericButton from '@/components/buttons/GenericButton';
import axios from 'axios'; import axios from 'axios';
import Image from 'next/image'; import Image from 'next/image';
@ -208,7 +209,7 @@ const SubscriptionPaymentButtons = ({ onSuccess, onError, onRecurringSubscriptio
{!invoice && ( {!invoice && (
<div className="w-full flex flex-row justify-between"> <div className="w-full flex flex-row justify-between">
{(oneTime || (!oneTime && !recurring)) && ( {(oneTime || (!oneTime && !recurring)) && (
<Button <GenericButton
label="Pay as you go" label="Pay as you go"
icon="pi pi-bolt" icon="pi pi-bolt"
onClick={async () => { onClick={async () => {
@ -220,7 +221,7 @@ const SubscriptionPaymentButtons = ({ onSuccess, onError, onRecurringSubscriptio
/> />
)} )}
{(recurring || (!oneTime && !recurring)) && ( {(recurring || (!oneTime && !recurring)) && (
<Button <GenericButton
label="Setup Recurring Subscription" label="Setup Recurring Subscription"
icon={ icon={
<Image <Image
@ -253,7 +254,7 @@ const SubscriptionPaymentButtons = ({ onSuccess, onError, onRecurringSubscriptio
placeholder="Enter NWC URL" placeholder="Enter NWC URL"
className="w-full p-2 mb-4 border rounded" className="w-full p-2 mb-4 border rounded"
/> />
<Button <GenericButton
label="Submit" label="Submit"
onClick={handleManualNwcSubmit} onClick={handleManualNwcSubmit}
className="mt-4 w-fit text-[#f8f8ff]" className="mt-4 w-fit text-[#f8f8ff]"

View File

@ -0,0 +1,11 @@
import { Button } from 'primereact/button';
import useWindowWidth from '@/hooks/useWindowWidth';
const GenericButton = ({ label, icon, onClick, severity, size, className, outlined = false, rounded = false, disabled = false, tooltip = null, tooltipOptions = null }) => {
const windowWidth = useWindowWidth();
return (
<Button label={label} icon={icon} onClick={onClick} severity={severity} size={size || (windowWidth < 768 ? 'small' : 'normal')} className={className} outlined={outlined} rounded={rounded} disabled={disabled} tooltip={tooltip} tooltipOptions={tooltipOptions} />
);
}
export default GenericButton;

View File

@ -2,21 +2,19 @@ import React from "react";
import Image from "next/image"; import Image from "next/image";
import { useImageProxy } from "@/hooks/useImageProxy"; import { useImageProxy } from "@/hooks/useImageProxy";
import { formatUnixTimestamp } from "@/utils/time"; import { formatUnixTimestamp } from "@/utils/time";
import { Button } from 'primereact/button'; import GenericButton from "@/components/buttons/GenericButton";
const SelectedContentItem = ({ content, onRemove }) => { const SelectedContentItem = ({ content, onRemove }) => {
const { returnImageProxy } = useImageProxy(); const { returnImageProxy } = useImageProxy();
return ( return (
<div className="w-full border-2 rounded-lg border-gray-700 p-2 rounded-tr-none rounded-br-none relative"> <div className="w-full border-2 rounded-lg border-gray-700 p-2 rounded-tr-none rounded-br-none relative">
<Button <GenericButton
icon="pi pi-times" icon="pi pi-times"
className="absolute top-2 right-2 py-1 px-2 w-auto h-auto" className="absolute top-2 right-2 py-1 px-2 w-auto h-auto"
severity="danger" severity="danger"
size="small" size="small"
rounded rounded
onClick={onRemove} onClick={onRemove}
aria-label="Remove"
/> />
<div className="flex flex-row gap-4"> <div className="flex flex-row gap-4">
<Image <Image

View File

@ -25,7 +25,7 @@ const CourseTemplate = ({ course }) => {
return ( return (
<div <div
className="flex flex-col items-center mx-auto px-4 mt-8 rounded-md" className="flex flex-col items-center mx-auto px-4 mt-8 rounded-md max-tab:px-0"
> >
{/* Wrap the image in a div with a relative class with a padding-bottom of 56.25% representing the aspect ratio of 16:9 */} {/* Wrap the image in a div with a relative class with a padding-bottom of 56.25% representing the aspect ratio of 16:9 */}
<div <div

View File

@ -26,7 +26,7 @@ const ResourceTemplate = ({ resource }) => {
return ( return (
<div <div
className="flex flex-col items-center mx-auto px-4 mt-8 rounded-md" className="flex flex-col items-center mx-auto px-4 mt-8 rounded-md max-tab:px-0"
> >
{/* Wrap the image in a div with a relative class with a padding-bottom of 56.25% representing the aspect ratio of 16:9 */} {/* Wrap the image in a div with a relative class with a padding-bottom of 56.25% representing the aspect ratio of 16:9 */}
<div <div

View File

@ -24,7 +24,7 @@ const WorkshopTemplate = ({ workshop }) => {
if (zapsError) return <div>Error: {zapsError}</div>; if (zapsError) return <div>Error: {zapsError}</div>;
return ( return (
<div className="flex flex-col items-center mx-auto px-4 mt-8 rounded-md"> <div className="flex flex-col items-center mx-auto px-4 mt-8 rounded-md max-tab:px-0">
<div <div
onClick={() => router.replace(`/details/${workshop.id}`)} onClick={() => router.replace(`/details/${workshop.id}`)}
className="relative w-full h-0 hover:opacity-80 transition-opacity duration-300 cursor-pointer" className="relative w-full h-0 hover:opacity-80 transition-opacity duration-300 cursor-pointer"

View File

@ -2,7 +2,7 @@ import React, { useEffect, useState, useCallback, useRef } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useImageProxy } from '@/hooks/useImageProxy'; import { useImageProxy } from '@/hooks/useImageProxy';
import { Tag } from 'primereact/tag'; import { Tag } from 'primereact/tag';
import { Button } from 'primereact/button'; import GenericButton from '@/components/buttons/GenericButton';
import Image from 'next/image'; import Image from 'next/image';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import axios from 'axios'; import axios from 'axios';
@ -407,9 +407,9 @@ export default function DraftCourseDetails({ processedEvent, draftId, lessons })
</div> </div>
<div className='w-[75vw] mx-auto flex flex-row justify-end mt-12'> <div className='w-[75vw] mx-auto flex flex-row justify-end mt-12'>
<div className='w-fit flex flex-row justify-between'> <div className='w-fit flex flex-row justify-between'>
<Button onClick={handleSubmit} label="Publish" severity='success' outlined className="w-auto m-2" /> <GenericButton onClick={handleSubmit} label="Publish" severity='success' outlined className="w-auto m-2" />
<Button onClick={() => router.push(`/course/${draftId}/draft/edit`)} label="Edit" severity='warning' outlined className="w-auto m-2" /> <GenericButton onClick={() => router.push(`/course/${draftId}/draft/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" /> <GenericButton onClick={handleDelete} label="Delete" severity='danger' outlined className="w-auto m-2 mr-0" />
</div> </div>
</div> </div>
<div className='w-[75vw] mx-auto mt-12 p-12 border-t-2 border-gray-300 max-tab:p-0 max-mob:p-0 max-tab:max-w-[100vw] max-mob:max-w-[100vw]'> <div className='w-[75vw] mx-auto mt-12 p-12 border-t-2 border-gray-300 max-tab:p-0 max-mob:p-0 max-tab:max-w-[100vw] max-mob:max-w-[100vw]'>

View File

@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Tag } from "primereact/tag"; import { Tag } from "primereact/tag";
import { Message } from "primereact/message"; import { Message } from "primereact/message";
import { Button } from "primereact/button"; import GenericButton from "@/components/buttons/GenericButton";
import Image from "next/image"; import Image from "next/image";
import { useImageProxy } from "@/hooks/useImageProxy"; import { useImageProxy } from "@/hooks/useImageProxy";
import { formatDateTime, formatUnixTimestamp } from "@/utils/time"; import { formatDateTime, formatUnixTimestamp } from "@/utils/time";
@ -87,14 +87,14 @@ const DraftCourseLesson = ({ lesson, course }) => {
{isPublished ? ( {isPublished ? (
<> <>
<Message severity="success" text="published" className="w-auto m-2" /> <Message severity="success" text="published" className="w-auto m-2" />
<Button onClick={() => router.push(`/details/${lesson.id}`)} label="View" outlined className="w-auto m-2" /> <GenericButton onClick={() => router.push(`/details/${lesson.id}`)} label="View" outlined className="w-auto m-2" />
<Button onClick={() => router.push(`/details/${lesson.id}/edit`)} label="Edit" severity='warning' outlined className="w-auto m-2" /> <GenericButton onClick={() => router.push(`/details/${lesson.id}/edit`)} label="Edit" severity='warning' outlined className="w-auto m-2" />
</> </>
) : ( ) : (
<> <>
<Message severity="info" text="draft (unpublished)" /> <Message severity="info" text="draft (unpublished)" />
<Button onClick={() => router.push(`/draft/${lesson.id}`)} label="View" outlined className="w-auto m-2" /> <GenericButton onClick={() => router.push(`/draft/${lesson.id}`)} label="View" outlined className="w-auto m-2" />
<Button onClick={() => router.push(`/draft/${lesson.id}/edit`)} label="Edit" severity='warning' outlined className="w-auto m-2" /> <GenericButton onClick={() => router.push(`/draft/${lesson.id}/edit`)} label="Edit" severity='warning' outlined className="w-auto m-2" />
</> </>
)} )}
</div> </div>

View File

@ -2,7 +2,7 @@ import React, {useEffect} from "react";
import Image from "next/image"; import Image from "next/image";
import { useImageProxy } from "@/hooks/useImageProxy"; import { useImageProxy } from "@/hooks/useImageProxy";
import { formatUnixTimestamp } from "@/utils/time"; import { formatUnixTimestamp } from "@/utils/time";
import { Button } from "primereact/button"; import GenericButton from "@/components/buttons/GenericButton";
const ContentDropdownItem = ({ content, onSelect }) => { const ContentDropdownItem = ({ content, onSelect }) => {
const { returnImageProxy } = useImageProxy(); const { returnImageProxy } = useImageProxy();
@ -25,7 +25,7 @@ const ContentDropdownItem = ({ content, onSelect }) => {
</div> </div>
</div> </div>
<div className="flex flex-col justify-end"> <div className="flex flex-col justify-end">
<Button label="Select" onClick={() => onSelect(content)} /> <GenericButton label="Select" onClick={() => onSelect(content)} className="text-[#f8f8ff]" />
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
import React, {useEffect} from "react"; import React, {useEffect} from "react";
import Image from "next/image"; import Image from "next/image";
import { Button } from "primereact/button"; import GenericButton from "@/components/buttons/GenericButton";
import { useImageProxy } from "@/hooks/useImageProxy"; import { useImageProxy } from "@/hooks/useImageProxy";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { Divider } from 'primereact/divider'; import { Divider } from 'primereact/divider';
@ -44,7 +44,7 @@ const ContentListItem = (content) => {
<span>{content.summary}</span> <span>{content.summary}</span>
</div> </div>
<div className="text-right"> <div className="text-right">
<Button <GenericButton
onClick={handleClick} onClick={handleClick}
label="Open" label="Open"
outlined outlined

View File

@ -1,16 +1,15 @@
import React, { useState, useEffect } from 'react'; import React from 'react';
import { Card } from 'primereact/card';
import { Avatar } from 'primereact/avatar';
import { Tag } from 'primereact/tag';
import { Button } from 'primereact/button';
import { ProgressSpinner } from 'primereact/progressspinner'; import { ProgressSpinner } from 'primereact/progressspinner';
import { useDiscordQuery } from '@/hooks/communityQueries/useDiscordQuery'; import { useDiscordQuery } from '@/hooks/communityQueries/useDiscordQuery';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { highlightText } from '@/utils/text'; import { highlightText } from '@/utils/text';
import CommunityMessage from '@/components/feeds/messages/CommunityMessage';
import useWindowWidth from '@/hooks/useWindowWidth';
const DiscordFeed = ({ searchQuery }) => { const DiscordFeed = ({ searchQuery }) => {
const router = useRouter(); const router = useRouter();
const { data, error, isLoading } = useDiscordQuery({page: router.query.page}); const { data, error, isLoading } = useDiscordQuery({page: router.query.page});
const windowWidth = useWindowWidth();
if (isLoading) { if (isLoading) {
return ( return (
@ -24,54 +23,22 @@ const DiscordFeed = ({ searchQuery }) => {
return <div className="text-red-500 text-center p-4">Failed to load messages. Please try again later.</div>; return <div className="text-red-500 text-center p-4">Failed to load messages. Please try again later.</div>;
} }
const header = (message) => (
<div className="flex flex-row w-full items-center justify-between p-4 bg-gray-800 rounded-t-lg">
<div className="flex flex-row items-center">
<Avatar image={message.avatar} shape="circle" size="large" className="border-2 border-blue-400" />
<p className="pl-4 font-bold text-xl text-white">{message.author}</p>
</div>
<div className="flex flex-col items-start justify-between">
<div className="flex flex-row w-full justify-between items-center my-1 max-sidebar:flex-col max-sidebar:items-start">
<Tag value={message.channel} severity="primary" className="w-fit text-[#f8f8ff] bg-gray-600 mr-2 max-sidebar:mr-0" />
<Tag icon="pi pi-discord" value="discord" className="w-fit text-[#f8f8ff] bg-blue-400 max-sidebar:mt-1" />
</div>
</div>
</div>
);
const footer = (message) => (
<div className="w-full flex justify-between items-center">
<span className="bg-gray-800 rounded-lg p-2 text-sm text-gray-300">
{new Date(message.timestamp).toLocaleString()}
</span>
<Button
label="View in Discord"
icon="pi pi-external-link"
outlined
size="small"
className='my-2'
onClick={() => window.open(`https://discord.com/channels/${message.channelId}/${message.id}`, '_blank')}
/>
</div>
);
const filteredData = data.filter(message => const filteredData = data.filter(message =>
message.content.toLowerCase().includes(searchQuery.toLowerCase()) message.content.toLowerCase().includes(searchQuery.toLowerCase())
); );
return ( return (
<div className="bg-gray-900 h-full w-full min-bottom-bar:w-[86vw]"> <div className="bg-gray-900 h-full w-full min-bottom-bar:w-[86vw]">
<div className="mx-4 mt-4"> <div className="mx-4">
{filteredData && filteredData.length > 0 ? ( {filteredData && filteredData.length > 0 ? (
filteredData.map(message => ( filteredData.map(message => (
<Card <CommunityMessage
key={message.id} key={message.id}
header={() => header(message)} message={message}
footer={() => footer(message)} searchQuery={searchQuery}
className="w-full bg-gray-700 shadow-lg hover:shadow-xl transition-shadow duration-300 mb-4" windowWidth={windowWidth}
> platform="discord"
<p className="m-0 text-lg text-gray-200">{highlightText(message.content, searchQuery)}</p> />
</Card>
)) ))
) : ( ) : (
<div className="text-gray-400 text-center p-4">No messages available.</div> <div className="text-gray-400 text-center p-4">No messages available.</div>

View File

@ -1,8 +1,4 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Card } from 'primereact/card';
import { Avatar } from 'primereact/avatar';
import { Tag } from 'primereact/tag';
import { Button } from 'primereact/button';
import { ProgressSpinner } from 'primereact/progressspinner'; import { ProgressSpinner } from 'primereact/progressspinner';
import { useDiscordQuery } from '@/hooks/communityQueries/useDiscordQuery'; import { useDiscordQuery } from '@/hooks/communityQueries/useDiscordQuery';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
@ -14,8 +10,9 @@ import { findKind0Fields } from '@/utils/nostr';
import NostrIcon from '../../../public/images/nostr.png'; import NostrIcon from '../../../public/images/nostr.png';
import Image from 'next/image'; import Image from 'next/image';
import { useImageProxy } from '@/hooks/useImageProxy'; import { useImageProxy } from '@/hooks/useImageProxy';
import { highlightText } from '@/utils/text';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import CommunityMessage from '@/components/feeds/messages/CommunityMessage';
import useWindowWidth from '@/hooks/useWindowWidth';
const StackerNewsIconComponent = () => ( const StackerNewsIconComponent = () => (
<svg width="16" height="16" className='mr-2' viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" className='mr-2' viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
@ -36,6 +33,7 @@ const GlobalFeed = ({searchQuery}) => {
const { communityNotes: nostrData, error: nostrError, isLoading: nostrLoading } = useCommunityNotes(); const { communityNotes: nostrData, error: nostrError, isLoading: nostrLoading } = useCommunityNotes();
const { ndk } = useNDKContext(); const { ndk } = useNDKContext();
const { returnImageProxy } = useImageProxy(); const { returnImageProxy } = useImageProxy();
const windowWidth = useWindowWidth();
const [authorData, setAuthorData] = useState({}); const [authorData, setAuthorData] = useState({});
@ -91,17 +89,6 @@ const GlobalFeed = ({searchQuery}) => {
return <div className="text-red-500 text-center p-4">Failed to load feed. Please try again later.</div>; return <div className="text-red-500 text-center p-4">Failed to load feed. Please try again later.</div>;
} }
const getAvatarImage = (item) => {
if (item.type === 'discord') {
return item.avatar ? returnImageProxy(item.avatar) : null;
} else if (item.type === 'nostr') {
return authorData[item.pubkey]?.avatar ? returnImageProxy(authorData[item.pubkey]?.avatar) : null;
} else if (item.type === 'stackernews') {
return item.user.image ? returnImageProxy(item.user.image) : null;
}
return null;
};
const combinedFeed = [ const combinedFeed = [
...(discordData || []).map(item => ({ ...item, type: 'discord' })), ...(discordData || []).map(item => ({ ...item, type: 'discord' })),
...(stackerNewsData || []).map(item => ({ ...item, type: 'stackernews' })), ...(stackerNewsData || []).map(item => ({ ...item, type: 'stackernews' })),
@ -120,101 +107,38 @@ const GlobalFeed = ({searchQuery}) => {
return false; return false;
}); });
const header = (item) => (
<div className="flex flex-row w-full items-center justify-between p-4 bg-gray-800 rounded-t-lg">
<div className="flex flex-row items-center">
<Avatar
image={getAvatarImage(item)}
icon={getAvatarImage(item) ? null : 'pi pi-user'}
shape="circle"
size="large"
className="border-2 border-blue-400"
/>
<p className="pl-4 font-bold text-xl text-white">
{item.type === 'discord' ? item.author :
item.type === 'stackernews' ? item.user.name :
authorData[item.pubkey]?.username || item.pubkey.substring(0, 12) + '...'}
</p>
</div>
<div className="flex flex-col items-start justify-between">
<div className="flex flex-row w-full justify-between items-center my-1 max-sidebar:flex-col max-sidebar:items-start">
{item.type === 'discord' && (
<>
<Tag value={item.channel} severity="primary" className="w-fit text-[#f8f8ff] bg-gray-600 mr-2 max-sidebar:mr-0" />
<Tag icon="pi pi-discord" value="discord" className="w-fit text-[#f8f8ff] bg-blue-400 max-sidebar:mt-1" />
</>
)}
{item.type === 'stackernews' && (
<>
<Tag value="~devs" severity="contrast" className="w-fit text-[#f8f8ff] mr-2 max-sidebar:mr-0" />
<Tag icon={<StackerNewsIconComponent />} value="stackernews" className="w-fit bg-gray-600 text-[#f8f8ff] max-sidebar:mt-1" />
</>
)}
{item.type === 'nostr' && (
<>
<Tag icon="pi pi-hashtag" value="plebdevs" severity="primary" className="w-fit text-[#f8f8ff] bg-gray-600 mr-2 max-sidebar:mr-0" />
<Tag icon={<Image src={NostrIcon} alt="Nostr" width={14} height={14} className='mr-[1px]' />} value="nostr" className="w-fit text-[#f8f8ff] bg-blue-400 max-sidebar:mt-1" />
</>
)}
</div>
</div>
</div>
);
const footer = (item) => (
<div className="w-full flex justify-between items-center">
<span className="bg-gray-800 rounded-lg p-2 text-sm text-gray-300">
{item.type === 'nostr'
? new Date(item.created_at * 1000).toLocaleString()
: new Date(item.timestamp || item.createdAt).toLocaleString()}
</span>
<Button
label={item.type === 'discord' ? "View in Discord" :
item.type === 'stackernews' ? "View on StackerNews" :
"View on Nostr"}
icon="pi pi-external-link"
outlined
severity={item.type === 'discord' ? "info" :
item.type === 'stackernews' ? "warning" :
"success"}
size="small"
className='my-2'
onClick={() => window.open(
item.type === 'discord' ? `https://discord.com/channels/${item.channelId}/${item.id}` :
item.type === 'stackernews' ? `https://stacker.news/items/${item.id}` :
`https://nostr.band/${nip19.noteEncode(item.id)}`,
'_blank'
)}
/>
</div>
);
return ( return (
<div className="bg-gray-900 h-full w-full min-bottom-bar:w-[86vw]"> <div className="bg-gray-900 h-full w-full min-bottom-bar:w-[86vw]">
<div className="mx-4 mt-4"> <div className="mx-4 mt-4">
{combinedFeed.length > 0 ? ( {combinedFeed.length > 0 ? (
combinedFeed.map(item => ( combinedFeed.map(item => (
<Card <CommunityMessage
key={item.id} key={item.id}
header={() => header(item)} message={{
footer={() => footer(item)} id: item.id,
className="w-full bg-gray-700 shadow-lg hover:shadow-xl transition-shadow duration-300 mb-4" author: item.type === 'discord' ? item.author :
> item.type === 'stackernews' ? item.user.name :
{item.type === 'discord' || item.type === 'nostr' ? ( authorData[item.pubkey]?.username || item.pubkey.substring(0, 12) + '...',
<p className="m-0 text-lg text-gray-200 overflow-hidden break-words"> avatar: item.type === 'discord' ? item.avatar :
{highlightText(item.content, searchQuery)} item.type === 'stackernews' ? item.user.image :
</p> authorData[item.pubkey]?.avatar,
) : ( content: item.type === 'stackernews' ? item.title : item.content,
<> timestamp: item.type === 'nostr' ? item.created_at * 1000 : new Date(item.timestamp || item.createdAt),
<h3 className="m-0 text-lg text-gray-200"> channel: item.type === 'discord' ? item.channel :
{highlightText(item.title, searchQuery)} item.type === 'stackernews' ? "~devs" :
</h3> "plebdevs"
<p className="text-sm text-gray-400"> }}
Comments: {item.comments.length} | Sats: {item.sats} searchQuery={searchQuery}
</p> windowWidth={windowWidth}
</> platform={item.type}
)} platformIcon={item.type === 'stackernews' ? <StackerNewsIconComponent /> :
</Card> item.type === 'nostr' ? <Image src={NostrIcon} alt="Nostr" width={14} height={14} className='mr-[1px]' /> :
null}
platformLink={item.type === 'discord' ? `https://discord.com/channels/${item.channelId}/${item.id}` :
item.type === 'stackernews' ? `https://stacker.news/items/${item.id}` :
`https://nostr.band/${nip19.noteEncode(item.id)}`}
additionalContent={item.type === 'stackernews' ? `Comments: ${item.comments.length} | Sats: ${item.sats}` : null}
/>
)) ))
) : ( ) : (
<div className="text-gray-400 text-center p-4"> <div className="text-gray-400 text-center p-4">

View File

@ -1,6 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { InputTextarea } from 'primereact/inputtextarea'; import { InputTextarea } from 'primereact/inputtextarea';
import { Button } from 'primereact/button'; import GenericButton from '@/components/buttons/GenericButton';
import { Panel } from 'primereact/panel'; import { Panel } from 'primereact/panel';
import { useNDKContext } from "@/context/NDKContext"; import { useNDKContext } from "@/context/NDKContext";
import { NDKEvent } from "@nostr-dev-kit/ndk"; import { NDKEvent } from "@nostr-dev-kit/ndk";
@ -8,8 +8,10 @@ import { useToast } from '@/hooks/useToast';
const MessageInput = ({ onMessageSent }) => { const MessageInput = ({ onMessageSent }) => {
const [message, setMessage] = useState(''); const [message, setMessage] = useState('');
const [collapsed, setCollapsed] = useState(false);
const { ndk, addSigner } = useNDKContext(); const { ndk, addSigner } = useNDKContext();
const { showToast } = useToast(); const { showToast } = useToast();
const handleSubmit = async () => { const handleSubmit = async () => {
if (!message.trim() || !ndk) return; if (!message.trim() || !ndk) return;
@ -33,20 +35,23 @@ const MessageInput = ({ onMessageSent }) => {
} }
}; };
const headerTemplate = (options) => {
return ( return (
<Panel header={null} toggleable collapsed={false} className="w-full" pt={{ <div className="flex align-items-center justify-content-between my-1 py-2">
header: { <GenericButton outlined severity="primary" size="small" className="py-1" onClick={options.onTogglerClick} icon={options.collapsed ? 'pi pi-chevron-down' : 'pi pi-chevron-up'} />
className: 'bg-transparent', <h3 className="m-0 ml-2">New Message</h3>
border: 'none', </div>
}, );
toggler: { };
className: 'bg-transparent',
border: 'none', return (
}, <Panel
togglerIcon: { headerTemplate={headerTemplate}
display: 'none', toggleable
}, collapsed={collapsed}
}}> onToggle={(e) => setCollapsed(e.value)}
className="w-full"
>
<div className="w-full flex flex-col"> <div className="w-full flex flex-col">
<InputTextarea <InputTextarea
value={message} value={message}
@ -59,11 +64,12 @@ const MessageInput = ({ onMessageSent }) => {
/> />
</div> </div>
<div className="w-full flex flex-row justify-end mt-4"> <div className="w-full flex flex-row justify-end mt-4">
<Button <GenericButton
label="Send" label="Send"
icon="pi pi-send" icon="pi pi-send"
outlined outlined
onClick={handleSubmit} onClick={handleSubmit}
className="w-fit py-2"
/> />
</div> </div>
</Panel> </Panel>

View File

@ -1,8 +1,4 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Card } from 'primereact/card';
import { Avatar } from 'primereact/avatar';
import { Tag } from 'primereact/tag';
import { Button } from 'primereact/button';
import { ProgressSpinner } from 'primereact/progressspinner'; import { ProgressSpinner } from 'primereact/progressspinner';
import { useNDKContext } from '@/context/NDKContext'; import { useNDKContext } from '@/context/NDKContext';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
@ -10,9 +6,10 @@ import { findKind0Fields } from '@/utils/nostr';
import NostrIcon from '../../../public/images/nostr.png'; import NostrIcon from '../../../public/images/nostr.png';
import Image from 'next/image'; import Image from 'next/image';
import { useImageProxy } from '@/hooks/useImageProxy'; import { useImageProxy } from '@/hooks/useImageProxy';
import useWindowWidth from '@/hooks/useWindowWidth';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import { useCommunityNotes } from '@/hooks/nostr/useCommunityNotes'; import { useCommunityNotes } from '@/hooks/nostr/useCommunityNotes';
import { highlightText } from '@/utils/text'; import CommunityMessage from '@/components/feeds/messages/CommunityMessage';
import ZapThreadsWrapper from '@/components/ZapThreadsWrapper'; import ZapThreadsWrapper from '@/components/ZapThreadsWrapper';
const NostrFeed = ({ searchQuery }) => { const NostrFeed = ({ searchQuery }) => {
@ -23,6 +20,8 @@ const NostrFeed = ({ searchQuery }) => {
const [authorData, setAuthorData] = useState({}); const [authorData, setAuthorData] = useState({});
const [npub, setNpub] = useState(null); const [npub, setNpub] = useState(null);
const windowWidth = useWindowWidth();
useEffect(() => { useEffect(() => {
const fetchAuthors = async () => { const fetchAuthors = async () => {
const authorDataMap = {}; const authorDataMap = {};
@ -68,61 +67,6 @@ const NostrFeed = ({ searchQuery }) => {
} }
}; };
const getAvatarImage = (message) => {
return authorData[message.pubkey]?.avatar ? returnImageProxy(authorData[message.pubkey]?.avatar) : null;
};
const renderHeader = (message) => (
<div className="flex flex-row w-full items-center justify-between p-4 bg-gray-800 rounded-t-lg">
<div className="flex flex-row items-center">
<Avatar
image={getAvatarImage(message)}
icon={getAvatarImage(message) ? null : 'pi pi-user'}
shape="circle"
size="large"
className="border-2 border-blue-400"
/>
<p className="pl-4 font-bold text-xl text-white">
{authorData[message.pubkey]?.username || message.pubkey.substring(0, 12) + '...'}
</p>
</div>
<div className="flex flex-col items-start justify-between">
<div className="flex flex-row w-full justify-between items-center my-1 max-sidebar:flex-col max-sidebar:items-start">
<Tag icon="pi pi-hashtag" value="plebdevs" severity="primary" className="w-fit text-[#f8f8ff] bg-gray-600 mr-2 max-sidebar:mr-0" />
<Tag icon={<Image src={NostrIcon} alt="Nostr" width={14} height={14} className='mr-[1px]' />} value="nostr" className="w-fit text-[#f8f8ff] bg-blue-400 max-sidebar:mt-1" />
</div>
</div>
</div>
);
const footer = (message) => (
<>
<div className="w-full flex justify-between items-center">
<span className="bg-gray-800 rounded-lg p-2 text-sm text-gray-300">
{new Date(message.created_at * 1000).toLocaleString()}
</span>
<Button
label="View on Nostr"
icon="pi pi-external-link"
outlined
size="small"
className='my-2'
onClick={() => window.open(`https://nostr.band/${nip19.noteEncode(message.id)}`, '_blank')}
/>
</div>
<div className="w-full">
{session?.user?.pubkey && (
<ZapThreadsWrapper
anchor={nip19.noteEncode(message.id)}
user={npub}
relays="wss://nos.lol/, wss://relay.damus.io/, wss://relay.snort.social/, wss://relay.nostr.band/, wss://nostr.mutinywallet.com/, wss://relay.mutinywallet.com/, wss://relay.primal.net/"
disable=""
/>
)}
</div>
</>
);
if (isLoading) { if (isLoading) {
return ( return (
<div className="h-[100vh] min-bottom-bar:w-[86vw] max-sidebar:w-[100vw]"> <div className="h-[100vh] min-bottom-bar:w-[86vw] max-sidebar:w-[100vw]">
@ -144,16 +88,32 @@ const NostrFeed = ({ searchQuery }) => {
<div className="mx-4 mt-4"> <div className="mx-4 mt-4">
{filteredNotes.length > 0 ? ( {filteredNotes.length > 0 ? (
filteredNotes.map(message => ( filteredNotes.map(message => (
<Card <CommunityMessage
key={message.id} key={message.id}
header={renderHeader(message)} message={{
footer={() => footer(message)} id: message.id,
className="w-full bg-gray-700 shadow-lg hover:shadow-xl transition-shadow duration-300 mb-4" author: authorData[message.pubkey]?.username || message.pubkey.substring(0, 12) + '...',
> avatar: authorData[message.pubkey]?.avatar ? returnImageProxy(authorData[message.pubkey]?.avatar) : null,
<p className="m-0 text-lg text-gray-200 overflow-hidden break-words"> content: message.content,
{highlightText(message.content, searchQuery)} timestamp: message.created_at * 1000,
</p> channel: "plebdevs"
</Card> }}
searchQuery={searchQuery}
windowWidth={windowWidth}
platform="nostr"
platformIcon={<Image src={NostrIcon} alt="Nostr" width={14} height={14} className='mr-[1px]' />}
platformLink={`https://nostr.band/${nip19.noteEncode(message.id)}`}
additionalFooter={
session?.user?.pubkey && (
<ZapThreadsWrapper
anchor={nip19.noteEncode(message.id)}
user={npub}
relays="wss://nos.lol/, wss://relay.damus.io/, wss://relay.snort.social/, wss://relay.nostr.band/, wss://nostr.mutinywallet.com/, wss://relay.mutinywallet.com/, wss://relay.primal.net/"
disable=""
/>
)
}
/>
)) ))
) : ( ) : (
<div className="text-gray-400 text-center p-4">No messages available.</div> <div className="text-gray-400 text-center p-4">No messages available.</div>

View File

@ -1,12 +1,9 @@
import React, { useEffect } from 'react'; import React from 'react';
import { Card } from 'primereact/card';
import { Avatar } from 'primereact/avatar';
import { Tag } from 'primereact/tag';
import { Button } from 'primereact/button';
import { ProgressSpinner } from 'primereact/progressspinner'; import { ProgressSpinner } from 'primereact/progressspinner';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { highlightText } from '@/utils/text';
import axios from 'axios'; import axios from 'axios';
import CommunityMessage from '@/components/feeds/messages/CommunityMessage';
import useWindowWidth from '@/hooks/useWindowWidth';
const StackerNewsIconComponent = () => ( const StackerNewsIconComponent = () => (
<svg width="16" height="16" className='mr-2' viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" className='mr-2' viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
@ -17,15 +14,12 @@ const StackerNewsIconComponent = () => (
const fetchStackerNews = async () => { const fetchStackerNews = async () => {
const response = await axios.get('/api/stackernews'); const response = await axios.get('/api/stackernews');
return response.data.data.items.items; // Note the change here return response.data.data.items.items;
}; };
const StackerNewsFeed = ({ searchQuery }) => { const StackerNewsFeed = ({ searchQuery }) => {
const { data: items, isLoading, error } = useQuery({queryKey: ['stackerNews'], queryFn: fetchStackerNews}); const { data: items, isLoading, error } = useQuery({queryKey: ['stackerNews'], queryFn: fetchStackerNews});
const windowWidth = useWindowWidth();
useEffect(() => {
console.log(items);
}, [items]);
if (isLoading) { if (isLoading) {
return ( return (
@ -40,38 +34,6 @@ const StackerNewsFeed = ({ searchQuery }) => {
return <div className="text-red-500 text-center p-4">Error loading data. Please try again later.</div>; return <div className="text-red-500 text-center p-4">Error loading data. Please try again later.</div>;
} }
const header = (item) => (
<div className="flex flex-row w-full items-center justify-between p-4 bg-gray-800 rounded-t-lg">
<div className="flex flex-row items-center">
<Avatar icon="pi pi-user" shape="circle" size="large" className="border-2 border-blue-400" />
<p className="pl-4 font-bold text-xl text-white">{item.user.name}</p>
</div>
<div className="flex flex-col items-start justify-between">
<div className="flex flex-row w-full justify-between items-center my-1 max-sidebar:flex-col max-sidebar:items-start">
<Tag value="~devs" severity="contrast" className="w-fit text-[#f8f8ff] mr-2 max-sidebar:mr-0" />
<Tag icon={<StackerNewsIconComponent />} value="stackernews" className="w-fit bg-gray-600 text-[#f8f8ff] max-sidebar:mt-1" />
</div>
</div>
</div>
);
const footer = (item) => (
<div className="w-full flex justify-between items-center">
<span className="bg-gray-800 rounded-lg p-2 text-sm text-gray-300">
{new Date(item.createdAt).toLocaleString()}
</span>
<Button
label="View on Stacker News"
icon="pi pi-external-link"
severity="warning"
outlined
size="small"
className='my-2'
onClick={() => window.open(`https://stacker.news/items/${item.id}`, '_blank')}
/>
</div>
);
const filteredItems = items.filter(item => const filteredItems = items.filter(item =>
item.title.toLowerCase().includes(searchQuery.toLowerCase()) item.title.toLowerCase().includes(searchQuery.toLowerCase())
); );
@ -81,17 +43,23 @@ const StackerNewsFeed = ({ searchQuery }) => {
<div className="mx-4 mt-4"> <div className="mx-4 mt-4">
{filteredItems && filteredItems.length > 0 ? ( {filteredItems && filteredItems.length > 0 ? (
filteredItems.map(item => ( filteredItems.map(item => (
<Card <CommunityMessage
key={item.id} key={item.id}
header={() => header(item)} message={{
footer={() => footer(item)} id: item.id,
className="w-full bg-gray-700 shadow-lg hover:shadow-xl transition-shadow duration-300 mb-4" author: item.user.name,
> avatar: item.user.image,
<h3 className="m-0 text-lg text-gray-200">{highlightText(item.title, searchQuery)}</h3> content: item.title,
<p className="text-sm text-gray-400"> timestamp: item.createdAt,
Comments: {item.comments.length} | Sats: {item.sats} channel: "~devs",
</p> additionalContent: `Comments: ${item.comments.length} | Sats: ${item.sats}`
</Card> }}
searchQuery={searchQuery}
windowWidth={windowWidth}
platform="stackernews"
platformIcon={<StackerNewsIconComponent />}
platformLink={`https://stacker.news/items/${item.id}`}
/>
)) ))
) : ( ) : (
<div className="text-gray-400 text-center p-4">No items available.</div> <div className="text-gray-400 text-center p-4">No items available.</div>

View File

@ -0,0 +1,95 @@
import React from 'react';
import { Card } from 'primereact/card';
import { Avatar } from 'primereact/avatar';
import { Tag } from 'primereact/tag';
import GenericButton from '@/components/buttons/GenericButton';
import { highlightText } from '@/utils/text';
import NostrIcon from '../../../../public/images/nostr.png';
import Image from 'next/image';
import { nip19 } from 'nostr-tools';
const StackerNewsIconComponent = () => (
<svg width="16" height="16" className='mr-2' viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill="#facc15" fillRule="evenodd" d="m41.7 91.4 41.644 59.22-78.966 69.228L129.25 155.94l-44.083-58.14 54.353-65.441Z"/>
<path fill="#facc15" fillRule="evenodd" d="m208.355 136.74-54.358-64.36-38.4 128.449 48.675-74.094 64.36 65.175L251.54 42.497Z"/>
</svg>
);
const CommunityMessage = ({ message, searchQuery, windowWidth, platform }) => {
const header = (
<div className="flex flex-row w-full items-center justify-between p-4 bg-gray-800 rounded-t-lg">
<div className="flex flex-row items-center">
<Avatar image={message.avatar} shape="circle" size="large" className="border-2 border-blue-400" />
<p className="pl-4 font-bold text-xl text-white">{message.author}</p>
</div>
<div className="flex flex-col items-start justify-between">
<div className="flex flex-row w-full justify-between items-center my-1 max-sidebar:flex-col max-sidebar:items-start">
<Tag value={message.channel} severity="primary" className="w-fit text-[#f8f8ff] bg-gray-600 mr-2 max-sidebar:mr-0" />
<Tag icon={getPlatformIcon(platform)} value={platform} className="w-fit text-[#f8f8ff] bg-blue-400 max-sidebar:mt-1" />
</div>
</div>
</div>
);
const footer = (
<div className="w-full flex justify-between items-center">
<span className="rounded-lg text-sm text-gray-300">
{new Date(message.timestamp).toLocaleString()}
</span>
<GenericButton
label={windowWidth > 768 ? `View in ${platform}` : null}
icon="pi pi-external-link"
outlined
size="small"
className='my-2'
onClick={() => window.open(getPlatformLink(platform, message.id), '_blank')}
tooltip={windowWidth < 768 ? `View in ${platform}` : null}
tooltipOptions={{ position: 'left' }}
/>
</div>
);
return (
<Card
header={header}
footer={footer}
className="w-full bg-gray-700 shadow-lg hover:shadow-xl transition-shadow duration-300 mb-4"
pt={{
footer: {
className: 'mt-0 pt-0'
},
content: {
className: 'mt-0 pt-0'
}
}}
>
<p className="m-0 text-lg text-gray-200 break-words">{highlightText(message.content, searchQuery)}</p>
</Card>
);
};
const getPlatformLink = (platform, id) => {
switch (platform) {
case 'discord':
return "https://discord.gg/t8DCMcq39d";
case 'stackernews':
return `https://stacker.news/items/${id}`;
case 'nostr':
return `https://nostr.band/${nip19.noteEncode(id)}`;
default:
return "#";
}
};
const getPlatformIcon = (platform) => {
switch (platform) {
case 'stackernews':
return <StackerNewsIconComponent />;
case 'nostr':
return <Image src={NostrIcon} alt="Nostr" width={16} height={16} className='mr-1' />;
default:
return `pi pi-${platform}`;
}
};
export default CommunityMessage;

View File

@ -4,8 +4,8 @@ import { InputText } from "primereact/inputtext";
import { InputTextarea } from "primereact/inputtextarea"; import { InputTextarea } from "primereact/inputtextarea";
import { InputNumber } from "primereact/inputnumber"; import { InputNumber } from "primereact/inputnumber";
import { InputSwitch } from "primereact/inputswitch"; import { InputSwitch } from "primereact/inputswitch";
import { Button } from "primereact/button"; import GenericButton from "@/components/buttons/GenericButton";
import { useRouter } from "next/router";; import { useRouter } from "next/router";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import { useToast } from "@/hooks/useToast"; import { useToast } from "@/hooks/useToast";
import { useNDKContext } from "@/context/NDKContext"; import { useNDKContext } from "@/context/NDKContext";
@ -263,12 +263,12 @@ const ResourceForm = ({ draft = null, isPublished = false }) => {
<div className="p-inputgroup flex-1" key={index}> <div className="p-inputgroup flex-1" key={index}>
<InputText value={link} onChange={(e) => handleAdditionalLinkChange(index, e.target.value)} placeholder="https://plebdevs.com" className="w-full mt-2" /> <InputText value={link} onChange={(e) => handleAdditionalLinkChange(index, e.target.value)} placeholder="https://plebdevs.com" className="w-full mt-2" />
{index > 0 && ( {index > 0 && (
<Button icon="pi pi-times" className="p-button-danger mt-2" onClick={(e) => removeAdditionalLink(e, index)} /> <GenericButton icon="pi pi-times" className="p-button-danger mt-2" onClick={(e) => removeAdditionalLink(e, index)} />
)} )}
</div> </div>
))} ))}
<div className="w-full flex flex-row items-end justify-end py-2"> <div className="w-full flex flex-row items-end justify-end py-2">
<Button icon="pi pi-plus" onClick={addAdditionalLink} /> <GenericButton icon="pi pi-plus" onClick={addAdditionalLink} />
</div> </div>
<Tooltip target=".pi-info-circle" /> <Tooltip target=".pi-info-circle" />
</div> </div>
@ -277,16 +277,16 @@ const ResourceForm = ({ draft = null, isPublished = false }) => {
<div className="p-inputgroup flex-1" key={index}> <div className="p-inputgroup flex-1" key={index}>
<InputText value={topic} onChange={(e) => handleTopicChange(index, e.target.value)} placeholder="Topic" className="w-full mt-2" /> <InputText value={topic} onChange={(e) => handleTopicChange(index, e.target.value)} placeholder="Topic" className="w-full mt-2" />
{index > 0 && ( {index > 0 && (
<Button icon="pi pi-times" className="p-button-danger mt-2" onClick={(e) => removeTopic(e, index)} /> <GenericButton icon="pi pi-times" className="p-button-danger mt-2" onClick={(e) => removeTopic(e, index)} />
)} )}
</div> </div>
))} ))}
<div className="w-full flex flex-row items-end justify-end py-2"> <div className="w-full flex flex-row items-end justify-end py-2">
<Button icon="pi pi-plus" onClick={addTopic} /> <GenericButton icon="pi pi-plus" onClick={addTopic} />
</div> </div>
</div> </div>
<div className="flex justify-center mt-8"> <div className="flex justify-center mt-8">
<Button type="submit" severity="success" outlined label={draft ? "Update Draft" : "Save Draft"} /> <GenericButton type="submit" severity="success" outlined label={draft ? "Update Draft" : "Save Draft"} />
</div> </div>
</form> </form>
); );

View File

@ -4,7 +4,7 @@ import { useRouter } from 'next/router';
import { InputText } from 'primereact/inputtext'; import { InputText } from 'primereact/inputtext';
import { InputNumber } from 'primereact/inputnumber'; import { InputNumber } from 'primereact/inputnumber';
import { InputSwitch } from 'primereact/inputswitch'; import { InputSwitch } from 'primereact/inputswitch';
import { Button } from 'primereact/button'; import GenericButton from '@/components/buttons/GenericButton';
import { useToast } from '@/hooks/useToast'; import { useToast } from '@/hooks/useToast';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import 'primeicons/primeicons.css'; import 'primeicons/primeicons.css';
@ -179,12 +179,12 @@ const WorkshopForm = ({ draft = null }) => {
<div className="p-inputgroup flex-1" key={index}> <div className="p-inputgroup flex-1" key={index}>
<InputText value={link} onChange={(e) => handleLinkChange(index, e.target.value)} placeholder="https://example.com" className="w-full mt-2" /> <InputText value={link} onChange={(e) => handleLinkChange(index, e.target.value)} placeholder="https://example.com" className="w-full mt-2" />
{index > 0 && ( {index > 0 && (
<Button icon="pi pi-times" className="p-button-danger mt-2" onClick={(e) => removeLink(e, index)} /> <GenericButton icon="pi pi-times" className="p-button-danger mt-2" onClick={(e) => removeLink(e, index)} />
)} )}
</div> </div>
))} ))}
<div className="w-full flex flex-row items-end justify-end py-2"> <div className="w-full flex flex-row items-end justify-end py-2">
<Button icon="pi pi-plus" onClick={addLink} /> <GenericButton icon="pi pi-plus" onClick={addLink} />
</div> </div>
<Tooltip target=".pi-info-circle" /> <Tooltip target=".pi-info-circle" />
</div> </div>
@ -193,16 +193,16 @@ const WorkshopForm = ({ draft = null }) => {
<div className="p-inputgroup flex-1" key={index}> <div className="p-inputgroup flex-1" key={index}>
<InputText value={topic} onChange={(e) => handleTopicChange(index, e.target.value)} placeholder="Topic" className="w-full mt-2" /> <InputText value={topic} onChange={(e) => handleTopicChange(index, e.target.value)} placeholder="Topic" className="w-full mt-2" />
{index > 0 && ( {index > 0 && (
<Button icon="pi pi-times" className="p-button-danger mt-2" onClick={(e) => removeTopic(e, index)} /> <GenericButton icon="pi pi-times" className="p-button-danger mt-2" onClick={(e) => removeTopic(e, index)} />
)} )}
</div> </div>
))} ))}
<div className="w-full flex flex-row items-end justify-end py-2"> <div className="w-full flex flex-row items-end justify-end py-2">
<Button icon="pi pi-plus" onClick={addTopic} /> <GenericButton icon="pi pi-plus" onClick={addTopic} />
</div> </div>
</div> </div>
<div className="flex justify-center mt-8"> <div className="flex justify-center mt-8">
<Button type="submit" severity="success" outlined label={draft ? "Update" : "Submit"} /> <GenericButton type="submit" severity="success" outlined label={draft ? "Update" : "Submit"} />
</div> </div>
</form> </form>
); );

View File

@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
import { InputText } from 'primereact/inputtext'; import { InputText } from 'primereact/inputtext';
import { InputNumber } from 'primereact/inputnumber'; import { InputNumber } from 'primereact/inputnumber';
import { InputSwitch } from 'primereact/inputswitch'; import { InputSwitch } from 'primereact/inputswitch';
import { Button } from 'primereact/button'; import GenericButton from '@/components/buttons/GenericButton';
import { ProgressSpinner } from 'primereact/progressspinner'; import { ProgressSpinner } from 'primereact/progressspinner';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
@ -213,14 +213,14 @@ const CourseForm = ({ draft = null }) => {
<div key={index} className="p-inputgroup flex-1 mt-4"> <div key={index} className="p-inputgroup flex-1 mt-4">
<InputText value={topic} onChange={(e) => handleTopicChange(index, e.target.value)} placeholder={`Topic #${index + 1}`} className="w-full" /> <InputText value={topic} onChange={(e) => handleTopicChange(index, e.target.value)} placeholder={`Topic #${index + 1}`} className="w-full" />
{index > 0 && ( {index > 0 && (
<Button icon="pi pi-times" className="p-button-danger mt-2" onClick={() => removeTopic(index)} /> <GenericButton icon="pi pi-times" className="p-button-danger mt-2" onClick={() => removeTopic(index)} />
)} )}
</div> </div>
))} ))}
<Button type="button" icon="pi pi-plus" onClick={addTopic} className="p-button-outlined mt-2" /> <GenericButton icon="pi pi-plus" onClick={addTopic} className="p-button-outlined mt-2" />
</div> </div>
<div className="flex justify-center mt-8"> <div className="flex justify-center mt-8">
<Button type="submit" label={draft ? 'Update Course Draft' : 'Create Course Draft'} className="p-button-raised p-button-success" /> <GenericButton type="submit" severity="success" outlined label={draft ? 'Update Course Draft' : 'Create Course Draft'} />
</div> </div>
</form> </form>
); );

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Dropdown } from 'primereact/dropdown'; import { Dropdown } from 'primereact/dropdown';
import { Button } from 'primereact/button'; import GenericButton from '@/components/buttons/GenericButton';
import { Dialog } from 'primereact/dialog'; import { Dialog } from 'primereact/dialog';
import { Accordion, AccordionTab } from 'primereact/accordion'; import { Accordion, AccordionTab } from 'primereact/accordion';
import EmbeddedResourceForm from '@/components/forms/course/embedded/EmbeddedResourceForm'; import EmbeddedResourceForm from '@/components/forms/course/embedded/EmbeddedResourceForm';
@ -141,7 +141,7 @@ const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent, onNewRe
return ( return (
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<p>Lesson {index + 1}</p> <p>Lesson {index + 1}</p>
<Button icon="pi pi-times" className="p-button-danger" onClick={() => removeLesson(index)} /> <GenericButton icon="pi pi-times" className="p-button-danger" onClick={() => removeLesson(index)} />
</div> </div>
); );
}; };
@ -167,8 +167,8 @@ const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent, onNewRe
<div className="flex mt-4"> <div className="flex mt-4">
{lesson.id ? null : ( {lesson.id ? null : (
<> <>
<Button label="New Resource" onClick={(e) => {e.preventDefault(); setShowResourceForm(true)}} className="mr-2" /> <GenericButton label="New Resource" onClick={(e) => {e.preventDefault(); setShowResourceForm(true)}} className="mr-2" />
<Button label="New Workshop" onClick={(e) => {e.preventDefault(); setShowWorkshopForm(true)}} className="mr-2" /> <GenericButton label="New Workshop" onClick={(e) => {e.preventDefault(); setShowWorkshopForm(true)}} className="mr-2" />
</> </>
)} )}
</div> </div>
@ -183,7 +183,7 @@ const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent, onNewRe
</AccordionTab> </AccordionTab>
))} ))}
</Accordion> </Accordion>
<Button <GenericButton
label="Add New Lesson" label="Add New Lesson"
onClick={addNewLesson} onClick={addNewLesson}
className="mt-4" className="mt-4"

View File

@ -3,7 +3,7 @@ import axios from "axios";
import { InputText } from "primereact/inputtext"; import { InputText } from "primereact/inputtext";
import { InputNumber } from "primereact/inputnumber"; import { InputNumber } from "primereact/inputnumber";
import { InputSwitch } from "primereact/inputswitch"; import { InputSwitch } from "primereact/inputswitch";
import { Button } from "primereact/button"; import GenericButton from "@/components/buttons/GenericButton";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import { useToast } from "@/hooks/useToast"; import { useToast } from "@/hooks/useToast";
import { useNDKContext } from "@/context/NDKContext"; import { useNDKContext } from "@/context/NDKContext";
@ -194,12 +194,12 @@ const EmbeddedResourceForm = ({ draft = null, isPublished = false, onSave, isPai
<div className="p-inputgroup flex-1" key={index}> <div className="p-inputgroup flex-1" key={index}>
<InputText value={link} onChange={(e) => handleAdditionalLinkChange(index, e.target.value)} placeholder="https://plebdevs.com" className="w-full mt-2" /> <InputText value={link} onChange={(e) => handleAdditionalLinkChange(index, e.target.value)} placeholder="https://plebdevs.com" className="w-full mt-2" />
{index > 0 && ( {index > 0 && (
<Button icon="pi pi-times" className="p-button-danger mt-2" onClick={(e) => removeAdditionalLink(e, index)} /> <GenericButton icon="pi pi-times" className="p-button-danger mt-2" onClick={(e) => removeAdditionalLink(e, index)} />
)} )}
</div> </div>
))} ))}
<div className="w-full flex flex-row items-end justify-end py-2"> <div className="w-full flex flex-row items-end justify-end py-2">
<Button icon="pi pi-plus" onClick={addAdditionalLink} /> <GenericButton icon="pi pi-plus" onClick={addAdditionalLink} />
</div> </div>
<Tooltip target=".pi-info-circle" /> <Tooltip target=".pi-info-circle" />
</div> </div>
@ -208,16 +208,16 @@ const EmbeddedResourceForm = ({ draft = null, isPublished = false, onSave, isPai
<div className="p-inputgroup flex-1" key={index}> <div className="p-inputgroup flex-1" key={index}>
<InputText value={topic} onChange={(e) => handleTopicChange(index, e.target.value)} placeholder="Topic" className="w-full mt-2" /> <InputText value={topic} onChange={(e) => handleTopicChange(index, e.target.value)} placeholder="Topic" className="w-full mt-2" />
{index > 0 && ( {index > 0 && (
<Button icon="pi pi-times" className="p-button-danger mt-2" onClick={(e) => removeTopic(e, index)} /> <GenericButton icon="pi pi-times" className="p-button-danger mt-2" onClick={(e) => removeTopic(e, index)} />
)} )}
</div> </div>
))} ))}
<div className="w-full flex flex-row items-end justify-end py-2"> <div className="w-full flex flex-row items-end justify-end py-2">
<Button icon="pi pi-plus" onClick={addTopic} /> <GenericButton icon="pi pi-plus" onClick={addTopic} />
</div> </div>
</div> </div>
<div className="flex justify-center mt-8"> <div className="flex justify-center mt-8">
<Button type="submit" severity="success" outlined label={draft ? "Update" : "Submit"} /> <GenericButton type="submit" severity="success" outlined label={draft ? "Update" : "Submit"} />
</div> </div>
</form> </form>
); );

View File

@ -1,9 +1,8 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import axios from 'axios'; import GenericButton from '@/components/buttons/GenericButton';
import { InputText } from 'primereact/inputtext'; import { InputText } from 'primereact/inputtext';
import { InputNumber } from 'primereact/inputnumber'; import { InputNumber } from 'primereact/inputnumber';
import { InputSwitch } from 'primereact/inputswitch'; import { InputSwitch } from 'primereact/inputswitch';
import { Button } from 'primereact/button';
import { useToast } from '@/hooks/useToast'; import { useToast } from '@/hooks/useToast';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import 'primeicons/primeicons.css'; import 'primeicons/primeicons.css';
@ -156,12 +155,12 @@ const EmbeddedWorkshopForm = ({ draft = null, onSave, isPaid }) => {
<div className="p-inputgroup flex-1" key={index}> <div className="p-inputgroup flex-1" key={index}>
<InputText value={link} onChange={(e) => handleLinkChange(index, e.target.value)} placeholder="https://example.com" className="w-full mt-2" /> <InputText value={link} onChange={(e) => handleLinkChange(index, e.target.value)} placeholder="https://example.com" className="w-full mt-2" />
{index > 0 && ( {index > 0 && (
<Button icon="pi pi-times" className="p-button-danger mt-2" onClick={(e) => removeLink(e, index)} /> <GenericButton icon="pi pi-times" className="p-button-danger mt-2" onClick={(e) => removeLink(e, index)} />
)} )}
</div> </div>
))} ))}
<div className="w-full flex flex-row items-end justify-end py-2"> <div className="w-full flex flex-row items-end justify-end py-2">
<Button icon="pi pi-plus" onClick={addLink} /> <GenericButton icon="pi pi-plus" onClick={addLink} />
</div> </div>
<Tooltip target=".pi-info-circle" /> <Tooltip target=".pi-info-circle" />
</div> </div>
@ -170,16 +169,16 @@ const EmbeddedWorkshopForm = ({ draft = null, onSave, isPaid }) => {
<div className="p-inputgroup flex-1" key={index}> <div className="p-inputgroup flex-1" key={index}>
<InputText value={topic} onChange={(e) => handleTopicChange(index, e.target.value)} placeholder="Topic" className="w-full mt-2" /> <InputText value={topic} onChange={(e) => handleTopicChange(index, e.target.value)} placeholder="Topic" className="w-full mt-2" />
{index > 0 && ( {index > 0 && (
<Button icon="pi pi-times" className="p-button-danger mt-2" onClick={(e) => removeTopic(e, index)} /> <GenericButton icon="pi pi-times" className="p-button-danger mt-2" onClick={(e) => removeTopic(e, index)} />
)} )}
</div> </div>
))} ))}
<div className="w-full flex flex-row items-end justify-end py-2"> <div className="w-full flex flex-row items-end justify-end py-2">
<Button icon="pi pi-plus" onClick={addTopic} /> <GenericButton icon="pi pi-plus" onClick={addTopic} />
</div> </div>
</div> </div>
<div className="flex justify-center mt-8"> <div className="flex justify-center mt-8">
<Button type="submit" severity="success" outlined label={draft ? "Update" : "Submit"} /> <GenericButton type="submit" severity="success" outlined label={draft ? "Update" : "Submit"} />
</div> </div>
</form> </form>
); );

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Button } from 'primereact/button';
import { TabMenu } from 'primereact/tabmenu'; import { TabMenu } from 'primereact/tabmenu';
import GenericButton from '@/components/buttons/GenericButton';
import Image from 'next/image'; import Image from 'next/image';
import StackerNewsIcon from '../../../public/images/sn.svg'; import StackerNewsIcon from '../../../public/images/sn.svg';
import NostrIcon from '../../../public/images/nostr.png'; import NostrIcon from '../../../public/images/nostr.png';
@ -35,12 +35,12 @@ const CommunityMenuTab = ({ selectedTopic, onTabChange }) => {
return { return {
label: ( label: (
<Button <GenericButton
className={`${selectedTopic === item ? 'bg-primary text-white' : ''}`} className={`${selectedTopic === item ? 'bg-primary text-white' : ''}`}
onClick={() => onTabChange(item)} onClick={() => onTabChange(item)}
outlined={selectedTopic !== item} outlined={selectedTopic !== item}
rounded rounded
size='small' size="small"
label={item} label={item}
icon={icon} icon={icon}
/> />

View File

@ -2,7 +2,7 @@ import React, { useRef, useState, useEffect } from 'react';
import Image from 'next/image'; import Image from 'next/image';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useImageProxy } from '@/hooks/useImageProxy'; import { useImageProxy } from '@/hooks/useImageProxy';
import { Button } from 'primereact/button'; import GenericButton from '@/components/buttons/GenericButton';
import { Menu } from 'primereact/menu'; import { Menu } from 'primereact/menu';
import useWindowWidth from '@/hooks/useWindowWidth'; import useWindowWidth from '@/hooks/useWindowWidth';
import { useSession, signOut } from 'next-auth/react'; import { useSession, signOut } from 'next-auth/react';
@ -87,7 +87,7 @@ const UserAvatar = () => {
} else { } else {
userAvatar = ( userAvatar = (
<div className='flex flex-row items-center justify-between'> <div className='flex flex-row items-center justify-between'>
<Button severity='help' rounded label="About" className='text-[#f8f8ff] mr-4' onClick={() => setVisible(true)} size={windowWidth < 768 ? 'small' : 'normal'} /> <GenericButton severity='help' rounded label="About" className='text-[#f8f8ff] mr-4' onClick={() => setVisible(true)} size={windowWidth < 768 ? 'small' : 'normal'} />
<Dialog header="About" visible={visible} onHide={() => { if (!visible) return; setVisible(false); }} className='w-[50vw] max-tab:w-[95vw]'> <Dialog header="About" visible={visible} onHide={() => { if (!visible) return; setVisible(false); }} className='w-[50vw] max-tab:w-[95vw]'>
<div className="space-y-6"> <div className="space-y-6">
<p className="text-lg"><i className="pi pi-info-circle mr-2"></i>PlebDevs is a custom-built education platform designed to help aspiring developers, with a special focus on Bitcoin Lightning and Nostr technologies.</p> <p className="text-lg"><i className="pi pi-info-circle mr-2"></i>PlebDevs is a custom-built education platform designed to help aspiring developers, with a special focus on Bitcoin Lightning and Nostr technologies.</p>
@ -136,7 +136,7 @@ const UserAvatar = () => {
<p className="italic text-lg"><i className="pi pi-flag mr-2"></i>PlebDevs aims to provide a comprehensive, decentralized learning experience for aspiring developers, with a strong emphasis on emerging technologies in the Bitcoin ecosystem.</p> <p className="italic text-lg"><i className="pi pi-flag mr-2"></i>PlebDevs aims to provide a comprehensive, decentralized learning experience for aspiring developers, with a strong emphasis on emerging technologies in the Bitcoin ecosystem.</p>
</div> </div>
</Dialog> </Dialog>
<Button <GenericButton
label="Login" label="Login"
icon="pi pi-user" icon="pi pi-user"
className="text-[#f8f8ff]" className="text-[#f8f8ff]"

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { Button } from "primereact/button"; import GenericButton from "@/components/buttons/GenericButton";
import MenuTab from "@/components/menutab/MenuTab"; import MenuTab from "@/components/menutab/MenuTab";
import { useCourses } from "@/hooks/nostr/useCourses"; import { useCourses } from "@/hooks/nostr/useCourses";
import { useResources } from "@/hooks/nostr/useResources"; import { useResources } from "@/hooks/nostr/useResources";
@ -13,6 +13,7 @@ import { useToast } from "@/hooks/useToast";
import { Divider } from "primereact/divider"; import { Divider } from "primereact/divider";
import ContentList from "@/components/content/lists/ContentList"; import ContentList from "@/components/content/lists/ContentList";
import { parseEvent } from "@/utils/nostr"; import { parseEvent } from "@/utils/nostr";
import { ProgressSpinner } from "primereact/progressspinner";
import { useNDKContext } from "@/context/NDKContext"; import { useNDKContext } from "@/context/NDKContext";
const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY; const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY;
@ -122,7 +123,7 @@ const UserContent = () => {
activeIndex={activeIndex} activeIndex={activeIndex}
onTabChange={setActiveIndex} onTabChange={setActiveIndex}
/> />
<Button <GenericButton
onClick={() => router.push("/create")} onClick={() => router.push("/create")}
label="Create" label="Create"
severity="success" severity="success"
@ -134,7 +135,7 @@ const UserContent = () => {
<div className="w-full mx-auto my-8"> <div className="w-full mx-auto my-8">
<div className="w-full mx-auto px-8 max-tab:px-0"> <div className="w-full mx-auto px-8 max-tab:px-0">
{isLoading ? ( {isLoading ? (
<p>Loading...</p> <ProgressSpinner className="w-full mx-auto" />
) : isError ? ( ) : isError ? (
<p>Error loading content.</p> <p>Error loading content.</p>
) : content.length > 0 ? ( ) : content.length > 0 ? (

View File

@ -1,5 +1,5 @@
import React, { useRef, useState, useEffect } from "react"; import React, { useRef, useState, useEffect } from "react";
import { Button } from "primereact/button"; import GenericButton from "@/components/buttons/GenericButton";
import { DataTable } from "primereact/datatable"; import { DataTable } from "primereact/datatable";
import { Column } from "primereact/column"; import { Column } from "primereact/column";
import { useImageProxy } from "@/hooks/useImageProxy"; import { useImageProxy } from "@/hooks/useImageProxy";
@ -28,7 +28,12 @@ const UserSettings = () => {
"wss://relay.nostr.band/", "wss://relay.nostr.band/",
"wss://nostr.mutinywallet.com/", "wss://nostr.mutinywallet.com/",
"wss://relay.mutinywallet.com/", "wss://relay.mutinywallet.com/",
"wss://relay.primal.net/" "wss://relay.primal.net/",
"wss://nostr21.com/",
"wss://nostrue.com/",
"wss://nostr.band/",
"wss://nostr.land/",
"wss://purplerelay.com/",
]; ];
const relayStatusBody = (url) => { const relayStatusBody = (url) => {
@ -42,8 +47,8 @@ const UserSettings = () => {
const relayActionsBody = () => { const relayActionsBody = () => {
return ( return (
<div> <div>
<Button icon="pi pi-plus" className="p-button-rounded p-button-success p-button-text mr-2" /> <GenericButton icon="pi pi-plus" className="p-button-rounded p-button-success p-button-text mr-2" />
<Button icon="pi pi-trash" className="p-button-rounded p-button-danger p-button-text" /> <GenericButton icon="pi pi-trash" className="p-button-rounded p-button-danger p-button-text" />
</div> </div>
); );
}; };
@ -51,7 +56,7 @@ const UserSettings = () => {
const header = ( const header = (
<div className="flex flex-row justify-between"> <div className="flex flex-row justify-between">
<span className="text-xl text-900 font-bold text-[#f8f8ff]">Relays</span> <span className="text-xl text-900 font-bold text-[#f8f8ff]">Relays</span>
<Button icon="pi pi-plus" className="p-button-rounded p-button-success p-button-text mr-2" /> <GenericButton icon="pi pi-plus" className="p-button-rounded p-button-success p-button-text mr-2" />
</div> </div>
); );

View File

@ -8,7 +8,7 @@ import { useRouter } from 'next/router';
import { useToast } from '@/hooks/useToast'; import { useToast } from '@/hooks/useToast';
import { Card } from 'primereact/card'; import { Card } from 'primereact/card';
import { Badge } from 'primereact/badge'; import { Badge } from 'primereact/badge';
import { Button } from "primereact/button"; import GenericButton from '@/components/buttons/GenericButton';
import { Menu } from "primereact/menu"; import { Menu } from "primereact/menu";
import { Message } from "primereact/message"; import { Message } from "primereact/message";
@ -126,7 +126,7 @@ const SubscribeModal = ({ user }) => {
{(!subscribed && !subscriptionExpiredAt) && ( {(!subscribed && !subscriptionExpiredAt) && (
<div className="flex flex-col"> <div className="flex flex-col">
<Message className="w-fit" severity="info" text="You currently have no active subscription" /> <Message className="w-fit" severity="info" text="You currently have no active subscription" />
<Button <GenericButton
label="Subscribe" label="Subscribe"
className="w-auto mt-8 text-[#f8f8ff]" className="w-auto mt-8 text-[#f8f8ff]"
onClick={() => setVisible(true)} onClick={() => setVisible(true)}
@ -136,7 +136,7 @@ const SubscribeModal = ({ user }) => {
{subscriptionExpiredAt && ( {subscriptionExpiredAt && (
<div className="flex flex-col"> <div className="flex flex-col">
<Message className="w-fit" severity="warn" text={`Your subscription expired on ${subscriptionExpiredAt.toLocaleDateString()}`} /> <Message className="w-fit" severity="warn" text={`Your subscription expired on ${subscriptionExpiredAt.toLocaleDateString()}`} />
<Button <GenericButton
label="Subscribe" label="Subscribe"
className="w-auto mt-8 text-[#f8f8ff]" className="w-auto mt-8 text-[#f8f8ff]"
onClick={() => setVisible(true)} onClick={() => setVisible(true)}

View File

@ -5,6 +5,7 @@ import { useSession, signOut } from 'next-auth/react';
import { Button } from 'primereact/button'; import { Button } from 'primereact/button';
import 'primeicons/primeicons.css'; import 'primeicons/primeicons.css';
import styles from "./sidebar.module.css"; import styles from "./sidebar.module.css";
import { Divider } from 'primereact/divider';
const Sidebar = () => { const Sidebar = () => {
const [isExpanded, setIsExpanded] = useState(true); const [isExpanded, setIsExpanded] = useState(true);
@ -102,6 +103,7 @@ const Sidebar = () => {
</div> </div>
)} )}
</div> </div>
<Divider className='pt-0 mt-0' />
<div className='mt-auto'> <div className='mt-auto'>
{isExpanded ? ( {isExpanded ? (
<div className='mt-auto'> <div className='mt-auto'>

View File

@ -11,7 +11,12 @@ const relayUrls = [
"wss://relay.nostr.band/", "wss://relay.nostr.band/",
"wss://nostr.mutinywallet.com/", "wss://nostr.mutinywallet.com/",
"wss://relay.mutinywallet.com/", "wss://relay.mutinywallet.com/",
"wss://relay.primal.net/" "wss://relay.primal.net/",
"wss://nostr21.com/",
"wss://nostrue.com/",
"wss://nostr.band/",
"wss://nostr.land/",
"wss://purplerelay.com/",
]; ];
export const NDKProvider = ({ children }) => { export const NDKProvider = ({ children }) => {

View File

@ -9,18 +9,18 @@ export function useDraftsQuery() {
const [user, setUser] = useState(null); const [user, setUser] = useState(null);
useEffect(() => { useEffect(() => {
if (session) { setIsClient(true);
}, []);
useEffect(() => {
if (session?.user) {
setUser(session.user); setUser(session.user);
} }
}, [session]); }, [session]);
useEffect(() => {
setIsClient(true);
}, []);
const fetchDraftsDB = async () => { const fetchDraftsDB = async () => {
try { try {
if (!user.id) { if (!user?.id) {
return []; return [];
} }
const response = await axios.get(`/api/drafts/all/${user.id}`); const response = await axios.get(`/api/drafts/all/${user.id}`);
@ -34,11 +34,9 @@ export function useDraftsQuery() {
}; };
const { data: drafts, isLoading: draftsLoading, error: draftsError, refetch: refetchDrafts } = useQuery({ const { data: drafts, isLoading: draftsLoading, error: draftsError, refetch: refetchDrafts } = useQuery({
queryKey: ['drafts', isClient], queryKey: ['drafts', isClient, user?.id],
queryFn: fetchDraftsDB, queryFn: fetchDraftsDB,
// staleTime: 1000 * 60 * 30, // 30 minutes enabled: isClient && !!user?.id,
// refetchInterval: 1000 * 60 * 30, // 30 minutes
enabled: isClient && !!user.id, // Only enable if client-side and user ID is available
}); });
return { drafts, draftsLoading, draftsError, refetchDrafts }; return { drafts, draftsLoading, draftsError, refetchDrafts };

View File

@ -1,7 +1,7 @@
import { signIn, useSession } from "next-auth/react" import { signIn, useSession } from "next-auth/react"
import { useState, useEffect } from "react" import { useState, useEffect } from "react"
import { useNDKContext } from "@/context/NDKContext"; import { useNDKContext } from "@/context/NDKContext";
import { Button } from 'primereact/button'; import GenericButton from "@/components/buttons/GenericButton";
import { InputText } from 'primereact/inputtext'; import { InputText } from 'primereact/inputtext';
export default function SignIn() { export default function SignIn() {
@ -43,21 +43,21 @@ export default function SignIn() {
return ( return (
<div className="w-[100vw] min-bottom-bar:w-[86vw] mx-auto mt-24 flex flex-col justify-center"> <div className="w-[100vw] min-bottom-bar:w-[86vw] mx-auto mt-24 flex flex-col justify-center">
<h1 className="text-center mb-8">Sign In</h1> <h1 className="text-center mb-8">Sign In</h1>
<Button <GenericButton
label={"login with nostr"} label={"login with nostr"}
icon="pi pi-user" icon="pi pi-user"
className="text-[#f8f8ff] w-[250px] my-4 mx-auto" className="text-[#f8f8ff] w-[250px] my-4 mx-auto"
rounded rounded
onClick={handleNostrSignIn} onClick={handleNostrSignIn}
/> />
<Button <GenericButton
label={"login anonymously"} label={"login anonymously"}
icon="pi pi-user" icon="pi pi-user"
className="text-[#f8f8ff] w-[250px] my-4 mx-auto" className="text-[#f8f8ff] w-[250px] my-4 mx-auto"
rounded rounded
onClick={handleAnonymousSignIn} onClick={handleAnonymousSignIn}
/> />
<Button <GenericButton
label={"login with email"} label={"login with email"}
icon="pi pi-envelope" icon="pi pi-envelope"
className="text-[#f8f8ff] w-[250px] my-4 mx-auto" className="text-[#f8f8ff] w-[250px] my-4 mx-auto"
@ -73,7 +73,7 @@ export default function SignIn() {
placeholder="Enter your email" placeholder="Enter your email"
className="w-[250px] my-4" className="w-[250px] my-4"
/> />
<Button <GenericButton
type="submit" type="submit"
label={"Submit"} label={"Submit"}
icon="pi pi-check" icon="pi pi-check"

View File

@ -7,7 +7,7 @@ import { useCourses } from '@/hooks/nostr/useCourses';
import { TabMenu } from 'primereact/tabmenu'; import { TabMenu } from 'primereact/tabmenu';
import 'primeicons/primeicons.css'; import 'primeicons/primeicons.css';
import { InputText } from 'primereact/inputtext'; import { InputText } from 'primereact/inputtext';
import { Button } from 'primereact/button'; import GenericButton from '@/components/buttons/GenericButton';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
const MenuTab = ({ items, selectedTopic, onTabChange }) => { const MenuTab = ({ items, selectedTopic, onTabChange }) => {
@ -26,7 +26,7 @@ const MenuTab = ({ items, selectedTopic, onTabChange }) => {
return { return {
label: ( label: (
<Button <GenericButton
className={`${isActive ? 'bg-primary text-white' : ''}`} className={`${isActive ? 'bg-primary text-white' : ''}`}
onClick={() => { onClick={() => {
onTabChange(item); onTabChange(item);

View File

@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
import axios from 'axios'; import axios from 'axios';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { parseEvent, findKind0Fields } from '@/utils/nostr'; import { parseEvent, findKind0Fields } from '@/utils/nostr';
import { Button } from 'primereact/button'; import GenericButton from '@/components/buttons/GenericButton';
import { nip19, nip04 } from 'nostr-tools'; import { nip19, nip04 } from 'nostr-tools';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
@ -247,8 +247,8 @@ export default function Details() {
{authorView && ( {authorView && (
<div className='w-[75vw] mx-auto flex flex-row justify-end mt-12'> <div className='w-[75vw] mx-auto flex flex-row justify-end mt-12'>
<div className='w-fit flex flex-row justify-between'> <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" /> <GenericButton 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" /> <GenericButton onClick={handleDelete} label="Delete" severity='danger' outlined className="w-auto m-2 mr-0" />
</div> </div>
</div> </div>
)} )}

View File

@ -6,7 +6,7 @@ import { nip19, nip04 } from 'nostr-tools';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import { useImageProxy } from '@/hooks/useImageProxy'; import { useImageProxy } from '@/hooks/useImageProxy';
import { Button } from 'primereact/button'; import GenericButton from '@/components/buttons/GenericButton';
import { useToast } from '@/hooks/useToast'; import { useToast } from '@/hooks/useToast';
import { Tag } from 'primereact/tag'; import { Tag } from 'primereact/tag';
import { useNDKContext } from '@/context/NDKContext'; import { useNDKContext } from '@/context/NDKContext';
@ -314,9 +314,9 @@ export default function Draft() {
</div> </div>
<div className='w-[75vw] mx-auto flex flex-row justify-end mt-12'> <div className='w-[75vw] mx-auto flex flex-row justify-end mt-12'>
<div className='w-fit flex flex-row justify-between'> <div className='w-fit flex flex-row justify-between'>
<Button onClick={handleSubmit} label="Publish" severity='success' outlined className="w-auto m-2" /> <GenericButton onClick={handleSubmit} label="Publish" severity='success' outlined className="w-auto m-2" />
<Button onClick={() => router.push(`/draft/${draft?.id}/edit`)} label="Edit" severity='warning' outlined className="w-auto m-2" /> <GenericButton onClick={() => router.push(`/draft/${draft?.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" /> <GenericButton onClick={handleDelete} label="Delete" severity='danger' outlined className="w-auto m-2 mr-0" />
</div> </div>
</div> </div>
<div className='w-[75vw] mx-auto mt-12 p-12 border-t-2 border-gray-300 max-tab:p-0 max-mob:p-0 max-tab:max-w-[100vw] max-mob:max-w-[100vw]'> <div className='w-[75vw] mx-auto mt-12 p-12 border-t-2 border-gray-300 max-tab:p-0 max-mob:p-0 max-tab:max-w-[100vw] max-mob:max-w-[100vw]'>

View File

@ -10,7 +10,7 @@ import { useRouter } from 'next/router';
import MessageInput from '@/components/feeds/MessageInput'; import MessageInput from '@/components/feeds/MessageInput';
import StackerNewsIcon from '../../public/images/sn.svg'; import StackerNewsIcon from '../../public/images/sn.svg';
import NostrIcon from '../../public/images/nostr.png'; import NostrIcon from '../../public/images/nostr.png';
import { Button } from 'primereact/button'; import GenericButton from '@/components/buttons/GenericButton';
import { Divider } from 'primereact/divider'; import { Divider } from 'primereact/divider';
const Feed = () => { const Feed = () => {
@ -59,7 +59,7 @@ const Feed = () => {
<div className="w-[100vw] min-bottom-bar:w-[86vw] px-4 pt-4 flex flex-col items-start"> <div className="w-[100vw] min-bottom-bar:w-[86vw] px-4 pt-4 flex flex-col items-start">
<div className='mb-4 flex flex-row items-end'> <div className='mb-4 flex flex-row items-end'>
<h2 className="font-bold mb-0">Community</h2> <h2 className="font-bold mb-0">Community</h2>
<Button <GenericButton
icon={getTagIcon(title)} icon={getTagIcon(title)}
className='ml-2 text-sm p-2 py-1 flex items-center cursor-default hover:bg-transparent' className='ml-2 text-sm p-2 py-1 flex items-center cursor-default hover:bg-transparent'
outlined outlined