From 7091da9ecd597936ec351500730ad9fa644073f9 Mon Sep 17 00:00:00 2001 From: austinkelsay Date: Sat, 14 Sep 2024 18:05:59 -0500 Subject: [PATCH] Mobile styling fixes, search bar and interactive carousela now both play along with mobile view --- .../InteractivePromotionalCarousel.js | 211 ++++++++++++++++++ .../content/courses/CourseDetails.js | 2 +- .../feeds/messages/CommunityMessage.js | 2 +- src/components/interactive-news-carousel.jsx | 137 ------------ src/components/navbar/Navbar.js | 4 +- src/components/search/SearchBar.js | 6 +- src/pages/content/index.js | 8 - src/pages/course/[slug]/draft/edit.js | 9 + src/pages/course/[slug]/draft/index.js | 11 +- src/pages/draft/[slug]/edit.js | 9 + src/pages/draft/[slug]/index.js | 10 + src/pages/index.js | 6 +- src/utils/text.js | 2 +- 13 files changed, 260 insertions(+), 157 deletions(-) create mode 100644 src/components/content/carousels/InteractivePromotionalCarousel.js delete mode 100644 src/components/interactive-news-carousel.jsx diff --git a/src/components/content/carousels/InteractivePromotionalCarousel.js b/src/components/content/carousels/InteractivePromotionalCarousel.js new file mode 100644 index 0000000..584edb1 --- /dev/null +++ b/src/components/content/carousels/InteractivePromotionalCarousel.js @@ -0,0 +1,211 @@ +import Image from "next/image" +import { useState } from "react" +import { useImageProxy } from "@/hooks/useImageProxy" +import GenericButton from "@/components/buttons/GenericButton" +import { useRouter } from "next/router" +import useWindowWidth from "@/hooks/useWindowWidth" + +// With current spacing the title can only be 1 line +const promotions = [ + { + id: 1, + category: "PLEBDEVS", + title: "Developer education & community platform", + description: "PlebDevs is your gateway to mastering Bitcoin, Lightning, and Nostr technologies. Join our community of aspiring developers and start your journey today!", + icon: "pi pi-code", + image: "https://media.istockphoto.com/id/537331500/photo/programming-code-abstract-technology-background-of-software-deve.jpg?s=612x612&w=0&k=20&c=jlYes8ZfnCmD0lLn-vKvzQoKXrWaEcVypHnB5MuO-g8=", + }, + { + id: 2, + category: "COURSES", + title: "Structured learning paths for new devs", + description: "Dive into our comprehensive courses covering Bitcoin protocol, Lightning Network, and Nostr. From basics to advanced topics, we've got you covered.", + icon: "pi pi-book", + image: "https://media.istockphoto.com/id/1224500457/photo/programming-code-abstract-technology-background-of-software-developer-and-computer-script.jpg?s=612x612&w=0&k=20&c=nHMypkMTU1HUUW85Zt0Ff7MDbq17n0eVeXaoM9Knt4Q=", + }, + { + id: 3, + category: "WORKSHOPS", + title: "Hands-on Video Workshops", + description: "Watch and learn with our interactive video workshops. Get practical experience building real Bitcoin and Lightning applications.", + icon: "pi pi-video", + image: "https://newsroom.siliconslopes.com/content/images/2018/10/code.jpg", + }, + { + id: 4, + category: "RESOURCES", + title: "In-depth Resources and Documentation", + description: "Access our extensive library of resources, including guides, documentation, and best practices for Bitcoin development.", + icon: "pi pi-file", + image: "https://img.freepik.com/free-photo/programming-background-with-person-working-with-codes-computer_23-2150010125.jpg", + }, + { + id: 5, + category: "COMMUNITY", + title: "Join Our Community", + description: "Connect with other developers, share your projects, and get support from our community of Bitcoin enthusiasts.", + icon: "pi pi-users", + image: "https://pikwizard.com/pw/medium/50238b1cad4ff412fdafc1325efa1c9f.jpg", + }, + { + id: 6, + category: "LIGHTNING / NOSTR", + title: "Lightning and Nostr integrated", + description: "This platform is the first of its kind to integrate Lightning Network and Nostr protocols, allowing users to send and receive payments and interact with the Nostr network.", + icon: "pi pi-bolt", + image: "https://www.financemagnates.com/wp-content/uploads/2016/05/Bicoin-lightning.jpg", + }, +] + +const InteractivePromotionalCarousel = () => { + const [selectedPromotion, setSelectedPromotion] = useState(promotions[0]) + const { returnImageProxy } = useImageProxy(); + const windowWidth = useWindowWidth(); + const isMobileView = windowWidth <= 1360; + const router = useRouter(); + + return ( +
+
+ {selectedPromotion.title} + {isMobileView ? ( +
+
{selectedPromotion.category}
+

+ {selectedPromotion.title} +

+

{selectedPromotion.description}

+
+ { + (() => { + switch (selectedPromotion.category) { + case "PLEBDEVS": + return ( + <> + router.push('/about')} severity="success" icon={} label="Learn More" className="py-2 font-semibold" size="small" outlined /> + router.push('/subscribe')} severity="warning" icon={} label="Subscribe" className="py-2 font-semibold" size="small" outlined /> + router.push('/content?tag=all')} severity="primary" icon={} label="View all content" className="py-2 font-semibold" size="small" outlined /> + + ); + case "COURSES": + return ( + router.push('/content?tag=courses')} icon={} label="View All Courses" className="py-2 font-semibold" size="small" outlined /> + ); + case "WORKSHOPS": + return ( + router.push('/content?tag=workshops')} icon={} label="View All Workshops" className="py-2 font-semibold" size="small" outlined /> + ); + case "RESOURCES": + return ( + router.push('/content?tag=resources')} icon={} label="View All Resources" className="py-2 font-semibold" size="small" outlined /> + ); + case "COMMUNITY": + return ( + router.push('/feed?channel=global')} icon={} label="Open Community Feed" className="py-2 font-semibold" size="small" outlined /> + ); + case "LIGHTNING / NOSTR": + return ( + router.push('/subscribe')} severity="warning" icon={} label="Subscribe" className="py-2 font-semibold" size="small" outlined /> + ); + default: + return null; + } + })() + } +
+
+ ) : ( + <> +
+
+
{selectedPromotion.category}
+

+ {selectedPromotion.title} +

+

{selectedPromotion.description}

+
+ { + (() => { + switch (selectedPromotion.category) { + case "PLEBDEVS": + return ( + <> + router.push('/about')} severity="success" icon={} label="Learn More" className="py-2 font-semibold" size="small" outlined /> + router.push('/subscribe')} severity="warning" icon={} label="Subscribe" className="py-2 font-semibold" size="small" outlined /> + router.push('/content?tag=all')} severity="primary" icon={} label="View all content" className="py-2 font-semibold" size="small" outlined /> + + ); + case "COURSES": + return ( + router.push('/content?tag=courses')} icon={} label="View All Courses" className="py-2 font-semibold" size="small" outlined /> + ); + case "WORKSHOPS": + return ( + router.push('/content?tag=workshops')} icon={} label="View All Workshops" className="py-2 font-semibold" size="small" outlined /> + ); + case "RESOURCES": + return ( + router.push('/content?tag=resources')} icon={} label="View All Resources" className="py-2 font-semibold" size="small" outlined /> + ); + case "COMMUNITY": + return ( + router.push('/feed?channel=global')} icon={} label="Open Community Feed" className="py-2 font-semibold" size="small" outlined /> + ); + case "LIGHTNING / NOSTR": + return ( + router.push('/subscribe')} severity="warning" icon={} label="Subscribe" className="py-2 font-semibold" size="small" outlined /> + ); + default: + return null; + } + })() + } +
+
+ + )} +
+
+ {isMobileView ? ( +
+ {promotions.map((promo) => ( +
setSelectedPromotion(promo)}> +
+ +
{promo.category}
+
+

{promo.title}

+
+ ))} +
+ ) : ( + promotions.map((promo) => ( +
setSelectedPromotion(promo)}> +
+ +
{promo.category}
+
+

{promo.title}

+
+ )) + )} +
+
+ ); +} + +export default InteractivePromotionalCarousel; \ No newline at end of file diff --git a/src/components/content/courses/CourseDetails.js b/src/components/content/courses/CourseDetails.js index 6b8581b..3cd96a5 100644 --- a/src/components/content/courses/CourseDetails.js +++ b/src/components/content/courses/CourseDetails.js @@ -179,7 +179,7 @@ export default function CourseDetails({ processedEvent, paidCourse, lessons, dec anchor={nAddress} user={user?.pubkey || null} relays="wss://nos.lol/, wss://relay.damus.io/, wss://relay.snort.social/, wss://relay.nostr.band/, wss://relay.mutinywallet.com/, wss://relay.primal.net/" - disable="" + disable="zaps" />
)} diff --git a/src/components/feeds/messages/CommunityMessage.js b/src/components/feeds/messages/CommunityMessage.js index 7aee020..169c669 100644 --- a/src/components/feeds/messages/CommunityMessage.js +++ b/src/components/feeds/messages/CommunityMessage.js @@ -85,7 +85,7 @@ const CommunityMessage = ({ message, searchQuery, windowWidth, platform }) => { anchor={nip19.noteEncode(message.id)} user={npub || null} relays="wss://nos.lol/, wss://relay.damus.io/, wss://relay.snort.social/, wss://relay.nostr.band/, wss://relay.mutinywallet.com/, wss://relay.primal.net/" - disable="" + disable="zaps" /> diff --git a/src/components/interactive-news-carousel.jsx b/src/components/interactive-news-carousel.jsx deleted file mode 100644 index a78034b..0000000 --- a/src/components/interactive-news-carousel.jsx +++ /dev/null @@ -1,137 +0,0 @@ -import Image from "next/image" -import { useState } from "react" -import { useImageProxy } from "@/hooks/useImageProxy" -import GenericButton from "@/components/buttons/GenericButton" -import { useRouter } from "next/router" -const promotions = [ - { - id: 1, - category: "PLEBDEVS", - title: "Learn how to code, build Bitcoin, Lightning, and Nostr apps, become a developer.", - description: "PlebDevs is your gateway to mastering Bitcoin, Lightning, and Nostr technologies. Join our community of aspiring developers and start your journey today!", - icon: "pi pi-code", - image: "https://media.istockphoto.com/id/537331500/photo/programming-code-abstract-technology-background-of-software-deve.jpg?s=612x612&w=0&k=20&c=jlYes8ZfnCmD0lLn-vKvzQoKXrWaEcVypHnB5MuO-g8=", - }, - { - id: 2, - category: "COURSES", - title: "Structured Learning Paths for Bitcoin Developers", - description: "Dive into our comprehensive courses covering Bitcoin protocol, Lightning Network, and Nostr. From basics to advanced topics, we've got you covered.", - icon: "pi pi-book", - image: "https://media.istockphoto.com/id/1224500457/photo/programming-code-abstract-technology-background-of-software-developer-and-computer-script.jpg?s=612x612&w=0&k=20&c=nHMypkMTU1HUUW85Zt0Ff7MDbq17n0eVeXaoM9Knt4Q=", - }, - { - id: 3, - category: "WORKSHOPS", - title: "Hands-on Video Workshops", - description: "Watch and learn with our interactive video workshops. Get practical experience building real Bitcoin and Lightning applications.", - icon: "pi pi-video", - image: "https://newsroom.siliconslopes.com/content/images/2018/10/code.jpg", - }, - { - id: 4, - category: "RESOURCES", - title: "In-depth Resources and Documentation", - description: "Access our extensive library of resources, including guides, documentation, and best practices for Bitcoin development.", - icon: "pi pi-file", - image: "https://img.freepik.com/free-photo/programming-background-with-person-working-with-codes-computer_23-2150010125.jpg", - }, - { - id: 5, - category: "COMMUNITY", - title: "Join Our Community", - description: "Connect with other developers, share your projects, and get support from our community of Bitcoin enthusiasts.", - icon: "pi pi-users", - image: "https://pikwizard.com/pw/medium/50238b1cad4ff412fdafc1325efa1c9f.jpg", - }, - { - id: 6, - category: "LIGHTNING / NOSTR", - title: "Lightning and Nostr integrated", - description: "This platform is the first of its kind to integrate Lightning Network and Nostr protocols, allowing users to send and receive payments and interact with the Nostr network.", - icon: "pi pi-bolt", - image: "https://www.financemagnates.com/wp-content/uploads/2016/05/Bicoin-lightning.jpg", - }, -] - -export function InteractivePromotionalCarousel() { - const [selectedPromotion, setSelectedPromotion] = useState(promotions[0]) - const { returnImageProxy } = useImageProxy(); - const router = useRouter(); - - return ( -
-
- {selectedPromotion.title} -
{/* Modified gradient */} -
-
{selectedPromotion.category}
{/* Changed color */} -

- {selectedPromotion.title} -

-

{selectedPromotion.description}

-
- { - (() => { - switch (selectedPromotion.category) { - case "PLEBDEVS": - return ( - <> - router.push('/about')} severity="success" icon={} label="Learn More" className="py-2 font-semibold" size="small" outlined /> - router.push('/subscribe')} severity="warning" icon={} label="Subscribe" className="py-2 font-semibold" size="small" outlined /> - router.push('/content?tag=all')} severity="primary" icon={} label="View all content" className="py-2 font-semibold" size="small" outlined /> - - ); - case "COURSES": - return ( - router.push('/content?tag=courses')} icon={} label="View All Courses" className="py-2 font-semibold" size="small" outlined /> - ); - case "WORKSHOPS": - return ( - router.push('/content?tag=workshops')} icon={} label="View All Workshops" className="py-2 font-semibold" size="small" outlined /> - ); - case "RESOURCES": - return ( - router.push('/content?tag=resources')} icon={} label="View All Resources" className="py-2 font-semibold" size="small" outlined /> - ); - case "COMMUNITY": - return ( - router.push('/feed?channel=global')} icon={} label="Open Community Feed" className="py-2 font-semibold" size="small" outlined /> - ); - case "LIGHTNING / NOSTR": - return ( - router.push('/subscribe')} severity="warning" icon={} label="Subscribe" className="py-2 font-semibold" size="small" outlined /> - ); - default: - return null; - } - })() - } -
-
-
-
{/* Reduced space-y */} - {promotions.map((promo) => ( -
setSelectedPromotion(promo)}> -
- {/* Changed icon color */} -
{promo.category}
{/* Changed text style */} -
-

{promo.title}

{/* Changed text style */} -
- ))} -
-
- ); -} \ No newline at end of file diff --git a/src/components/navbar/Navbar.js b/src/components/navbar/Navbar.js index 09a5944..b7c65a9 100644 --- a/src/components/navbar/Navbar.js +++ b/src/components/navbar/Navbar.js @@ -7,9 +7,11 @@ import SearchBar from '../search/SearchBar'; import 'primereact/resources/primereact.min.css'; import 'primeicons/primeicons.css'; import { useNDKContext } from '@/context/NDKContext'; +import useWindowWidth from '@/hooks/useWindowWidth'; const Navbar = () => { const router = useRouter(); + const windowWidth = useWindowWidth(); const navbarHeight = '60px'; const {ndk} = useNDKContext(); @@ -25,7 +27,7 @@ const Navbar = () => { />

PlebDevs

- {ndk && } + {ndk && windowWidth > 600 && } ); diff --git a/src/components/search/SearchBar.js b/src/components/search/SearchBar.js index 709c575..7408ea2 100644 --- a/src/components/search/SearchBar.js +++ b/src/components/search/SearchBar.js @@ -9,12 +9,14 @@ import MessageDropdownItem from '@/components/content/dropdowns/MessageDropdownI import { useContentSearch } from '@/hooks/useContentSearch'; import { useCommunitySearch } from '@/hooks/useCommunitySearch'; import { useRouter } from 'next/router'; +import useWindowWidth from '@/hooks/useWindowWidth'; import styles from './searchbar.module.css'; const SearchBar = () => { const { searchContent, searchResults: contentResults } = useContentSearch(); const { searchCommunity, searchResults: communityResults } = useCommunitySearch(); const router = useRouter(); + const windowWidth = useWindowWidth(); const [selectedSearchOption, setSelectedSearchOption] = useState({ name: 'Content', code: 'content', icon: 'pi pi-video' }); const searchOptions = [ { name: 'Content', code: 'content', icon: 'pi pi-video' }, @@ -71,11 +73,11 @@ const SearchBar = () => { } return ( -
+
845 ? 'w-[300px]' : 'w-[160px]'}`} value={searchTerm} onChange={handleSearch} placeholder={`Search ${selectedSearchOption.name.toLowerCase()}`} diff --git a/src/pages/content/index.js b/src/pages/content/index.js index 570d670..9e49d42 100644 --- a/src/pages/content/index.js +++ b/src/pages/content/index.js @@ -76,7 +76,6 @@ const ContentPage = () => { const [processedCourses, setProcessedCourses] = useState([]); const [allContent, setAllContent] = useState([]); const [allTopics, setAllTopics] = useState([]); - const [searchQuery, setSearchQuery] = useState(''); const [selectedTopic, setSelectedTopic] = useState('All') const [filteredContent, setFilteredContent] = useState([]); @@ -165,13 +164,6 @@ const ContentPage = () => {

All Content

- setSearchQuery(e.target.value)} - placeholder="Search" - icon="pi pi-search" - className="w-full" - />
!['Courses', 'Workshops', 'Resources'].includes(topic))]} diff --git a/src/pages/course/[slug]/draft/edit.js b/src/pages/course/[slug]/draft/edit.js index 10c99a6..97e0edc 100644 --- a/src/pages/course/[slug]/draft/edit.js +++ b/src/pages/course/[slug]/draft/edit.js @@ -4,12 +4,21 @@ import { useRouter } from "next/router"; import CourseForm from "@/components/forms/course/CourseForm"; import { useNDKContext } from "@/context/NDKContext"; import { useToast } from "@/hooks/useToast"; +import { useIsAdmin } from "@/hooks/useIsAdmin"; export default function Edit() { const [draft, setDraft] = useState(null); const { ndk } = useNDKContext(); const router = useRouter(); const { showToast } = useToast(); + const { isAdmin, isLoading } = useIsAdmin(); + useEffect(() => { + if (isLoading) return; + + if (!isAdmin) { + router.push('/'); + } + }, [isAdmin, router, isLoading]); useEffect(() => { if (router.isReady) { diff --git a/src/pages/course/[slug]/draft/index.js b/src/pages/course/[slug]/draft/index.js index 42513fd..5ea358b 100644 --- a/src/pages/course/[slug]/draft/index.js +++ b/src/pages/course/[slug]/draft/index.js @@ -6,17 +6,26 @@ import DraftCourseDetails from "@/components/content/courses/DraftCourseDetails" import DraftCourseLesson from "@/components/content/courses/DraftCourseLesson"; import { useNDKContext } from "@/context/NDKContext"; import { useSession } from "next-auth/react"; - +import { useIsAdmin } from "@/hooks/useIsAdmin"; const DraftCourse = () => { const { data: session, status } = useSession(); const [course, setCourse] = useState(null); const [lessons, setLessons] = useState([]); const [lessonsWithAuthors, setLessonsWithAuthors] = useState([]); + const { isAdmin, isLoading } = useIsAdmin(); const router = useRouter(); const {ndk, addSigner} = useNDKContext(); const { slug } = router.query; + useEffect(() => { + if (isLoading) return; + + if (!isAdmin) { + router.push('/'); + } + }, [isAdmin, router, isLoading]); + const fetchAuthor = useCallback(async (pubkey) => { if (!pubkey) return; const author = await ndk.getUser({ pubkey }); diff --git a/src/pages/draft/[slug]/edit.js b/src/pages/draft/[slug]/edit.js index 7267d6e..179026f 100644 --- a/src/pages/draft/[slug]/edit.js +++ b/src/pages/draft/[slug]/edit.js @@ -4,10 +4,19 @@ import axios from "axios"; import ResourceForm from "@/components/forms/ResourceForm"; import WorkshopForm from "@/components/forms/WorkshopForm"; import CourseForm from "@/components/forms/course/CourseForm"; +import { useIsAdmin } from "@/hooks/useIsAdmin"; const Edit = () => { const [draft, setDraft] = useState(null); const router = useRouter(); + const { isAdmin, isLoading } = useIsAdmin(); + useEffect(() => { + if (isLoading) return; + + if (!isAdmin) { + router.push('/'); + } + }, [isAdmin, router, isLoading]); useEffect(() => { if (router.isReady) { diff --git a/src/pages/draft/[slug]/index.js b/src/pages/draft/[slug]/index.js index b7fab53..0fa9485 100644 --- a/src/pages/draft/[slug]/index.js +++ b/src/pages/draft/[slug]/index.js @@ -18,6 +18,7 @@ import 'primeicons/primeicons.css'; import dynamic from 'next/dynamic'; import { validateEvent } from '@/utils/nostr'; import { defaultRelayUrls } from '@/context/NDKContext'; +import { useIsAdmin } from "@/hooks/useIsAdmin"; const MDDisplay = dynamic( () => import("@uiw/react-markdown-preview"), @@ -36,6 +37,15 @@ export default function Draft() { const router = useRouter(); const { showToast } = useToast(); const { ndk, addSigner } = useNDKContext(); + const { isAdmin, isLoading } = useIsAdmin(); + + useEffect(() => { + if (isLoading) return; + + if (!isAdmin) { + router.push('/'); + } + }, [isAdmin, router, isLoading]); useEffect(() => { if (session) { diff --git a/src/pages/index.js b/src/pages/index.js index 12338ac..e2898eb 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -2,12 +2,9 @@ import Head from 'next/head'; import React from 'react'; import CoursesCarousel from '@/components/content/carousels/CoursesCarousel'; import WorkshopsCarousel from '@/components/content/carousels/WorkshopsCarousel'; -import HeroBanner from '@/components/banner/HeroBanner'; import ResourcesCarousel from '@/components/content/carousels/ResourcesCarousel'; -import { InteractivePromotionalCarousel } from '@/components/interactive-news-carousel'; +import InteractivePromotionalCarousel from '@/components/content/carousels/InteractivePromotionalCarousel'; -// todo append link to plebdevs item in each published course or resource description -// todo perhaps need to update slug on details on course pages to be d tag instead of db id export default function Home() { return ( <> @@ -18,7 +15,6 @@ export default function Home() {
- {/* */} diff --git a/src/utils/text.js b/src/utils/text.js index 60caaa3..cd85ba6 100644 --- a/src/utils/text.js +++ b/src/utils/text.js @@ -3,7 +3,7 @@ export const highlightText = (text, query) => { const parts = text.split(new RegExp(`(${query})`, 'gi')); return parts.map((part, index) => part.toLowerCase() === query.toLowerCase() - ? {part} + ? {part} : part ); }; \ No newline at end of file