From d13c7ca6c318fc4b34915790793a6ee9e91a715b Mon Sep 17 00:00:00 2001 From: freakoverse Date: Thu, 12 Sep 2024 12:16:15 +0000 Subject: [PATCH 01/22] added hover effect --- src/styles/cardBlogs.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/styles/cardBlogs.css b/src/styles/cardBlogs.css index 7861ec7..68f0d6f 100644 --- a/src/styles/cardBlogs.css +++ b/src/styles/cardBlogs.css @@ -26,6 +26,7 @@ } .cardBlogMainInside { + transition: ease 0.4s; position: absolute; top: 0; bottom: 0; @@ -36,5 +37,10 @@ justify-content: end; align-items: start; padding: 15px; + background: linear-gradient(rgb(0 0 0 / 0%) 0%, rgb(0 0 0 / 50%) 100%); +} + +.cardBlogMainInside:hover { + background: linear-gradient(rgb(0 0 0 / 25%) 0%, rgb(0 0 0 / 75%) 100%); } -- 2.34.1 From a56d26387e2217a8b8f4b89dcef1ef7ea50f896d Mon Sep 17 00:00:00 2001 From: freakoverse Date: Thu, 12 Sep 2024 12:17:48 +0000 Subject: [PATCH 02/22] removed inline css in one element --- src/components/BlogCard.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/BlogCard.tsx b/src/components/BlogCard.tsx index 2ff0fa2..e2065b7 100644 --- a/src/components/BlogCard.tsx +++ b/src/components/BlogCard.tsx @@ -15,10 +15,6 @@ export const BlogCard = ({ backgroundLink }: BlogCardProps) => { >

Date: Thu, 12 Sep 2024 12:24:00 +0000 Subject: [PATCH 03/22] Update src/styles/cardBlogs.css --- src/styles/cardBlogs.css | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/styles/cardBlogs.css b/src/styles/cardBlogs.css index 68f0d6f..1991b68 100644 --- a/src/styles/cardBlogs.css +++ b/src/styles/cardBlogs.css @@ -37,10 +37,12 @@ justify-content: end; align-items: start; padding: 15px; - background: linear-gradient(rgb(0 0 0 / 0%) 0%, rgb(0 0 0 / 50%) 100%); + background: linear-gradient(rgb(0 0 0 / 0%) 0%, rgb(0 0 0 / 75%) 100%); } .cardBlogMainInside:hover { - background: linear-gradient(rgb(0 0 0 / 25%) 0%, rgb(0 0 0 / 75%) 100%); + transition: ease 0.4s; + background: linear-gradient(rgb(0 0 0 / 35%) 0%, rgb(0 0 0 / 85%) 100%); + backdrop-filter: blur(5px); } -- 2.34.1 From d70e302a69c76ddb986dceb6e92767a307df85d1 Mon Sep 17 00:00:00 2001 From: freakoverse Date: Thu, 12 Sep 2024 14:51:44 +0000 Subject: [PATCH 04/22] Update src/styles/popup.css --- src/styles/popup.css | 1 - 1 file changed, 1 deletion(-) diff --git a/src/styles/popup.css b/src/styles/popup.css index 260519e..2c03c5c 100644 --- a/src/styles/popup.css +++ b/src/styles/popup.css @@ -53,7 +53,6 @@ align-items: center; padding: 25px; overflow: auto; - max-height: 70vh; } .popUpMainCardBottomQR { -- 2.34.1 From 22fc2b4ba3b8f83fb2196db130bd1f62f0d4af27 Mon Sep 17 00:00:00 2001 From: daniyal Date: Mon, 16 Sep 2024 12:35:42 +0500 Subject: [PATCH 05/22] chore: used Link component from react-router-dom instead of a tag --- src/components/ModCard.tsx | 18 +++++------------- src/components/ProfileSection.tsx | 13 ++++--------- src/pages/home.tsx | 7 ++----- src/pages/mod/internal/comment/index.tsx | 14 ++++---------- src/pages/mods.tsx | 5 +---- 5 files changed, 16 insertions(+), 41 deletions(-) diff --git a/src/components/ModCard.tsx b/src/components/ModCard.tsx index fe9f678..0ceb5d5 100644 --- a/src/components/ModCard.tsx +++ b/src/components/ModCard.tsx @@ -1,3 +1,4 @@ +import { Link } from 'react-router-dom' import '../styles/cardMod.css' import { handleModImageError } from '../utils' @@ -6,8 +7,7 @@ type ModCardProps = { gameName: string summary: string imageUrl: string - link: string - handleClick: () => void + route: string } export const ModCard = ({ @@ -15,18 +15,10 @@ export const ModCard = ({ gameName, summary, imageUrl, - link, - handleClick + route }: ModCardProps) => { return ( - { - e.preventDefault() - handleClick() - }} - > +
-
+ ) } diff --git a/src/components/ProfileSection.tsx b/src/components/ProfileSection.tsx index 3051b1f..b441582 100644 --- a/src/components/ProfileSection.tsx +++ b/src/components/ProfileSection.tsx @@ -1,7 +1,7 @@ import { Event, Filter, kinds, nip19, UnsignedEvent } from 'nostr-tools' import { QRCodeSVG } from 'qrcode.react' import { useState } from 'react' -import { useNavigate } from 'react-router-dom' +import { Link } from 'react-router-dom' import { toast } from 'react-toastify' import { MetadataController, @@ -23,7 +23,6 @@ type Props = { } export const ProfileSection = ({ pubkey }: Props) => { - const navigate = useNavigate() const [profile, setProfile] = useState() useDidMount(async () => { @@ -60,13 +59,9 @@ export const ProfileSection = ({ pubkey }: Props) => {
- +

{ - const navigate = useNavigate() const [mod, setMod] = useState() useDidMount(() => { @@ -249,8 +248,7 @@ const DisplayMod = ({ naddr }: DisplayModProps) => { gameName={mod.game} summary={mod.summary} imageUrl={mod.featuredImageUrl} - link={`#${route}`} - handleClick={() => navigate(route)} + route={route} /> ) } @@ -298,8 +296,7 @@ const DisplayLatestMods = () => { gameName={mod.game} summary={mod.summary} imageUrl={mod.featuredImageUrl} - link={`#${route}`} - handleClick={() => navigate(route)} + route={route} /> ) }) diff --git a/src/pages/mod/internal/comment/index.tsx b/src/pages/mod/internal/comment/index.tsx index 93ea146..f42a94b 100644 --- a/src/pages/mod/internal/comment/index.tsx +++ b/src/pages/mod/internal/comment/index.tsx @@ -15,7 +15,7 @@ import { } from 'nostr-tools' import React, { useEffect, useMemo } from 'react' import { Dispatch, SetStateAction, useState } from 'react' -import { useNavigate } from 'react-router-dom' +import { Link } from 'react-router-dom' import { toast } from 'react-toastify' import { getProfilePageRoute } from 'routes' import { ModDetails, UserProfile } from 'types' @@ -355,8 +355,6 @@ const Filter = React.memo( ) const Comment = (props: CommentEvent) => { - const navigate = useNavigate() - const [profile, setProfile] = useState() useDidMount(async () => { @@ -376,19 +374,15 @@ const Comment = (props: CommentEvent) => {

diff --git a/src/pages/mods.tsx b/src/pages/mods.tsx index ef8b62b..55c609b 100644 --- a/src/pages/mods.tsx +++ b/src/pages/mods.tsx @@ -7,7 +7,6 @@ import React, { useMemo, useState } from 'react' -import { useNavigate } from 'react-router-dom' import { LoadingSpinner } from '../components/LoadingSpinner' import { ModCard } from '../components/ModCard' import { MetadataController } from '../controllers' @@ -48,7 +47,6 @@ interface FilterOptions { } export const ModsPage = () => { - const navigate = useNavigate() const [isFetching, setIsFetching] = useState(false) const [mods, setMods] = useState([]) const [filterOptions, setFilterOptions] = useState({ @@ -223,8 +221,7 @@ export const ModsPage = () => { gameName={mod.game} summary={mod.summary} imageUrl={mod.featuredImageUrl} - link={`#${route}`} - handleClick={() => navigate(route)} + route={route} /> ) })} -- 2.34.1 From d3a93eab3eabdb7b3729e581fb05e45d395ab2b3 Mon Sep 17 00:00:00 2001 From: daniyal Date: Mon, 16 Sep 2024 12:36:35 +0500 Subject: [PATCH 06/22] chore: add social nav component in main layout --- src/layout/index.tsx | 2 + src/layout/socialNav.tsx | 115 +++++++++++++++++++++++++++++++++++++++ src/pages/search.tsx | 3 + src/routes/index.tsx | 6 ++ src/styles/socialNav.css | 98 +++++++++++++++++++++++++++++++++ 5 files changed, 224 insertions(+) create mode 100644 src/layout/socialNav.tsx create mode 100644 src/pages/search.tsx create mode 100644 src/styles/socialNav.css diff --git a/src/layout/index.tsx b/src/layout/index.tsx index d4796dd..257fa74 100644 --- a/src/layout/index.tsx +++ b/src/layout/index.tsx @@ -1,6 +1,7 @@ import { Outlet } from 'react-router-dom' import { Footer } from './footer' import { Header } from './header' +import { SocialNav } from './socialNav' export const Layout = () => { return ( @@ -8,6 +9,7 @@ export const Layout = () => {
+ ) } diff --git a/src/layout/socialNav.tsx b/src/layout/socialNav.tsx new file mode 100644 index 0000000..179d0bf --- /dev/null +++ b/src/layout/socialNav.tsx @@ -0,0 +1,115 @@ +import { useState } from 'react' +import { Link } from 'react-router-dom' +import { appRoutes, getProfilePageRoute } from 'routes' +import 'styles/socialNav.css' + +export const SocialNav = () => { + const [isCollapsed, setIsCollapsed] = useState(true) + + const toggleNav = () => { + setIsCollapsed(!isCollapsed) + } + + return ( +
+
+ {!isCollapsed && ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ )} + +
+ + + +
+
+
+ ) +} diff --git a/src/pages/search.tsx b/src/pages/search.tsx new file mode 100644 index 0000000..ea66cd8 --- /dev/null +++ b/src/pages/search.tsx @@ -0,0 +1,3 @@ +export const SearchPage = () => { + return

WIP

+} diff --git a/src/routes/index.tsx b/src/routes/index.tsx index d52735b..3cf4f53 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -1,3 +1,4 @@ +import { SearchPage } from 'pages/search' import { AboutPage } from '../pages/about' import { BlogsPage } from '../pages/blogs' import { GamesPage } from '../pages/games' @@ -20,6 +21,7 @@ export const appRoutes = { submitMod: '/submit-mod', editMod: '/edit-mod/:naddr', write: '/write', + search: '/search', settingsProfile: '/settings-profile', settingsRelays: '/settings-relays', settingsPreferences: '/settings-preferences', @@ -77,6 +79,10 @@ export const routes = [ path: appRoutes.write, element: }, + { + path: appRoutes.search, + element: + }, { path: appRoutes.settingsProfile, element: diff --git a/src/styles/socialNav.css b/src/styles/socialNav.css new file mode 100644 index 0000000..510e0a9 --- /dev/null +++ b/src/styles/socialNav.css @@ -0,0 +1,98 @@ +.socialNav { + transition: ease 0.4s; + position: fixed; + bottom: 0; + overflow: hidden; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 0 10px; + right: 50%; + transform: translateX(50%); +} + +.socialNavInside { + width: 100%; + padding: 10px; + border-radius: 15px; + Background: linear-gradient(to top right, rgba(27,27,27,0.9), rgba(35,35,35,0.9), rgba(27,27,27,0.9)); + box-shadow: 0 0 8px rgba(0,0,0,0.2); + backdrop-filter: blur(5px); + display: flex; + flex-direction: row; + grid-gap: 5px; + border: solid 2px rgba(255,255,255,0.05); + overflow-x: auto; + max-width: 80vw; +} + +.socialNavInside::-webkit-scrollbar { + display: none; +} + +.btnMain.socialNavInsideBtn { + transition: ease 0.4s; + padding: 0 15px; + font-size: 24px; + height: 45px; + width: 55px; + border-radius: 10px; + Background: linear-gradient(to top right, rgba(50,50,50,0), rgba(55,55,55,0), rgba(50,50,50,0)); + color: rgba(255,255,255,0.25); +} + +.btnMain.socialNavInsideBtn:hover { + transition: ease 0.4s; + background: #434343; +} + +.btnMain.socialNavInsideBtn.socialNavInsideBtnActive { + Background: #434343; + box-shadow: 0 0 8px 0 rgb(0,0,0,0.1); + color: rgba(255,255,255,0.75); +} + +.socialNavInsideWrapper { + margin: 10px 0 15px 0; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + grid-gap: 5px; +} + +.socialNavCollapse { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + Background: linear-gradient(to top right, rgba(27,27,27,0.75), rgba(35,35,35,0.75), rgba(27,27,27,0.75)); + box-shadow: 0 0 8px rgba(0,0,0,0.2); + padding: 15px 5px; + border-radius: 5px; + border: solid 1px rgba(255,255,255,0.05); + backdrop-filter: blur(5px); + cursor: pointer; + transform: scale(1); + color: rgba(255,255,255,0.75); +} + +.socialNavCollapse:hover { + Background: linear-gradient(rgba(255,255,255,0.01), rgba(255,255,255,0.01)), linear-gradient(to right top, rgba(27,27,27,0.75), rgba(35,35,35,0.75), rgba(27,27,27,0.75)); + color: rgb(255,255,255); +} + +.socialNavCollapseIcon { + transition: ease 0.4s; +} + +.btnMain.socialNavInsideBtn::before { + background: linear-gradient(rgba(255,255,255,0.05), rgba(255,255,255,0.05)), linear-gradient(to top right, #262626, #292929, #262626), linear-gradient(to top right, #262626, #292929, #262626); + opacity: 0; +} + +.btnMain.socialNavInsideBtn:hover::before { + opacity: 1; +} + -- 2.34.1 From 3a71a4a297f529977f348ce7fb86dcc70ae90930 Mon Sep 17 00:00:00 2001 From: freakoverse Date: Mon, 16 Sep 2024 08:43:07 +0000 Subject: [PATCH 07/22] changed default state of social nav --- src/layout/socialNav.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layout/socialNav.tsx b/src/layout/socialNav.tsx index 179d0bf..855fe89 100644 --- a/src/layout/socialNav.tsx +++ b/src/layout/socialNav.tsx @@ -4,7 +4,7 @@ import { appRoutes, getProfilePageRoute } from 'routes' import 'styles/socialNav.css' export const SocialNav = () => { - const [isCollapsed, setIsCollapsed] = useState(true) + const [isCollapsed, setIsCollapsed] = useState(false) const toggleNav = () => { setIsCollapsed(!isCollapsed) -- 2.34.1 From 4175ebc01051903166a19110200a03bdad7bf601 Mon Sep 17 00:00:00 2001 From: freakoverse Date: Tue, 17 Sep 2024 10:50:18 +0000 Subject: [PATCH 08/22] css mobile optimizing slides --- src/styles/SimpleSlider.css | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/styles/SimpleSlider.css b/src/styles/SimpleSlider.css index 38b2ac0..d2e5296 100644 --- a/src/styles/SimpleSlider.css +++ b/src/styles/SimpleSlider.css @@ -102,14 +102,16 @@ @media (max-width: 992px) { .swiper-slide.IBMSMSliderContainerWrapperSlider { grid-template-columns: 1.15fr 0.85fr; - padding: 0 0 25px 0; + padding: 0 5px 25px 5px; grid-gap: 15px; } } @media (max-width: 768px) { .swiper-slide.IBMSMSliderContainerWrapperSlider { - grid-template-columns: 1fr; + display: flex; + flex-direction: column; + grid-gap: 15px; } } @@ -214,6 +216,12 @@ position: relative; } +@media (max-width: 768px) { + .IBMSMSCWSPicWrapper { + height: 300px; + } +} + .IBMSMSCWSInfo { display: flex; flex-direction: column; @@ -234,7 +242,7 @@ @media (max-width: 768px) { .IBMSMSCWSInfo { - /*margin: -25px 10px 0 10px;*/ + height: 100%; } } -- 2.34.1 From 05adb0007204db396d9c9dff2bb9e421473c1a76 Mon Sep 17 00:00:00 2001 From: freakoverse Date: Tue, 17 Sep 2024 11:14:34 +0000 Subject: [PATCH 09/22] mobile css optimizing social nav --- src/styles/socialNav.css | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/styles/socialNav.css b/src/styles/socialNav.css index 510e0a9..2d1d16f 100644 --- a/src/styles/socialNav.css +++ b/src/styles/socialNav.css @@ -12,6 +12,16 @@ transform: translateX(50%); } +@media (max-width: 576px) { + .socialNav { + transition: ease 0.4s; + right: 100%; + transform: translateX(100%); + width: 100%; + align-items: end; + } +} + .socialNavInside { width: 100%; padding: 10px; @@ -27,6 +37,12 @@ max-width: 80vw; } +@media (max-width: 576px) { + .socialNavInside { + max-width: unset; + } +} + .socialNavInside::-webkit-scrollbar { display: none; } @@ -62,6 +78,13 @@ grid-gap: 5px; } +@media (max-width: 576px) { + .socialNavInsideWrapper { + width: 100%; + justify-content: end; + } +} + .socialNavCollapse { display: flex; flex-direction: column; -- 2.34.1 From 6b1d4e73224b056dca2aac3d02450a6e457d9e11 Mon Sep 17 00:00:00 2001 From: freakoverse Date: Tue, 17 Sep 2024 11:45:05 +0000 Subject: [PATCH 10/22] added a div wrapper for mod summary for slider to fix text overflow issue --- src/pages/home.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/home.tsx b/src/pages/home.tsx index 8795283..072b6e9 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -181,10 +181,12 @@ const SlideContent = ({ naddr }: SlideContentProps) => {

{mod.title}

-

- {mod.summary} -
-

+
+

+ {mod.summary} +
+

+

{mod.game}
-- 2.34.1 From d76676c0898017406fa5b2528a55fae61359d79f Mon Sep 17 00:00:00 2001 From: freakoverse Date: Tue, 17 Sep 2024 11:46:24 +0000 Subject: [PATCH 11/22] added a new class --- src/styles/SimpleSlider.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/styles/SimpleSlider.css b/src/styles/SimpleSlider.css index d2e5296..ccabd09 100644 --- a/src/styles/SimpleSlider.css +++ b/src/styles/SimpleSlider.css @@ -264,6 +264,10 @@ font-weight: bold; } +.IBMSMSCWSInfoTextWrapper { + flex-grow: 1; +} + .IBMSMSCWSInfoText { display: -webkit-box; -webkit-box-orient: vertical; @@ -272,7 +276,6 @@ color: rgba(255, 255, 255, 0.5); font-size: 15px; line-height: 1.5; - flex-grow: 1; } @media (max-width: 576px) { -- 2.34.1 From e3f49832f2b44ca4c68378502e2dbdb25b26566f Mon Sep 17 00:00:00 2001 From: freakoverse Date: Tue, 17 Sep 2024 11:52:46 +0000 Subject: [PATCH 12/22] slider css mobile optimization --- src/styles/SimpleSlider.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/styles/SimpleSlider.css b/src/styles/SimpleSlider.css index ccabd09..e6b73ac 100644 --- a/src/styles/SimpleSlider.css +++ b/src/styles/SimpleSlider.css @@ -112,6 +112,7 @@ display: flex; flex-direction: column; grid-gap: 15px; + height: 500px; } } -- 2.34.1 From f708dd6530a27eb7432c33827c7084af416f693b Mon Sep 17 00:00:00 2001 From: freakoverse Date: Tue, 17 Sep 2024 19:35:23 +0000 Subject: [PATCH 13/22] css mobile optimizing --- src/styles/author.css | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/styles/author.css b/src/styles/author.css index 98a5b11..9f1743c 100644 --- a/src/styles/author.css +++ b/src/styles/author.css @@ -149,6 +149,7 @@ white-space: nowrap; text-overflow: ellipsis; color: rgba(255,255,255,0.75); + max-width: 223px; } .IBMSMSMSSS_Author_Top_PPWrapper { @@ -160,6 +161,15 @@ .IBMSMSMSSS_Author_Top_Handle { color: rgba(255,255,255,0.5); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + max-width: 223px; +} + +.IBMSMSMSSS_Author_Top_Handle.IBMSMSMSSS_Author_Top_HandleNomen { + color: #F7931A; + font-weight: bold; } .IBMSMSMSSS_Author_Top_NostrLinksLink.IBMSMSMSSS_A_T_NLL_IBMSMSMSSSFollow { -- 2.34.1 From aa8d18ea531a81d27f3779da89e65d40c08d219a Mon Sep 17 00:00:00 2001 From: freakoverse Date: Tue, 17 Sep 2024 19:36:02 +0000 Subject: [PATCH 14/22] Update src/styles/innerPage.css --- src/styles/innerPage.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/innerPage.css b/src/styles/innerPage.css index 7bc1b4c..9daf2e3 100644 --- a/src/styles/innerPage.css +++ b/src/styles/innerPage.css @@ -4,7 +4,7 @@ grid-gap: 25px; } -@media (max-width: 992px) { +@media (max-width: 1200px) { .IBMSMSplitMain { display: flex; flex-direction: column; -- 2.34.1 From 49ed027b5c7230640e67db54efa5ec11cacdc240 Mon Sep 17 00:00:00 2001 From: freakoverse Date: Tue, 17 Sep 2024 19:45:26 +0000 Subject: [PATCH 15/22] Update src/styles/author.css --- src/styles/author.css | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/styles/author.css b/src/styles/author.css index 9f1743c..97e8a30 100644 --- a/src/styles/author.css +++ b/src/styles/author.css @@ -145,11 +145,11 @@ } .IBMSMSMSSS_Author_Top_Name { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; color: rgba(255,255,255,0.75); - max-width: 223px; + display: -webkit-box; + -webkit-box-orient: vertical; + overflow: hidden; + -webkit-line-clamp: 1; } .IBMSMSMSSS_Author_Top_PPWrapper { @@ -161,10 +161,10 @@ .IBMSMSMSSS_Author_Top_Handle { color: rgba(255,255,255,0.5); + display: -webkit-box; + -webkit-box-orient: vertical; overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - max-width: 223px; + -webkit-line-clamp: 1; } .IBMSMSMSSS_Author_Top_Handle.IBMSMSMSSS_Author_Top_HandleNomen { -- 2.34.1 From 5b641ff4cc6cb0a24454f7c54a8cec90ff4db66f Mon Sep 17 00:00:00 2001 From: daniyal Date: Wed, 18 Sep 2024 08:16:28 +0500 Subject: [PATCH 16/22] chore: add error boundary component --- src/components/ErrorBoundary.tsx | 48 ++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/components/ErrorBoundary.tsx diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx new file mode 100644 index 0000000..cf8cf88 --- /dev/null +++ b/src/components/ErrorBoundary.tsx @@ -0,0 +1,48 @@ +import { Component, ErrorInfo, ReactNode } from 'react' + +// Define the state interface for error boundary +interface ErrorBoundaryState { + hasError: boolean +} + +// Define the props interface (if you want to pass any props) +interface ErrorBoundaryProps { + children: ReactNode +} + +export class ErrorBoundary extends Component< + ErrorBoundaryProps, + ErrorBoundaryState +> { + constructor(props: ErrorBoundaryProps) { + super(props) + this.state = { hasError: false } + } + + // Update state so the next render will show the fallback UI. + static getDerivedStateFromError(_: Error): ErrorBoundaryState { + return { hasError: true } + } + + // Log the error and error info (optional) + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.error('Uncaught error:', error, errorInfo) + // You could also send the error to a logging service here + console.error('props', this.props) + } + + render() { + if (this.state.hasError) { + // You can render any fallback UI here + return ( +

+

Oops! Something went wrong.

+

Please check console.

+
+ ) + } + + // If no error, render children + return this.props.children + } +} -- 2.34.1 From 06f0282cad471e8c893054fd3a0966a2704950a9 Mon Sep 17 00:00:00 2001 From: daniyal Date: Wed, 18 Sep 2024 08:17:07 +0500 Subject: [PATCH 17/22] chore: memoize modCard --- src/components/ModCard.tsx | 147 ++++++++++++++++++------------------- 1 file changed, 72 insertions(+), 75 deletions(-) diff --git a/src/components/ModCard.tsx b/src/components/ModCard.tsx index 0ceb5d5..ca653fb 100644 --- a/src/components/ModCard.tsx +++ b/src/components/ModCard.tsx @@ -1,3 +1,4 @@ +import React from 'react' import { Link } from 'react-router-dom' import '../styles/cardMod.css' import { handleModImageError } from '../utils' @@ -10,83 +11,79 @@ type ModCardProps = { route: string } -export const ModCard = ({ - title, - gameName, - summary, - imageUrl, - route -}: ModCardProps) => { - return ( - -
-
- -
-
-

{title}

-

{summary}

-
-

{gameName}

+export const ModCard = React.memo( + ({ title, gameName, summary, imageUrl, route }: ModCardProps) => { + return ( + +
+
+
-
-
-
-
- - - -

420

+
+

{title}

+

{summary}

+
+

{gameName}

-
- - - -

420

-
-
- - - -

420

-
-
- - - -

420

+
+
+
+
+ + + +

420

+
+
+ + + +

420

+
+
+ + + +

420

+
+
+ + + +

420

+
-
- - ) -} + + ) + } +) -- 2.34.1 From 9730fec14f8e7974cfa61bd9c843e0ecada6aab2 Mon Sep 17 00:00:00 2001 From: daniyal Date: Wed, 18 Sep 2024 08:18:03 +0500 Subject: [PATCH 18/22] chore: move pagination for separate component file --- src/components/Pagination.tsx | 40 +++++++++++++++++++++++++++++++++++ src/pages/mods.tsx | 40 +---------------------------------- 2 files changed, 41 insertions(+), 39 deletions(-) create mode 100644 src/components/Pagination.tsx diff --git a/src/components/Pagination.tsx b/src/components/Pagination.tsx new file mode 100644 index 0000000..5e6e878 --- /dev/null +++ b/src/components/Pagination.tsx @@ -0,0 +1,40 @@ +import React from 'react' + +type PaginationProps = { + page: number + disabledNext: boolean + handlePrev: () => void + handleNext: () => void +} + +export const Pagination = React.memo( + ({ page, disabledNext, handlePrev, handleNext }: PaginationProps) => { + return ( +
+
+
+ +
+ +
+ +
+
+
+ ) + } +) diff --git a/src/pages/mods.tsx b/src/pages/mods.tsx index 55c609b..e6a5326 100644 --- a/src/pages/mods.tsx +++ b/src/pages/mods.tsx @@ -19,6 +19,7 @@ import '../styles/styles.css' import { ModDetails, MuteLists } from '../types' import { fetchMods } from '../utils' import { MOD_FILTER_LIMIT } from '../constants' +import { Pagination } from 'components/Pagination' enum SortBy { Latest = 'Latest', @@ -421,42 +422,3 @@ const Filters = React.memo( ) } ) - -type PaginationProps = { - page: number - disabledNext: boolean - handlePrev: () => void - handleNext: () => void -} - -const Pagination = React.memo( - ({ page, disabledNext, handlePrev, handleNext }: PaginationProps) => { - return ( -
-
-
- -
- -
- -
-
-
- ) - } -) -- 2.34.1 From a90e932ed6569c86bac32db3cb0735a8bd1691e2 Mon Sep 17 00:00:00 2001 From: daniyal Date: Wed, 18 Sep 2024 08:19:08 +0500 Subject: [PATCH 19/22] chore: refactore profile section component --- src/components/ProfileSection.tsx | 210 ++++++++++++++++-------------- 1 file changed, 115 insertions(+), 95 deletions(-) diff --git a/src/components/ProfileSection.tsx b/src/components/ProfileSection.tsx index b441582..c2ab74a 100644 --- a/src/components/ProfileSection.tsx +++ b/src/components/ProfileSection.tsx @@ -14,9 +14,11 @@ import '../styles/author.css' import '../styles/innerPage.css' import '../styles/socialPosts.css' import { UserProfile } from '../types' -import { copyTextToClipboard, log, LogType, now } from '../utils' +import { copyTextToClipboard, log, LogType, now, npubToHex } from '../utils' import { LoadingSpinner } from './LoadingSpinner' import { ZapPopUp } from './Zap' +import { NDKUserProfile } from '@nostr-dev-kit/ndk' +import _ from 'lodash' type Props = { pubkey: string @@ -32,106 +34,13 @@ export const ProfileSection = ({ pubkey }: Props) => { }) }) - const handleCopy = async () => { - copyTextToClipboard(profile?.npub as string).then((isCopied) => { - if (isCopied) { - toast.success('Npub copied to clipboard!') - } else { - toast.error( - 'Failed to copy, look into console for more details on error!' - ) - } - }) - } - if (!profile) return null - const profileRoute = getProfilePageRoute( - nip19.nprofileEncode({ - pubkey - }) - ) - return (
-
-
-
- -
-
-
-
-
-
-
-
-

- {profile.displayName || profile.name || ''} -

-

- {profile.nip05 || ''} -

-
-
-
- -
-
-

- {profile.npub} -

-
-
-
- - - -
- - -
-
-
-
-

- {profile.bio || profile.about} -

-
-
-
- -
+
@@ -181,6 +90,117 @@ export const ProfileSection = ({ pubkey }: Props) => { ) } +type ProfileProps = { + profile: NDKUserProfile +} + +export const Profile = ({ profile }: ProfileProps) => { + const handleCopy = async () => { + copyTextToClipboard(profile.npub as string).then((isCopied) => { + if (isCopied) { + toast.success('Npub copied to clipboard!') + } else { + toast.error( + 'Failed to copy, look into console for more details on error!' + ) + } + }) + } + + const hexPubkey = npubToHex(profile.pubkey as string) + + if (!hexPubkey) return null + + const profileRoute = getProfilePageRoute( + nip19.nprofileEncode({ + pubkey: hexPubkey + }) + ) + + const npub = (profile.npub as string) || '' + const displayName = + profile.displayName || + profile.name || + _.truncate(npub, { + length: 16 + }) + const nip05 = profile.nip05 || '' + const about = profile.bio || profile.about || '' + + return ( +
+
+
+ +
+
+
+
+
+
+
+
+

{displayName}

+

{nip05}

+
+
+
+ +
+
+

+ {npub} +

+
+
+
+ + + +
+ + +
+
+
+
+

{about}

+
+
+
+ +
+ ) +} + interface Post { name: string link: string -- 2.34.1 From 9e8aa162973ef993cd41c6c5bcfecef452fec925 Mon Sep 17 00:00:00 2001 From: daniyal Date: Wed, 18 Sep 2024 08:19:42 +0500 Subject: [PATCH 20/22] chore: fix relay controller --- src/controllers/relay.ts | 42 ++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/src/controllers/relay.ts b/src/controllers/relay.ts index 0b1413f..0811cd6 100644 --- a/src/controllers/relay.ts +++ b/src/controllers/relay.ts @@ -368,24 +368,46 @@ export class RelayController { */ fetchEvents = async ( filter: Filter, - relays: string[] = [] + relayUrls: string[] = [] ): Promise => { - // add app relay to relays array - relays.push(import.meta.env.VITE_APP_RELAY) + const relaySet = new Set() + + // add all the relays passed to relay set + relayUrls.forEach((relayUrl) => { + relaySet.add(relayUrl) + }) + + relaySet.add(import.meta.env.VITE_APP_RELAY) const metadataController = await MetadataController.getInstance() // add admin relays to relays array - metadataController.adminRelays.forEach((url) => { - relays.push(url) + metadataController.adminRelays.forEach((relayUrl) => { + relaySet.add(relayUrl) }) + relayUrls = Array.from(relaySet) + // Connect to all specified relays - const relayPromises = relays.map((relayUrl) => this.connectRelay(relayUrl)) - await Promise.allSettled(relayPromises) + const relayPromises = relayUrls.map((relayUrl) => + this.connectRelay(relayUrl) + ) + + // Use Promise.allSettled to wait for all promises to settle + const results = await Promise.allSettled(relayPromises) + + // Extract non-null values from fulfilled promises in a single pass + const relays = results.reduce((acc, result) => { + if (result.status === 'fulfilled') { + const value = result.value + if (value) { + acc.push(value) + } + } + return acc + }, []) // Check if any relays are connected - if (this.connectedRelays.length === 0) { - log(this.debug, LogType.Error, 'No relay is connected to fetch events!') + if (relays.length === 0) { throw new Error('No relay is connected to fetch events!') } @@ -393,7 +415,7 @@ export class RelayController { const eventIds = new Set() // To keep track of event IDs and avoid duplicates // Create a promise for each relay subscription - const subPromises = this.connectedRelays.map((relay) => { + const subPromises = relays.map((relay) => { return new Promise((resolve) => { // Subscribe to the relay with the specified filter const sub = relay.subscribe([filter], { -- 2.34.1 From 4dc65b92f708ed64b2b4faeec3bb100fcdb6c833 Mon Sep 17 00:00:00 2001 From: daniyal Date: Wed, 18 Sep 2024 08:20:32 +0500 Subject: [PATCH 21/22] feat: implemented search page --- src/pages/search.tsx | 653 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 651 insertions(+), 2 deletions(-) diff --git a/src/pages/search.tsx b/src/pages/search.tsx index ea66cd8..ad1ada3 100644 --- a/src/pages/search.tsx +++ b/src/pages/search.tsx @@ -1,3 +1,652 @@ -export const SearchPage = () => { - return

WIP

+import { NDKEvent, NDKUserProfile, profileFromEvent } from '@nostr-dev-kit/ndk' +import { ErrorBoundary } from 'components/ErrorBoundary' +import { GameCard } from 'components/GameCard' +import { LoadingSpinner } from 'components/LoadingSpinner' +import { ModCard } from 'components/ModCard' +import { Pagination } from 'components/Pagination' +import { Profile } from 'components/ProfileSection' +import { T_TAG_VALUE } from 'constants.ts' +import { MetadataController, RelayController } from 'controllers' +import { useAppSelector, useDidMount } from 'hooks' +import { Filter, kinds, nip19 } from 'nostr-tools' +import { Subscription } from 'nostr-tools/abstract-relay' +import Papa from 'papaparse' +import React, { + Dispatch, + SetStateAction, + useEffect, + useMemo, + useRef, + useState +} from 'react' +import { toast } from 'react-toastify' +import { getModPageRoute } from 'routes' +import { ModDetails, MuteLists } from 'types' +import { extractModData, isModDataComplete, log, LogType } from 'utils' + +enum SortByEnum { + Latest = 'Latest', + Oldest = 'Oldest', + Best_Rated = 'Best Rated', + Worst_Rated = 'Worst Rated' +} + +enum ModeratedFilterEnum { + Moderated = 'Moderated', + Unmoderated = 'Unmoderated', + Unmoderated_Fully = 'Unmoderated Fully' +} + +enum SearchingFilterEnum { + Mods = 'Mods', + Games = 'Games', + Users = 'Users' +} + +interface FilterOptions { + sort: SortByEnum + moderated: ModeratedFilterEnum + searching: SearchingFilterEnum +} + +export const SearchPage = () => { + const searchTermRef = useRef(null) + const [filterOptions, setFilterOptions] = useState({ + sort: SortByEnum.Latest, + moderated: ModeratedFilterEnum.Moderated, + searching: SearchingFilterEnum.Mods + }) + const [searchTerm, setSearchTerm] = useState('') + const [muteLists, setMuteLists] = useState<{ + admin: MuteLists + user: MuteLists + }>({ + admin: { + authors: [], + replaceableEvents: [] + }, + user: { + authors: [], + replaceableEvents: [] + } + }) + + const userState = useAppSelector((state) => state.user) + + useDidMount(async () => { + const pubkey = userState.user?.pubkey as string | undefined + + const metadataController = await MetadataController.getInstance() + metadataController.getMuteLists(pubkey).then((lists) => { + setMuteLists(lists) + }) + }) + + const handleSearch = () => { + const value = searchTermRef.current?.value || '' // Access the input value from the ref + setSearchTerm(value) + } + + // Handle "Enter" key press inside the input + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + handleSearch() + } + } + + return ( +
+
+
+
+
+
+

+ Search:  + + {searchTerm} + +

+
+
+
+
+ + +
+
+
+
+
+ + {filterOptions.searching === SearchingFilterEnum.Mods && ( + + )} + {filterOptions.searching === SearchingFilterEnum.Users && ( + + )} + {filterOptions.searching === SearchingFilterEnum.Games && ( + + )} +
+
+
+ ) +} + +type FiltersProps = { + filterOptions: FilterOptions + setFilterOptions: Dispatch> +} + +const Filters = React.memo( + ({ filterOptions, setFilterOptions }: FiltersProps) => { + const userState = useAppSelector((state) => state.user) + + return ( +
+
+
+
+ + +
+ {Object.values(SortByEnum).map((item, index) => ( +
+ setFilterOptions((prev) => ({ + ...prev, + sort: item + })) + } + > + {item} +
+ ))} +
+
+
+
+
+ +
+ {Object.values(ModeratedFilterEnum).map((item, index) => { + if (item === ModeratedFilterEnum.Unmoderated_Fully) { + const isAdmin = + userState.user?.npub === + import.meta.env.VITE_REPORTING_NPUB + + if (!isAdmin) return null + } + + return ( +
+ setFilterOptions((prev) => ({ + ...prev, + moderated: item + })) + } + > + {item} +
+ ) + })} +
+
+
+
+
+ +
+ {Object.values(SearchingFilterEnum).map((item, index) => ( +
+ setFilterOptions((prev) => ({ + ...prev, + searching: item + })) + } + > + {item} +
+ ))} +
+
+
+
+
+ ) + } +) + +const MAX_MODS_PER_PAGE = 10 + +type ModsResultProps = { + filterOptions: FilterOptions + searchTerm: string + muteLists: { + admin: MuteLists + user: MuteLists + } +} + +const ModsResult = ({ + filterOptions, + searchTerm, + muteLists +}: ModsResultProps) => { + const hasEffectRun = useRef(false) + const [isSubscribing, setIsSubscribing] = useState(false) + const [mods, setMods] = useState([]) + const [page, setPage] = useState(1) + const userState = useAppSelector((state) => state.user) + + useEffect(() => { + if (hasEffectRun.current) { + return + } + + hasEffectRun.current = true // Set it so the effect doesn't run again + + const filter: Filter = { + kinds: [kinds.ClassifiedListing], + '#t': [T_TAG_VALUE] + } + + setIsSubscribing(true) + + let subscriptions: Subscription[] = [] + + RelayController.getInstance() + .subscribeForEvents(filter, [], (event) => { + if (isModDataComplete(event)) { + const mod = extractModData(event) + setMods((prev) => [...prev, mod]) + } + }) + .then((subs) => { + subscriptions = subs + }) + .catch((err) => { + log( + true, + LogType.Error, + 'An error occurred in subscribing to relays.', + err + ) + toast.error(err.message || err) + }) + .finally(() => { + setIsSubscribing(false) + }) + + // Cleanup function to stop all subscriptions + return () => { + subscriptions.forEach((sub) => sub.close()) // close each subscription + } + }, []) + + useEffect(() => { + setPage(1) + }, [searchTerm]) + + const filteredMods = useMemo(() => { + if (searchTerm === '') return [] + + const lowerCaseSearchTerm = searchTerm.toLowerCase() + + const filterFn = (mod: ModDetails) => + mod.title.toLowerCase().includes(lowerCaseSearchTerm) || + mod.game.toLowerCase().includes(lowerCaseSearchTerm) || + mod.summary.toLowerCase().includes(lowerCaseSearchTerm) || + mod.body.toLowerCase().includes(lowerCaseSearchTerm) || + mod.tags.findIndex((tag) => + tag.toLowerCase().includes(lowerCaseSearchTerm) + ) > -1 + + return mods.filter(filterFn) + }, [mods, searchTerm]) + + const filteredModList = useMemo(() => { + let filtered: ModDetails[] = [...filteredMods] + const isAdmin = userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB + const isUnmoderatedFully = + filterOptions.moderated === ModeratedFilterEnum.Unmoderated_Fully + + // Only apply filtering if the user is not an admin or the admin has not selected "Unmoderated Fully" + if (!(isAdmin && isUnmoderatedFully)) { + filtered = filtered.filter( + (mod) => + !muteLists.admin.authors.includes(mod.author) && + !muteLists.admin.replaceableEvents.includes(mod.aTag) + ) + } + + if (filterOptions.moderated === ModeratedFilterEnum.Moderated) { + filtered = filtered.filter( + (mod) => + !muteLists.user.authors.includes(mod.author) && + !muteLists.user.replaceableEvents.includes(mod.aTag) + ) + } + + if (filterOptions.sort === SortByEnum.Latest) { + filtered.sort((a, b) => b.published_at - a.published_at) + } else if (filterOptions.sort === SortByEnum.Oldest) { + filtered.sort((a, b) => a.published_at - b.published_at) + } + + return filtered + }, [ + filteredMods, + userState.user?.npub, + filterOptions.sort, + filterOptions.moderated, + muteLists + ]) + + const handleNext = () => { + setPage((prev) => prev + 1) + } + + const handlePrev = () => { + setPage((prev) => prev - 1) + } + + return ( + <> + {isSubscribing && ( + + )} +
+
+ {filteredModList + .slice((page - 1) * MAX_MODS_PER_PAGE, page * MAX_MODS_PER_PAGE) + .map((mod) => { + const route = getModPageRoute( + nip19.naddrEncode({ + identifier: mod.aTag, + pubkey: mod.author, + kind: kinds.ClassifiedListing + }) + ) + + return ( + + ) + })} +
+
+ + + ) +} + +type UsersResultProps = { + searchTerm: string + moderationFilter: ModeratedFilterEnum + muteLists: { + admin: MuteLists + user: MuteLists + } +} + +const UsersResult = ({ + searchTerm, + moderationFilter, + muteLists +}: UsersResultProps) => { + const [isFetching, setIsFetching] = useState(false) + const [profiles, setProfiles] = useState([]) + + const userState = useAppSelector((state) => state.user) + + useEffect(() => { + if (searchTerm === '') { + setProfiles([]) + } else { + const filter: Filter = { + kinds: [kinds.Metadata], + search: searchTerm + } + + setIsFetching(true) + RelayController.getInstance() + .fetchEvents(filter, ['wss://purplepag.es', 'wss://user.kindpag.es']) + .then((events) => { + const results = events.map((event) => { + const ndkEvent = new NDKEvent(undefined, event) + const profile = profileFromEvent(ndkEvent) + return profile + }) + setProfiles(results) + }) + .catch((err) => { + log(true, LogType.Error, 'An error occurred in fetching users', err) + }) + .finally(() => { + setIsFetching(false) + }) + } + }, [searchTerm]) + + const filteredProfiles = useMemo(() => { + let filtered = [...profiles] + const isAdmin = userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB + const isUnmoderatedFully = + moderationFilter === ModeratedFilterEnum.Unmoderated_Fully + + // Only apply filtering if the user is not an admin or the admin has not selected "Unmoderated Fully" + if (!(isAdmin && isUnmoderatedFully)) { + filtered = filtered.filter( + (profile) => !muteLists.admin.authors.includes(profile.pubkey as string) + ) + } + + if (moderationFilter === ModeratedFilterEnum.Moderated) { + filtered = filtered.filter( + (profile) => !muteLists.user.authors.includes(profile.pubkey as string) + ) + } + + return filtered + }, [userState.user?.npub, moderationFilter, profiles, muteLists]) + return ( + <> + {isFetching && } +
+
+ {filteredProfiles.map((profile) => { + if (profile.pubkey) { + return ( + + + + ) + } + + return null + })} +
+
+ + ) +} + +type Game = { + 'Game Name': string + '16 by 9 image': string + 'Boxart image': string +} + +const MAX_GAMES_PER_PAGE = 10 + +type GamesResultProps = { + searchTerm: string +} + +const GamesResult = ({ searchTerm }: GamesResultProps) => { + const hasProcessedCSV = useRef(false) + const [isProcessingCSVFile, setIsProcessingCSVFile] = useState(false) + const [games, setGames] = useState([]) + const [page, setPage] = useState(1) + + useEffect(() => { + if (hasProcessedCSV.current) return + hasProcessedCSV.current = true + + setIsProcessingCSVFile(true) + + // Fetch the CSV file from the public folder + fetch('/assets/games.csv') + .then((response) => response.text()) + .then((csvText) => { + // Parse the CSV text using PapaParse + Papa.parse(csvText, { + worker: true, + header: true, + complete: (results) => { + const uniqueGames: Game[] = [] + const gameNames = new Set() + + // Remove duplicate games based on 'Game Name' + results.data.forEach((game) => { + if (!gameNames.has(game['Game Name'])) { + gameNames.add(game['Game Name']) + uniqueGames.push(game) + } + }) + + // Set the unique games list + setGames(uniqueGames) + } + }) + }) + .catch((err) => { + log(true, LogType.Error, 'Error occurred in processing csv file', err) + toast.error(err.message || err) + }) + .finally(() => { + setIsProcessingCSVFile(false) + }) + }, []) + + // Reset the page to 1 whenever searchTerm changes + useEffect(() => { + setPage(1) + }, [searchTerm]) + + const filteredGames = useMemo(() => { + if (searchTerm === '') return [] + + const lowerCaseSearchTerm = searchTerm.toLowerCase() + + return games.filter((game) => + game['Game Name'].toLowerCase().includes(lowerCaseSearchTerm) + ) + }, [searchTerm, games]) + + const handleNext = () => { + setPage((prev) => prev + 1) + } + + const handlePrev = () => { + setPage((prev) => prev - 1) + } + + return ( + <> + {isProcessingCSVFile && } +
+
+ {filteredGames + .slice((page - 1) * MAX_GAMES_PER_PAGE, page * MAX_GAMES_PER_PAGE) + .map((game) => ( + + ))} +
+
+ + + ) } -- 2.34.1 From b9d5bb82113718d6a2560271125dee2921c914cf Mon Sep 17 00:00:00 2001 From: freakoverse Date: Wed, 18 Sep 2024 08:03:52 +0000 Subject: [PATCH 22/22] Update src/styles/cardMod.css --- src/styles/cardMod.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/cardMod.css b/src/styles/cardMod.css index 49702e1..3028908 100644 --- a/src/styles/cardMod.css +++ b/src/styles/cardMod.css @@ -106,7 +106,7 @@ display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; - -webkit-line-clamp: 3; + -webkit-line-clamp: 2; color: rgba(255, 255, 255, 0.5); font-size: 15px; line-height: 1.5; -- 2.34.1