mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-04-19 19:01:19 +00:00
more ui improvements, sidebar, details page, navbar
This commit is contained in:
parent
6f398f2026
commit
be6bb02db0
@ -26,7 +26,7 @@ export default function CoursesCarousel() {
|
||||
|
||||
const courseTemplate = (course) => {
|
||||
return (
|
||||
<div onClick={() => router.push(`/course/${course.id}`)} className="flex flex-col items-center w-full px-4 cursor-pointer mt-8">
|
||||
<div onClick={() => router.push(`/details/${course.id}`)} className="flex flex-col items-center w-full px-4 cursor-pointer mt-8">
|
||||
<div className="w-86 h-60 bg-gray-200 overflow-hidden rounded-md shadow-lg">
|
||||
<Image
|
||||
alt="resource thumbnail"
|
||||
|
@ -3,31 +3,92 @@ import Image from 'next/image';
|
||||
import UserAvatar from './user/UserAvatar';
|
||||
import MenuTab from '../menutab/MenuTab';
|
||||
import { Menubar } from 'primereact/menubar';
|
||||
import { Menu } from 'primereact/menu';
|
||||
import { useRouter } from 'next/router';
|
||||
import 'primereact/resources/primereact.min.css';
|
||||
import 'primeicons/primeicons.css';
|
||||
import styles from './navbar.module.css';
|
||||
|
||||
const Navbar = () => {
|
||||
const router = useRouter();
|
||||
|
||||
const menu = useRef(null);
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
label: 'Home',
|
||||
icon: 'pi pi-home',
|
||||
command: () => {
|
||||
// Add your edit functionality here
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Content',
|
||||
icon: 'pi pi-video',
|
||||
command: () => {
|
||||
// Add your delete functionality here
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Chat',
|
||||
icon: 'pi pi-comment',
|
||||
items: [
|
||||
{
|
||||
label: 'General',
|
||||
icon: 'pi pi-hashtag',
|
||||
command: () => {
|
||||
// Add your edit functionality here
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Nostr',
|
||||
icon: 'pi pi-hashtag',
|
||||
command: () => {
|
||||
// Add your delete functionality here
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Discord',
|
||||
icon: 'pi pi-hashtag',
|
||||
command: () => {
|
||||
// Add your delete functionality here
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Stackernews',
|
||||
icon: 'pi pi-hashtag',
|
||||
command: () => {
|
||||
// Add your delete functionality here
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const start = (
|
||||
<div className='w-full flex flex-row justify-between'>
|
||||
<div onClick={() => router.push('/')} className={styles.titleContainer}>
|
||||
<div className='flex items-center'>
|
||||
<div className='hidden max-tab:block max-mob:block max-tab:px-6 max-mob:px-6'>
|
||||
<i className="pi pi-bars text-xl pt-1"
|
||||
onClick={(e) => menu.current.toggle(e)}></i>
|
||||
<Menu model={menuItems} popup ref={menu} />
|
||||
</div>
|
||||
<div onClick={() => router.push('/')} className="flex flex-row items-center justify-center cursor-pointer">
|
||||
<Image
|
||||
alt="logo"
|
||||
src="/plebdevs-guy.jpg"
|
||||
width={50}
|
||||
height={50}
|
||||
className={`${styles.logo}`}
|
||||
className="rounded-full mr-2 max-tab:hidden max-mob:hidden"
|
||||
/>
|
||||
<h1 className={styles.title}>PlebDevs</h1>
|
||||
<h1 className="text-white text-xl font-semibold max-tab:text-2xl max-mob:text-2xl">PlebDevs</h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Menubar start={start} end={UserAvatar} className='px-[2%] bg-gray-800 border-t-0 border-l-0 border-r-0 rounded-none' />
|
||||
<Menubar
|
||||
start={start}
|
||||
end={UserAvatar}
|
||||
className='px-[2%] bg-gray-800 border-t-0 border-l-0 border-r-0 rounded-none' />
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { Accordion, AccordionTab } from 'primereact/accordion';
|
||||
import { useRouter } from 'next/router';
|
||||
import 'primeicons/primeicons.css';
|
||||
@ -6,17 +6,21 @@ import 'primeicons/primeicons.css';
|
||||
const Sidebar = () => {
|
||||
const router = useRouter();
|
||||
|
||||
// Helper function to determine if the path matches the current route
|
||||
const isActive = (path) => {
|
||||
return router.pathname === path;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='w-64 bg-gray-800 p-4'>
|
||||
<div onClick={() => router.push('/')} className='w-full cursor-pointer hover:bg-gray-700 rounded-lg'>
|
||||
<p className="p-4 pl-5 rounded-md font-bold"><i className="pi pi-home" /> Home</p>
|
||||
<div className='max-mob:hidden max-tab:hidden w-72 bg-gray-800 p-4'>
|
||||
<div onClick={() => router.push('/')} className={`w-full cursor-pointer hover:bg-gray-700 rounded-lg ${isActive('/') ? 'bg-gray-700' : ''}`}>
|
||||
<p className="p-2 my-2 pl-5 rounded-md font-bold"><i className="pi pi-home" /> Home</p>
|
||||
</div>
|
||||
<div onClick={() => router.push('/content')} className='w-full cursor-pointer hover:bg-gray-700 rounded-lg'>
|
||||
<p className="p-4 pl-5 rounded-md font-bold"><i className="pi pi-video" /> Content</p>
|
||||
<div onClick={() => router.push('/content')} className={`w-full cursor-pointer hover:bg-gray-700 rounded-lg ${isActive('/content') ? 'bg-gray-700' : ''}`}>
|
||||
<p className="p-2 my-2 pl-5 rounded-md font-bold"><i className="pi pi-video" /> Content</p>
|
||||
</div>
|
||||
|
||||
<Accordion
|
||||
className="unstyled border-none bg-transparent"
|
||||
activeIndex={0}
|
||||
pt={{
|
||||
tab: {
|
||||
@ -24,25 +28,25 @@ const Sidebar = () => {
|
||||
className: 'border-none bg-transparent hover:bg-gray-700 rounded-lg',
|
||||
}),
|
||||
headerAction: ({ context }) => ({
|
||||
className: 'border-none bg-transparent',
|
||||
className: 'border-none bg-transparent py-3 my-2',
|
||||
}),
|
||||
content: { className: 'border-none bg-transparent pt-0' }
|
||||
}
|
||||
}}
|
||||
>
|
||||
className="unstyled border-none bg-transparent">
|
||||
<AccordionTab header={"Chat"}>
|
||||
<div onClick={() => router.push('/chat/general')} className='w-full cursor-pointer hover:bg-gray-700 rounded-lg'>
|
||||
<p className="p-4 rounded-md font-bold"><i className="pi pi-hashtag"></i> general</p>
|
||||
</div>
|
||||
<div onClick={() => router.push('/chat/nostr')} className='w-full cursor-pointer hover:bg-gray-700 rounded-lg'>
|
||||
<p className="p-4 rounded-md font-bold"><i className="pi pi-hashtag"></i> nostr</p>
|
||||
</div>
|
||||
<div onClick={() => router.push('/chat/discord')} className='w-full cursor-pointer hover:bg-gray-700 rounded-lg'>
|
||||
<p className="p-4 rounded-md font-bold"><i className="pi pi-hashtag"></i> discord</p>
|
||||
</div>
|
||||
<div onClick={() => router.push('/chat/stackernews')} className='w-full cursor-pointer hover:bg-gray-700 rounded-lg'>
|
||||
<p className="p-4 rounded-md font-bold"><i className="pi pi-hashtag"></i> stackernews</p>
|
||||
</div>
|
||||
<div onClick={() => router.push('/chat/general')} className={`w-full cursor-pointer hover:bg-gray-700 rounded-lg ${isActive('/chat/general') ? 'bg-gray-700' : ''}`}>
|
||||
<p className="p-2 my-2 rounded-md font-bold"><i className="pi pi-hashtag"></i> general</p>
|
||||
</div>
|
||||
<div onClick={() => router.push('/chat/nostr')} className={`w-full cursor-pointer hover:bg-gray-700 rounded-lg ${isActive('/chat/nostr') ? 'bg-gray-700' : ''}`}>
|
||||
<p className="p-2 my-2 rounded-md font-bold"><i className="pi pi-hashtag"></i> nostr</p>
|
||||
</div>
|
||||
<div onClick={() => router.push('/chat/discord')} className={`w-full cursor-pointer hover:bg-gray-700 rounded-lg ${isActive('/chat/discord') ? 'bg-gray-700' : ''}`}>
|
||||
<p className="p-2 my-2 rounded-md font-bold"><i className="pi pi-hashtag"></i> discord</p>
|
||||
</div>
|
||||
<div onClick={() => router.push('/chat/stackernews')} className={`w-full cursor-pointer hover:bg-gray-700 rounded-lg ${isActive('/chat/stackernews') ? 'bg-gray-700' : ''}`}>
|
||||
<p className="p-2 my-2 rounded-md font-bold"><i className="pi pi-hashtag"></i> stackernews</p>
|
||||
</div>
|
||||
</AccordionTab>
|
||||
</Accordion>
|
||||
</div>
|
||||
|
@ -9,6 +9,7 @@ import { formatTimestampToHowLongAgo } from '@/utils/time';
|
||||
|
||||
export default function WorkshopsCarousel() {
|
||||
const resources = useSelector((state) => state.events.resources);
|
||||
console.log(resources);
|
||||
const [processedResources, setProcessedResources] = useState([]);
|
||||
const { returnImageProxy } = useImageProxy();
|
||||
|
||||
@ -24,7 +25,7 @@ export default function WorkshopsCarousel() {
|
||||
|
||||
const resourceTemplate = (resource) => {
|
||||
return (
|
||||
<div onClick={() => router.push(`/resource/${resource.id}`)} className="flex flex-col items-center w-full px-4 cursor-pointer mt-8">
|
||||
<div onClick={() => router.push(`/details/${resource.id}`)} className="flex flex-col items-center w-full px-4 cursor-pointer mt-8">
|
||||
<div className="w-86 h-60 bg-gray-200 overflow-hidden rounded-md shadow-lg">
|
||||
<Image
|
||||
alt="resource thumbnail"
|
||||
|
@ -1,5 +1,4 @@
|
||||
import Head from 'next/head'
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import CoursesCarousel from '@/components/courses/CoursesCarousel'
|
||||
import WorkshopsCarousel from '@/components/workshops/WorkshopsCarousel'
|
||||
import MenuTab from '@/components/menutab/MenuTab'
|
||||
@ -22,18 +21,10 @@ export default function Content() {
|
||||
}, [fetchResources, fetchCourses]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Create Next App</title>
|
||||
<meta name="description" content="Generated by create next app" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<main>
|
||||
<MenuTab items={homeItems} />
|
||||
<CoursesCarousel />
|
||||
<WorkshopsCarousel />
|
||||
</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
62
src/pages/details/[slug].js
Normal file
62
src/pages/details/[slug].js
Normal file
@ -0,0 +1,62 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useNostr } from '@/hooks/useNostr';
|
||||
import { parseEvent } from '@/utils/nostr';
|
||||
import { useImageProxy } from '@/hooks/useImageProxy';
|
||||
import Image from 'next/image';
|
||||
import 'primeicons/primeicons.css';
|
||||
|
||||
export default function Details() {
|
||||
const [event, setEvent] = useState(null);
|
||||
const [processedEvent, setProcessedEvent] = useState({});
|
||||
|
||||
const { returnImageProxy } = useImageProxy();
|
||||
const { fetchSingleEvent } = useNostr();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (router.isReady) {
|
||||
const { slug } = router.query;
|
||||
|
||||
const fetchEvent = async (slug) => {
|
||||
const event = await fetchSingleEvent(slug);
|
||||
if (event) {
|
||||
setEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
fetchEvent(slug);
|
||||
}
|
||||
}, [router.isReady, router.query]);
|
||||
|
||||
useEffect(() => {
|
||||
if (event) {
|
||||
const { id, content, title, summary, image, published_at } = parseEvent(event);
|
||||
setProcessedEvent({ id, content, title, summary, image, published_at });
|
||||
}
|
||||
}, [event]);
|
||||
|
||||
return (
|
||||
<div className='flex flex-row justify-between m-4'>
|
||||
<i className='pi pi-arrow-left cursor-pointer hover:opacity-75' onClick={() => router.push('/')} />
|
||||
<div className='flex flex-col'>
|
||||
{
|
||||
processedEvent && (
|
||||
<>
|
||||
<Image
|
||||
alt="resource thumbnail"
|
||||
src={returnImageProxy(processedEvent.image)}
|
||||
width={344}
|
||||
height={194}
|
||||
className="w-full h-full object-cover object-center rounded-lg"
|
||||
/>
|
||||
<h2>{processedEvent.title}</h2>
|
||||
<p>{processedEvent.summary}</p>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import React, {useRef} from "react";
|
||||
import { Button } from "primereact/button";
|
||||
import { DataTable } from 'primereact/datatable';
|
||||
import { Menu } from 'primereact/menu';
|
||||
import { Column } from 'primereact/column';
|
||||
import { useSelector } from "react-redux";
|
||||
import { useImageProxy } from "@/hooks/useImageProxy";
|
||||
@ -9,44 +10,85 @@ import Image from "next/image";
|
||||
const Profile = () => {
|
||||
const user = useSelector((state) => state.user.user);
|
||||
const { returnImageProxy } = useImageProxy();
|
||||
const menu = useRef(null);
|
||||
|
||||
const purchases = [
|
||||
{ code: '123', name: 'Product 1', category: 'Category 1', quantity: 1 },
|
||||
{ code: '124', name: 'Product 2', category: 'Category 2', quantity: 2 },
|
||||
{ code: '125', name: 'Product 3', category: 'Category 3', quantity: 3 },
|
||||
{ code: '126', name: 'Product 4', category: 'Category 4', quantity: 4 },
|
||||
{ code: '127', name: 'Product 5', category: 'Category 5', quantity: 5 },
|
||||
{
|
||||
cost: 100,
|
||||
name: 'Course 1',
|
||||
category: 'Education',
|
||||
date: '2021-09-01'
|
||||
},
|
||||
{
|
||||
cost: 200,
|
||||
name: 'Course 2',
|
||||
category: 'Education',
|
||||
date: '2021-09-01'
|
||||
},
|
||||
{
|
||||
cost: 300,
|
||||
name: 'Course 3',
|
||||
category: 'Education',
|
||||
date: '2021-09-01'
|
||||
},
|
||||
{
|
||||
cost: 400,
|
||||
name: 'Course 4',
|
||||
category: 'Education',
|
||||
date: '2021-09-01'
|
||||
}
|
||||
];
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
label: 'Edit',
|
||||
icon: 'pi pi-pencil',
|
||||
command: () => {
|
||||
// Add your edit functionality here
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
icon: 'pi pi-trash',
|
||||
command: () => {
|
||||
// Add your delete functionality here
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col justify-center">
|
||||
{user.avatar && (
|
||||
<Image
|
||||
alt="logo"
|
||||
src={returnImageProxy(user.avatar)}
|
||||
width={100}
|
||||
height={100}
|
||||
className="rounded-full mx-auto my-4"
|
||||
/>
|
||||
)}
|
||||
<div className="flex flex-col justify-center px-12">
|
||||
<div className="relative flex w-full items-center justify-center">
|
||||
{user.avatar && (
|
||||
<Image
|
||||
alt="user's avatar"
|
||||
src={returnImageProxy(user.avatar)}
|
||||
width={100}
|
||||
height={100}
|
||||
className="rounded-full my-4"
|
||||
/>
|
||||
)}
|
||||
<i className="pi pi-ellipsis-h absolute right-24 text-2xl my-4 cursor-pointer hover:opacity-75"
|
||||
onClick={(e) => menu.current.toggle(e)}></i>
|
||||
<Menu model={menuItems} popup ref={menu} />
|
||||
</div>
|
||||
|
||||
|
||||
<h1 className="text-center text-2xl my-2">{user.username}</h1>
|
||||
<h2 className="text-center text-xl my-2">{user.pubkey}</h2>
|
||||
<div className="flex flex-row w-1/2 mx-auto my-4 justify-between">
|
||||
<Button label="Edit" className="p-button-raised text-[#f8f8ff]" />
|
||||
<Button label="Delete" className="p-button-raised p-button-danger text-[#f8f8ff]" />
|
||||
</div>
|
||||
<div className="flex flex-col w-1/2 mx-auto my-4 justify-between items-center">
|
||||
<h2>Subscription</h2>
|
||||
<p>You currently have no active subscription</p>
|
||||
<Button label="Subscribe" className="p-button-raised p-button-success w-auto my-2 text-[#f8f8ff]" />
|
||||
</div>
|
||||
</div>
|
||||
<DataTable value={purchases} tableStyle={{ minWidth: '50rem' }}>
|
||||
<Column field="code" header="Code"></Column>
|
||||
<DataTable emptyMessage="No purchases" value={purchases} tableStyle={{ minWidth: '10rem' }}>
|
||||
<Column field="cost" header="Cost"></Column>
|
||||
<Column field="name" header="Name"></Column>
|
||||
<Column field="category" header="Category"></Column>
|
||||
<Column field="quantity" header="Quantity"></Column>
|
||||
<Column field="date" header="Date"></Column>
|
||||
</DataTable>
|
||||
</>
|
||||
)
|
||||
|
@ -5,15 +5,16 @@ const addItems = (state, action, key) => {
|
||||
const existingIds = new Set(state[key].map(item => item.id));
|
||||
|
||||
if (Array.isArray(action.payload)) {
|
||||
console.log('action.payload', action.payload);
|
||||
// Filter out duplicates based on the id
|
||||
const uniqueItems = action.payload.filter(item => !existingIds.has(item.id));
|
||||
// If payload is an array, spread it into the existing array without duplicates
|
||||
state[key] = [...state[key], ...uniqueItems];
|
||||
state[key] = [...state[key], ...action.payload];
|
||||
} else {
|
||||
// If payload is a single item, push it into the array if it's not a duplicate
|
||||
if (!existingIds.has(action.payload.id)) {
|
||||
// if (!existingIds.has(action.payload.id)) {
|
||||
state[key].push(action.payload);
|
||||
}
|
||||
// }
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -18,3 +18,8 @@
|
||||
background-color: transparent !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.p-menubar-button .p-icon {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
@ -7,10 +7,13 @@ module.exports = {
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
screens: {
|
||||
'max-mob': {'max': '475px'},
|
||||
'max-tab': {'max': '768px'},
|
||||
},
|
||||
backgroundImage: {
|
||||
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
|
||||
'gradient-conic':
|
||||
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
|
||||
'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user