Consistent widths accross pages aligning with navbar

This commit is contained in:
austinkelsay 2025-03-26 13:17:44 -05:00
parent d353e948f7
commit d3092f56af
No known key found for this signature in database
GPG Key ID: 5A763922E5BA08EE
21 changed files with 150 additions and 131 deletions

View File

@ -27,6 +27,7 @@ export default function GenericCarousel({items, selectedTopic, title}) {
return `${type}-${baseKey}-${index}`;
};
// todo: max sizing for single peice of content being shown
const renderItem = (item, index) => {
if (!item) return <TemplateSkeleton key={generateUniqueTemplateKey(item, index, 'skeleton')} />;
@ -58,7 +59,7 @@ export default function GenericCarousel({items, selectedTopic, title}) {
};
return (
<div className="w-full px-4 mb-4">
<div className="w-full mb-4">
<div className="grid grid-cols-2 gap-4 max-w-full max-tab:grid-cols-1 lg:grid-cols-3">
{items.map((item, index) => (
<div key={generateUniqueTemplateKey(item, index, 'container')} className="w-full min-w-0">

View File

@ -15,7 +15,7 @@ const DiscordFeed = ({ searchQuery }) => {
if (!data) return [];
return data
.filter(message =>
message.content.toLowerCase().includes(searchQuery.toLowerCase())
searchQuery ? message.content.toLowerCase().includes(searchQuery.toLowerCase()) : true
)
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
}, [data, searchQuery]);
@ -33,8 +33,8 @@ const DiscordFeed = ({ searchQuery }) => {
}
return (
<div className="h-full w-full min-bottom-bar:w-[86vw]">
<div className="mx-4">
<div className="h-full w-full">
<div className="mx-0">
{filteredData.length > 0 ? (
filteredData.map(message => (
<CommunityMessage

View File

@ -98,6 +98,7 @@ const GlobalFeed = ({searchQuery}) => {
const dateB = b.type === 'nostr' ? b.created_at * 1000 : new Date(b.timestamp || b.createdAt);
return dateB - dateA;
}).filter(item => {
if (!searchQuery) return true;
const searchLower = searchQuery.toLowerCase();
if (item.type === 'discord' || item.type === 'nostr') {
return item.content.toLowerCase().includes(searchLower);
@ -108,8 +109,8 @@ const GlobalFeed = ({searchQuery}) => {
});
return (
<div className="h-full w-full min-bottom-bar:w-[86vw]">
<div className="mx-4 mt-4">
<div className="h-full w-full">
<div className="mx-0 mt-4">
{combinedFeed.length > 0 ? (
combinedFeed.map(item => (
<CommunityMessage

View File

@ -1,7 +1,6 @@
import React, { useState } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import { InputTextarea } from 'primereact/inputtextarea';
import GenericButton from '@/components/buttons/GenericButton';
import { Panel } from 'primereact/panel';
import { useNDKContext } from "@/context/NDKContext";
import { NDKEvent } from "@nostr-dev-kit/ndk";
import { finalizeEvent, verifyEvent } from 'nostr-tools/pure'
@ -12,21 +11,54 @@ import { useSession } from 'next-auth/react';
const MessageInput = () => {
const [message, setMessage] = useState('');
const [collapsed, setCollapsed] = useState(true);
const [isSubmitting, setIsSubmitting] = useState(false);
const { ndk, addSigner } = useNDKContext();
const { showToast } = useToast();
const { data: session } = useSession();
const pool = useRef(null);
// Initialize pool when needed
const getPool = async () => {
if (!pool.current) {
pool.current = new SimplePool();
}
return pool.current;
};
const publishToRelay = async (relay, event, currentPool) => {
try {
// Wait for relay connection
await currentPool.ensureRelay(relay);
// Try to publish
await currentPool.publish([relay], event);
return true;
} catch (err) {
console.warn(`Failed to publish to ${relay}:`, err);
return false;
}
};
const handleSubmit = async () => {
if (session && session?.user && session.user?.privkey) {
handleManualSubmit(session.user.privkey);
} else {
handleExtensionSubmit();
if (!message.trim()) return;
if (isSubmitting) return;
try {
setIsSubmitting(true);
if (session && session?.user && session.user?.privkey) {
await handleManualSubmit(session.user.privkey);
} else {
await handleExtensionSubmit();
}
} catch (error) {
console.error("Error submitting message:", error);
showToast('error', 'Error', 'There was an error sending your message. Please try again.');
} finally {
setIsSubmitting(false);
}
}
const handleExtensionSubmit = async () => {
if (!message.trim() || !ndk) return;
if (!ndk) return;
try {
if (!ndk.signer) {
@ -39,10 +71,10 @@ const MessageInput = () => {
await event.publish();
showToast('success', 'Message Sent', 'Your message has been sent to the PlebDevs community.');
setMessage(''); // Clear the input after successful publish
setMessage('');
} catch (error) {
console.error("Error publishing message:", error);
showToast('error', 'Error', 'There was an error sending your message. Please try again.');
throw error;
}
};
@ -55,66 +87,62 @@ const MessageInput = () => {
['t', 'plebdevs']
],
content: message,
}, privkey)
}, privkey);
let isGood = verifyEvent(event);
if (!isGood) {
throw new Error('Event verification failed');
}
if (isGood) {
const pool = new SimplePool();
const published = await pool.publish(appConfig.defaultRelayUrls, event);
if (published) {
try {
const currentPool = await getPool();
let publishedToAny = false;
// Try to publish to each relay sequentially
for (const relay of appConfig.defaultRelayUrls) {
const success = await publishToRelay(relay, event, currentPool);
if (success) {
publishedToAny = true;
break; // Stop after first successful publish
}
}
if (publishedToAny) {
showToast('success', 'Message Sent', 'Your message has been sent to the PlebDevs community.');
setMessage('');
} else {
showToast('error', 'Error', 'There was an error sending your message. Please try again.');
throw new Error('Failed to publish to any relay');
}
} else {
showToast('error', 'Error', 'There was an error sending your message. Please try again.');
} catch (err) {
console.error("Publishing error:", err);
throw err;
}
} catch (error) {
console.error("Error finalizing event:", error);
showToast('error', 'Error', 'There was an error sending your message. Please try again.');
throw error;
}
}
const headerTemplate = (options) => {
return (
<div className="flex align-items-center justify-content-between my-1 py-2">
<GenericButton outlined severity="primary" size="small" className="py-0" onClick={options.onTogglerClick} icon={options.collapsed ? 'pi pi-chevron-down' : 'pi pi-chevron-up'} />
<h2 className="m-0 ml-2">New Message</h2>
</div>
);
};
return (
<Panel
headerTemplate={headerTemplate}
toggleable
collapsed={collapsed}
onToggle={(e) => setCollapsed(e.value)}
className="w-full"
>
<div className="w-full flex flex-col">
<InputTextarea
value={message}
onChange={(e) => setMessage(e.target.value)}
rows={2}
cols={10}
autoResize
placeholder="Type your message here..."
className="w-full"
/>
</div>
<div className="w-full flex flex-row justify-end mt-4">
<GenericButton
label="Send"
icon="pi pi-send"
outlined
onClick={handleSubmit}
className="w-fit py-2"
/>
</div>
</Panel>
<div className="flex flex-row items-center gap-2">
<InputTextarea
value={message}
onChange={(e) => setMessage(e.target.value)}
rows={1}
autoResize
placeholder="Type your message here..."
className="flex-1 bg-[#1e2732] border-[#2e3b4e] rounded-lg"
disabled={isSubmitting}
/>
<GenericButton
icon="pi pi-send"
outlined
onClick={handleSubmit}
className="h-full"
disabled={isSubmitting || !message.trim()}
loading={isSubmitting}
/>
</div>
);
};

View File

@ -72,13 +72,13 @@ const NostrFeed = ({ searchQuery }) => {
const filteredNotes = communityNotes
.filter(message =>
message.content.toLowerCase().includes(searchQuery.toLowerCase())
searchQuery ? message.content.toLowerCase().includes(searchQuery.toLowerCase()) : true
)
.sort((a, b) => b.created_at - a.created_at);
return (
<div className="h-full w-full min-bottom-bar:w-[86vw]">
<div className="mx-4 mt-4">
<div className="h-full w-full">
<div className="mx-0 mt-4">
{filteredNotes.length > 0 ? (
filteredNotes.map(message => (
<CommunityMessage

View File

@ -36,13 +36,13 @@ const StackerNewsFeed = ({ searchQuery }) => {
const filteredItems = items
.filter(item =>
item.title.toLowerCase().includes(searchQuery.toLowerCase())
searchQuery ? item.title.toLowerCase().includes(searchQuery.toLowerCase()) : true
)
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
return (
<div className="h-full w-full min-bottom-bar:w-[86vw]">
<div className="mx-4 mt-4">
<div className="h-full w-full">
<div className="mx-0 mt-4">
{filteredItems && filteredItems.length > 0 ? (
filteredItems.map(item => (
<CommunityMessage

View File

@ -144,7 +144,9 @@ const CommunityMessage = ({ message, searchQuery, windowWidth, platform }) => {
}
}}
>
<p className="m-0 text-lg text-gray-200 break-words">{highlightText(message.content, searchQuery)}</p>
<p className="m-0 text-lg text-gray-200 break-words">
{searchQuery ? highlightText(message.content, searchQuery) : message.content}
</p>
</Card>
);
};

View File

@ -52,9 +52,9 @@ const CommunityMenuTab = ({ selectedTopic, onTabChange }) => {
activeIndex={allItems.indexOf(selectedTopic)}
onTabChange={(e) => onTabChange(allItems[e.index])}
pt={{
menu: { className: 'bg-transparent border-none ml-2 my-4 py-1' },
menu: { className: 'bg-transparent border-none my-1 py-1' },
action: ({ context, parent }) => ({
className: 'cursor-pointer select-none flex items-center relative no-underline overflow-hidden border-b-2 p-2 font-bold rounded-t-lg',
className: 'cursor-pointer select-none flex items-center relative no-underline overflow-hidden border-b-2 p-2 pl-[4px] font-bold rounded-t-lg',
style: { top: '2px' }
}),
menuitem: { className: 'mr-0' }

View File

@ -27,7 +27,7 @@ const Navbar = () => {
{
label: 'Feeds',
icon: 'pi pi-comments',
command: () => router.push('/feeds?channel=global')
command: () => router.push('/feed?channel=global')
},
{
label: 'Subscribe',
@ -49,9 +49,9 @@ const Navbar = () => {
src="/images/plebdevs-icon.png"
width={50}
height={50}
className="rounded-full mr-2 max-tab:hidden max-mob:hidden"
className="rounded-full max-tab:hidden max-mob:hidden"
/>
<h1 className="text-white text-xl font-semibold max-tab:text-2xl max-mob:text-2xl pb-1">PlebDevs</h1>
<h1 className="text-white text-xl font-semibold max-tab:text-2xl max-mob:text-2xl pb-1 pl-2">PlebDevs</h1>
</div>
<div
className={`ml-2 p-2 cursor-pointer transition-all duration-300 flex items-center justify-center ${isHovered ? 'bg-gray-700 rounded-full' : ''}`}
@ -83,7 +83,7 @@ const Navbar = () => {
<Menubar
start={start}
end={UserAvatar}
className='px-6 py-8 bg-gray-800 border-t-0 border-l-0 border-r-0 rounded-none fixed z-10 w-[100vw] max-tab:px-[5%] max-mob:px-[5%]'
className='px-10 py-8 bg-gray-800 border-t-0 border-l-0 border-r-0 rounded-none fixed z-10 w-[100vw] max-tab:px-[5%] max-mob:px-[5%]'
style={{ height: navbarHeight }}
/>
</div>

View File

@ -10,7 +10,6 @@
.logo {
border-radius: 50%;
margin-right: 8px;
height: 50px;
width: 50px;
}

View File

@ -1,13 +1,11 @@
import React, { useRef, useState, useEffect } from 'react';
import Image from 'next/image';
import { Avatar } from 'primereact/avatar';
import { useRouter } from 'next/router';
import { useImageProxy } from '@/hooks/useImageProxy';
import GenericButton from '@/components/buttons/GenericButton';
import { Menu } from 'primereact/menu';
import useWindowWidth from '@/hooks/useWindowWidth';
import { useSession, signOut } from 'next-auth/react';
import { ProgressSpinner } from 'primereact/progressspinner';
import { useIsAdmin } from '@/hooks/useIsAdmin';
import 'primereact/resources/primereact.min.css';
import 'primeicons/primeicons.css';

View File

@ -113,7 +113,7 @@ const UserContent = () => {
const isError = coursesError || documentsError || videosError || draftsError || contentIdsError || courseDraftsError;
return (
<div className="p-4">
<div className="py-4 px-1">
{
windowWidth < 768 && (
<h1 className="text-3xl font-bold mb-6">My Content</h1>

View File

@ -32,7 +32,7 @@ const UserProfile = () => {
return (
user && (
<div className="p-4">
<div className="py-4 px-1">
{
windowWidth < 768 && (
<h1 className="text-3xl font-bold mb-6">Profile</h1>

View File

@ -19,7 +19,7 @@ const UserSettings = () => {
return (
user && (
<div className="p-4">
<div className="py-4 px-1">
{windowWidth < 768 && (
<h1 className="text-3xl font-bold mb-6">Settings</h1>
)}

View File

@ -93,7 +93,7 @@ const UserSubscription = () => {
};
return (
<div className="p-4">
<div className="py-4 px-1">
{windowWidth < 768 && (
<h1 className="text-3xl font-bold mb-6">Subscription Management</h1>
)}

View File

@ -66,7 +66,7 @@ const SearchBar = () => {
const handleContentSelect = (content) => {
if (content?.type === 'course') {
router.push(`/course/${content.id}`);
router.push(`/course/${content?.d || content?.id}`);
} else {
router.push(`/details/${content.id}`);
}

View File

@ -12,8 +12,8 @@ const AboutPage = () => {
const { showToast } = useToast();
const windowWidth = useWindowWidth();
const isTabView = windowWidth <= 1360;
const isMobile = windowWidth < 768;
const isTabView = windowWidth <= 1160;
const isMobile = windowWidth < 668;
const copyToClipboard = async (text) => {
try {
@ -32,7 +32,7 @@ const AboutPage = () => {
};
return (
<div className={`${isTabView ? 'w-full' : 'w-[83vw]'} ${isMobile ? 'p-0' : 'p-4'} mx-auto`}>
<div className={`${isTabView ? 'w-full' : 'w-full px-12'} ${isMobile ? 'p-0' : 'p-4'} mx-auto`}>
<InteractivePromotionalCarousel />
<Card title="Key Features" className={`mb-4 ${isMobile ? 'm-2' : null}`}>
<div className="flex flex-col gap-4 max-w-[80%] max-mob:max-w-full">

View File

@ -58,9 +58,9 @@ const MenuTab = ({ items, selectedTopic, onTabChange }) => {
activeIndex={allItems.indexOf(selectedTopic)}
onTabChange={(e) => onTabChange(allItems[e.index])}
pt={{
menu: { className: 'bg-transparent border-none ml-2 my-4 py-1' },
menu: { className: 'bg-transparent border-none my-2 py-1' },
action: ({ context, parent }) => ({
className: 'cursor-pointer select-none flex items-center relative no-underline overflow-hidden border-b-2 p-2 font-bold rounded-t-lg',
className: 'cursor-pointer select-none flex items-center relative no-underline overflow-hidden border-b-2 p-2 pl-1 font-bold rounded-t-lg',
style: { top: '2px' }
}),
menuitem: { className: 'mr-0' }
@ -156,7 +156,7 @@ const ContentPage = () => {
router.push(`/content${queryParam}`, undefined, { shallow: true });
filterContent(newTopic, allContent);
};
const renderCarousels = () => {
return (
<GenericCarousel
@ -170,15 +170,14 @@ const ContentPage = () => {
};
return (
<div className="w-full px-4">
<div className="w-fit mx-4 mt-8 flex flex-col items-start">
<h1 className="text-3xl font-bold mb-4 ml-1">All Content</h1>
<div className="w-full px-10 max-mob:px-1">
<div className="w-fit mt-8 flex flex-col items-start">
<h1 className="text-3xl font-bold mb-4 ml-2">All Content</h1>
</div>
<MenuTab
items={allTopics}
selectedTopic={selectedTopic}
onTabChange={handleTopicChange}
className="max-w-[90%] mx-auto"
/>
{renderCarousels()}
</div>

View File

@ -1,6 +1,5 @@
import React, { useState, useEffect } from 'react';
import Image from 'next/image';
import { InputText } from 'primereact/inputtext';
import CommunityMenuTab from '@/components/menutab/CommunityMenuTab';
import NostrFeed from '@/components/feeds/NostrFeed';
import DiscordFeed from '@/components/feeds/DiscordFeed';
@ -17,7 +16,6 @@ const Feed = () => {
const [selectedTopic, setSelectedTopic] = useState('global');
const [title, setTitle] = useState('Community');
const allTopics = ['global', 'nostr', 'discord', 'stackernews'];
const [searchQuery, setSearchQuery] = useState('');
const router = useRouter();
@ -50,38 +48,31 @@ const Feed = () => {
};
return (
<div className="w-full max-w-5xl mx-auto">
<div className="mb-6">
<div className='mb-4 flex flex-row items-end'>
<h1 className="font-bold mb-0">Feeds</h1>
<GenericButton
icon={getTagIcon(title)}
className='ml-2 text-sm p-2 py-1 flex items-center cursor-default hover:bg-transparent'
outlined
severity={{
'global': 'success',
'discord': 'primary',
'stackernews': 'warning',
'nostr': 'help'
}[title] || 'info'}
label={`${title}`}
/>
</div>
<div className='w-full flex flex-row items-center justify-between mb-2'>
<InputText
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search"
icon="pi pi-search"
className="w-fit"
/>
<div className="w-full mx-auto px-10">
<div className="mb-2">
<div className='flex flex-col'>
<div className='my-4 flex flex-row items-end'>
<h1 className="font-bold mb-0">Feeds</h1>
<GenericButton
icon={getTagIcon(title)}
className='ml-2 text-sm p-2 py-1 flex items-center cursor-default hover:bg-transparent'
outlined
severity={{
'global': 'success',
'discord': 'primary',
'stackernews': 'warning',
'nostr': 'help'
}[title] || 'info'}
label={`${title}`}
/>
</div>
{selectedTopic === 'nostr' && (
<div className='w-full mt-2'>
<MessageInput />
</div>
)}
</div>
<Divider />
{selectedTopic === 'nostr' && (
<MessageInput
collapsed={false}
/>
)}
</div>
<CommunityMenuTab
items={allTopics}
@ -90,10 +81,10 @@ const Feed = () => {
className="mb-4"
/>
<div className="feed-content">
{selectedTopic === 'global' && <GlobalFeed searchQuery={searchQuery} />}
{selectedTopic === 'nostr' && <NostrFeed searchQuery={searchQuery} />}
{selectedTopic === 'discord' && <DiscordFeed searchQuery={searchQuery} />}
{selectedTopic === 'stackernews' && <StackerNewsFeed searchQuery={searchQuery} />}
{selectedTopic === 'global' && <GlobalFeed />}
{selectedTopic === 'nostr' && <NostrFeed />}
{selectedTopic === 'discord' && <DiscordFeed />}
{selectedTopic === 'stackernews' && <StackerNewsFeed />}
</div>
</div>
);

View File

@ -53,7 +53,7 @@ const Profile = () => {
if (!session) return null;
return (
<div className="w-full min-h-full min-bottom-bar:w-[86vw] mx-auto">
<div className="w-full min-h-full mx-auto px-10">
<TabView
pt={{
root: {

View File

@ -123,7 +123,7 @@ const Subscribe = () => {
];
return (
<div className="p-4">
<div className="w-full px-12 my-4">
{windowWidth < 768 && (
<h1 className="text-3xl font-bold mb-6">Subscription Management</h1>
)}