mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-06 18:31:00 +00:00
Added generic button
This commit is contained in:
parent
046a130efa
commit
7c8273d663
@ -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}
|
||||||
|
@ -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)}
|
||||||
|
@ -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]"
|
||||||
|
11
src/components/buttons/GenericButton.js
Normal file
11
src/components/buttons/GenericButton.js
Normal 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;
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
||||||
|
@ -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]'>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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">
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
95
src/components/feeds/messages/CommunityMessage.js
Normal file
95
src/components/feeds/messages/CommunityMessage.js
Normal 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;
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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"
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
@ -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]"
|
||||||
|
@ -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 ? (
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -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)}
|
||||||
|
@ -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'>
|
||||||
|
@ -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 }) => {
|
||||||
|
@ -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 };
|
||||||
|
@ -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"
|
||||||
|
@ -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);
|
||||||
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
@ -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]'>
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user