From 0102f414039dd4ce8e5d7c8cdc6b055a7edc9425 Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 23 Oct 2024 17:13:29 +0200 Subject: [PATCH 01/10] chore(prettier): css format --- src/styles/profile.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/styles/profile.css b/src/styles/profile.css index c72d15a..556b066 100644 --- a/src/styles/profile.css +++ b/src/styles/profile.css @@ -22,6 +22,5 @@ flex-wrap: wrap; grid-gap: 10px; padding: 10px 0 0 0; - border-top: solid 1px rgba(255,255,255,0.1); + border-top: solid 1px rgba(255, 255, 255, 0.1); } - From a97a03417828aa54b938c4743c5bccbd18cfbf56 Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 23 Oct 2024 17:19:05 +0200 Subject: [PATCH 02/10] feat: add Tabs component --- src/components/Tabs.tsx | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/components/Tabs.tsx diff --git a/src/components/Tabs.tsx b/src/components/Tabs.tsx new file mode 100644 index 0000000..ebe96a5 --- /dev/null +++ b/src/components/Tabs.tsx @@ -0,0 +1,26 @@ +interface TabsProps { + tabs: string[] + tab: number + setTab: React.Dispatch> +} + +export const Tabs = ({ tabs, tab, setTab }: TabsProps) => { + return ( +
+ {tabs.map((t, i) => { + return ( + + ) + })} +
+ ) +} From 88106734925ebcc5821ee5470e9b6ad5008f75fa Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 23 Oct 2024 17:23:48 +0200 Subject: [PATCH 03/10] refactor: extend checkbox field input --- src/components/Inputs.tsx | 20 +++++++++++++++++--- src/components/ModForm.tsx | 1 + src/pages/write.tsx | 1 + 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/components/Inputs.tsx b/src/components/Inputs.tsx index 5a30009..e40cc91 100644 --- a/src/components/Inputs.tsx +++ b/src/components/Inputs.tsx @@ -89,13 +89,27 @@ interface CheckboxFieldProps { name: string isChecked: boolean handleChange: (e: React.ChangeEvent) => void + type?: 'default' | 'stylized' } export const CheckboxField = React.memo( - ({ label, name, isChecked, handleChange }: CheckboxFieldProps) => ( -
- + ({ + label, + name, + isChecked, + handleChange, + type = 'default' + }: CheckboxFieldProps) => ( +
+ { name='nsfw' isChecked={formState.nsfw} handleChange={handleCheckboxChange} + type='stylized' />
diff --git a/src/pages/write.tsx b/src/pages/write.tsx index 230edd7..58813a5 100644 --- a/src/pages/write.tsx +++ b/src/pages/write.tsx @@ -55,6 +55,7 @@ export const WritePage = () => { name='nsfw' isChecked={false} handleChange={() => {}} + type='stylized' />
+ +
+
+ + + + {/* Tabs Content */} + {tab === 0 && ( + <> + + +
+ {filteredModList.map((mod) => ( + + ))} +
+ + + + )} + + {tab === 1 && <>USER's BLOGS WIP} + {tab === 2 && <>USER's POSTS WIP} +
+
+ + + + + {showReportPopUp && ( + setShowReportPopUp(false)} + /> + )} + + + ) +} + +type ReportUserPopupProps = { + reportedPubkey: string + handleClose: () => void +} + +const USER_REPORT_REASONS = [ + { label: `User posts actual CP`, key: 'user_actuallyCP' }, + { label: `User is a spammer`, key: 'user_spam' }, + { label: `User is a scammer`, key: 'user_scam' }, + { label: `User posts malware`, key: 'user_malware' }, + { label: `User posts non-mods`, key: 'user_notAGameMod' }, + { label: `User doesn't tag NSFW`, key: 'user_wasntTaggedNSFW' }, + { label: `Other (user)`, key: 'user_otherReason' } +] + +const ReportUserPopup = ({ + reportedPubkey, + handleClose +}: ReportUserPopupProps) => { + const { ndk, fetchEventFromUserRelays, publish } = useNDKContext() + const userState = useAppSelector((state) => state.user) + const [selectedOptions, setSelectedOptions] = useState( + USER_REPORT_REASONS.reduce((acc: { [key: string]: boolean }, cur) => { + acc[cur.key] = false + return acc + }, {}) + ) + const [isLoading, setIsLoading] = useState(false) + const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('') + + const handleCheckboxChange = (option: keyof typeof selectedOptions) => { + setSelectedOptions((prevState) => ({ + ...prevState, + [option]: !prevState[option] + })) + } + + const handleSubmit = async () => { + const selectedOptionsCount = Object.values(selectedOptions).filter( + (isSelected) => isSelected + ).length + + if (selectedOptionsCount === 0) { + toast.error('At least one option should be checked!') + return + } + + setIsLoading(true) + setLoadingSpinnerDesc('Getting user pubkey') + let userHexKey: string + if (userState.auth && userState.user?.pubkey) { + userHexKey = userState.user.pubkey as string + } else { + userHexKey = (await window.nostr?.getPublicKey()) as string + } + + if (!userHexKey) { + toast.error('Could not get pubkey for reporting user!') + setIsLoading(false) + return + } + + const reportingNpub = import.meta.env.VITE_REPORTING_NPUB + const reportingPubkey = npubToHex(reportingNpub) + + if (reportingPubkey === userHexKey) { + setLoadingSpinnerDesc(`Finding user's mute list`) + // Define the event filter to search for the user's mute list events. + // We look for events of a specific kind (Mutelist) authored by the given hexPubkey. + const filter: NDKFilter = { + kinds: [NDKKind.MuteList], + authors: [userHexKey] + } + + // Fetch the mute list event from the relays. This returns the event containing the user's mute list. + const muteListEvent = await fetchEventFromUserRelays( + filter, + userHexKey, + UserRelaysType.Write + ) + + let unsignedEvent: UnsignedEvent + + if (muteListEvent) { + // get a list of tags + const tags = muteListEvent.tags + const alreadyExists = + tags.findIndex( + (item) => item[0] === 'p' && item[1] === reportedPubkey + ) !== -1 + + if (alreadyExists) { + setIsLoading(false) + return toast.warn( + `Reporter user's pubkey is already in the mute list` + ) + } + + tags.push(['p', reportedPubkey]) + + unsignedEvent = { + pubkey: muteListEvent.pubkey, + kind: NDKKind.MuteList, + content: muteListEvent.content, + created_at: now(), + tags: [...tags] + } + } else { + unsignedEvent = { + pubkey: userHexKey, + kind: NDKKind.MuteList, + content: '', + created_at: now(), + tags: [['p', reportedPubkey]] + } + } + + setLoadingSpinnerDesc('Updating mute list event') + const isUpdated = await signAndPublish(unsignedEvent, ndk, publish) + if (isUpdated) handleClose() + } else { + const href = window.location.href + let message = `I'd like to report ${href} due to following reasons:\n` + + Object.entries(selectedOptions).forEach(([key, value]) => { + if (value) { + message += `* ${key}\n` + } + }) + + setLoadingSpinnerDesc('Sending report') + const isSent = await sendDMUsingRandomKey( + message, + reportingPubkey!, + ndk, + publish + ) + if (isSent) handleClose() + } + setIsLoading(false) + } + + return ( + <> + {isLoading && } +
+
+
+
+
+
+

Report Post

+
+
+ + + +
+
+
+
+
+ + {USER_REPORT_REASONS.map((r) => ( + handleCheckboxChange(r.key)} + /> + ))} + +
+
+
+
+
+
+
+ + ) } diff --git a/src/types/modsFilter.ts b/src/types/modsFilter.ts index 8d724ec..a10542d 100644 --- a/src/types/modsFilter.ts +++ b/src/types/modsFilter.ts @@ -22,4 +22,5 @@ export interface FilterOptions { nsfw: NSFWFilter source: string moderated: ModeratedFilter + author?: string } From bb653fa356fce6667acf112217c6a46e9c951178 Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 23 Oct 2024 17:52:53 +0200 Subject: [PATCH 06/10] fix: add more pages --- src/layout/feed.tsx | 10 ++++++++++ src/pages/404.tsx | 30 ++++++++++++++++++++++++++++++ src/pages/feed.tsx | 3 +++ src/pages/notifications.tsx | 3 +++ 4 files changed, 46 insertions(+) create mode 100644 src/layout/feed.tsx create mode 100644 src/pages/404.tsx create mode 100644 src/pages/feed.tsx create mode 100644 src/pages/notifications.tsx diff --git a/src/layout/feed.tsx b/src/layout/feed.tsx new file mode 100644 index 0000000..2566f31 --- /dev/null +++ b/src/layout/feed.tsx @@ -0,0 +1,10 @@ +import { Outlet } from 'react-router-dom' + +export const FeedLayout = () => { + return ( + <> +

WIP

+ + + ) +} diff --git a/src/pages/404.tsx b/src/pages/404.tsx new file mode 100644 index 0000000..1c53760 --- /dev/null +++ b/src/pages/404.tsx @@ -0,0 +1,30 @@ +import { Link } from 'react-router-dom' +import { appRoutes } from 'routes' + +export const NotFoundPage = () => { + return ( +
+
+
+
+
+

Page not found

+
+
+

The page you're attempting to visit doesn't exist

+
+
+ + Go home + +
+
+
+
+
+ ) +} diff --git a/src/pages/feed.tsx b/src/pages/feed.tsx new file mode 100644 index 0000000..bcfc305 --- /dev/null +++ b/src/pages/feed.tsx @@ -0,0 +1,3 @@ +export const FeedPage = () => { + return

Feed

+} diff --git a/src/pages/notifications.tsx b/src/pages/notifications.tsx new file mode 100644 index 0000000..5681bdd --- /dev/null +++ b/src/pages/notifications.tsx @@ -0,0 +1,3 @@ +export const NotificationsPage = () => { + return

Notifications

+} From 7393940027c00c9ce170b5808b28c05f903b3b72 Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 23 Oct 2024 17:54:33 +0200 Subject: [PATCH 07/10] feat: update routes --- src/routes/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 668194b..991105e 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -13,7 +13,7 @@ import { GamePage } from 'pages/game' export const appRoutes = { index: '/', - home: '/home', + home: '/', games: '/games', game: '/game/:name', mods: '/mods', @@ -28,7 +28,7 @@ export const appRoutes = { settingsRelays: '/settings-relays', settingsPreferences: '/settings-preferences', settingsAdmin: '/settings-admin', - profile: '/profile/:nprofile' + profile: '/profile/:nprofile?' } export const getGamePageRoute = (name: string) => From 76478ad57257978237e72962c83e152f463213fb Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 23 Oct 2024 17:58:41 +0200 Subject: [PATCH 08/10] fix: quick nav buttons and active state --- src/layout/socialNav.tsx | 58 ++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/src/layout/socialNav.tsx b/src/layout/socialNav.tsx index 022f555..e38bd88 100644 --- a/src/layout/socialNav.tsx +++ b/src/layout/socialNav.tsx @@ -1,24 +1,17 @@ +import { useAppSelector } from 'hooks' import { useState } from 'react' -import { Link, useLocation } from 'react-router-dom' +import { NavLink, NavLinkProps } from 'react-router-dom' import { appRoutes, getProfilePageRoute } from 'routes' import 'styles/socialNav.css' export const SocialNav = () => { - const location = useLocation() - const currentPath = location.pathname const [isCollapsed, setIsCollapsed] = useState(false) + const userState = useAppSelector((state) => state.user) const toggleNav = () => { setIsCollapsed(!isCollapsed) } - const isOnHomePage = - currentPath === appRoutes.index || currentPath === appRoutes.home - const isOnSearchPage = currentPath === appRoutes.search - const isOnProfilePage = new RegExp( - `^${appRoutes.profile.replace(':nprofile', '[^/]+')}$` - ).test(currentPath) - return (
{
- + {!!userState.auth && ( + + )}
)}
@@ -78,19 +68,25 @@ export const SocialNav = () => { ) } -interface NavButtonProps { - to: string - isActive: boolean +interface NavButtonProps extends NavLinkProps { svgPath: string viewBox?: string } -const NavButton = ({ to, isActive, svgPath, viewBox = '0 0 512 512' }: NavButtonProps) => ( - ( + + `btn btnMain socialNavInsideBtn ${ + isActive ? 'socialNavInsideBtnActive' : '' + }` + } > - -); - - + +) From 99ce338502149f135c3cfc7f483a0a8b44d5e845 Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 23 Oct 2024 18:00:53 +0200 Subject: [PATCH 09/10] feat: browser router and SPA 404.html --- index.html | 31 ++++++++ public/404.html | 51 ++++++++++++ src/App.tsx | 19 +---- src/layout/socialNav.tsx | 4 +- src/main.tsx | 11 +-- src/routes/index.tsx | 164 +++++++++++++++++++++++---------------- 6 files changed, 186 insertions(+), 94 deletions(-) create mode 100644 public/404.html diff --git a/index.html b/index.html index 9b3186d..2f1f87c 100644 --- a/index.html +++ b/index.html @@ -9,6 +9,37 @@ DEG Mods - Liberating Game Mods + + + +
diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..8912b25 --- /dev/null +++ b/public/404.html @@ -0,0 +1,51 @@ + + + + + + Single Page Apps for GitHub Pages + + + + diff --git a/src/App.tsx b/src/App.tsx index 60b4df3..fe0ccd1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,6 @@ -import { Route, Routes } from 'react-router-dom' -import { Layout } from './layout' -import { routes } from './routes' +import { RouterProvider } from 'react-router-dom' import { useEffect } from 'react' +import { router } from 'routes' import './styles/styles.css' function App() { @@ -22,19 +21,7 @@ function App() { } }, []) - return ( - - }> - {routes.map((route, index) => ( - - ))} - - - ) + return } export default App diff --git a/src/layout/socialNav.tsx b/src/layout/socialNav.tsx index e38bd88..914e8c0 100644 --- a/src/layout/socialNav.tsx +++ b/src/layout/socialNav.tsx @@ -29,11 +29,11 @@ export const SocialNav = () => { svgPath='M511.8 287.6L512.5 447.7C512.5 450.5 512.3 453.1 512 455.8V472C512 494.1 494.1 512 472 512H456C454.9 512 453.8 511.1 452.7 511.9C451.3 511.1 449.9 512 448.5 512H392C369.9 512 352 494.1 352 472V384C352 366.3 337.7 352 320 352H256C238.3 352 224 366.3 224 384V472C224 494.1 206.1 512 184 512H128.1C126.6 512 125.1 511.9 123.6 511.8C122.4 511.9 121.2 512 120 512H104C81.91 512 64 494.1 64 472V360C64 359.1 64.03 358.1 64.09 357.2V287.6H32.05C14.02 287.6 0 273.5 0 255.5C0 246.5 3.004 238.5 10.01 231.5L266.4 8.016C273.4 1.002 281.4 0 288.4 0C295.4 0 303.4 2.004 309.5 7.014L416 100.7V64C416 46.33 430.3 32 448 32H480C497.7 32 512 46.33 512 64V185L564.8 231.5C572.8 238.5 576.9 246.5 575.8 255.5C575.8 273.5 560.8 287.6 543.8 287.6L511.8 287.6z' /> - - - - - - + + + + ) diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 991105e..18e4d63 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -1,3 +1,5 @@ +import { createBrowserRouter } from 'react-router-dom' +import { Layout } from 'layout' import { SearchPage } from 'pages/search' import { AboutPage } from '../pages/about' import { BlogsPage } from '../pages/blogs' @@ -10,6 +12,10 @@ import { SettingsPage } from '../pages/settings' import { SubmitModPage } from '../pages/submitMod' import { WritePage } from '../pages/write' import { GamePage } from 'pages/game' +import { NotFoundPage } from 'pages/404' +import { FeedLayout } from 'layout/feed' +import { FeedPage } from 'pages/feed' +import { NotificationsPage } from 'pages/notifications' export const appRoutes = { index: '/', @@ -28,7 +34,9 @@ export const appRoutes = { settingsRelays: '/settings-relays', settingsPreferences: '/settings-preferences', settingsAdmin: '/settings-admin', - profile: '/profile/:nprofile?' + profile: '/profile/:nprofile?', + feed: '/feed', + notifications: '/notifications' } export const getGamePageRoute = (name: string) => @@ -43,73 +51,91 @@ export const getModsEditPageRoute = (eventId: string) => export const getProfilePageRoute = (nprofile: string) => appRoutes.profile.replace(':nprofile', nprofile) -export const routes = [ +export const router = createBrowserRouter([ { - path: appRoutes.index, - element: - }, - { - path: appRoutes.home, - element: - }, - { - path: appRoutes.games, - element: - }, - { - path: appRoutes.game, - element: - }, - { - path: appRoutes.mods, - element: - }, - { - path: appRoutes.mod, - element: - }, - { - path: appRoutes.about, - element: - }, - { - path: appRoutes.blog, - element: - }, - { - path: appRoutes.submitMod, - element: - }, - { - path: appRoutes.editMod, - element: - }, - { - path: appRoutes.write, - element: - }, - { - path: appRoutes.search, - element: - }, - { - path: appRoutes.settingsProfile, - element: - }, - { - path: appRoutes.settingsRelays, - element: - }, - { - path: appRoutes.settingsPreferences, - element: - }, - { - path: appRoutes.settingsAdmin, - element: - }, - { - path: appRoutes.profile, - element: + element: , + children: [ + { + path: appRoutes.index, + element: + }, + { + path: appRoutes.games, + element: + }, + { + path: appRoutes.game, + element: + }, + { + path: appRoutes.mods, + element: + }, + { + path: appRoutes.mod, + element: + }, + { + path: appRoutes.about, + element: + }, + { + path: appRoutes.blog, + element: + }, + { + path: appRoutes.submitMod, + element: + }, + { + path: appRoutes.editMod, + element: + }, + { + path: appRoutes.write, + element: + }, + { + path: appRoutes.search, + element: + }, + { + path: appRoutes.settingsProfile, + element: + }, + { + path: appRoutes.settingsRelays, + element: + }, + { + path: appRoutes.settingsPreferences, + element: + }, + { + path: appRoutes.settingsAdmin, + element: + }, + { + path: appRoutes.profile, + element: + }, + { + element: , + children: [ + { + path: appRoutes.feed, + element: + }, + { + path: appRoutes.notifications, + element: + } + ] + }, + { + path: '*', + element: + } + ] } -] +]) From 53861a36d34cf969c8d8547e1b2c41b43221c3dd Mon Sep 17 00:00:00 2001 From: enes Date: Thu, 24 Oct 2024 10:48:49 +0200 Subject: [PATCH 10/10] fix: props and placeholder wip text --- src/layout/socialNav.tsx | 8 +++----- src/pages/profile.tsx | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/layout/socialNav.tsx b/src/layout/socialNav.tsx index 914e8c0..cdc142c 100644 --- a/src/layout/socialNav.tsx +++ b/src/layout/socialNav.tsx @@ -74,14 +74,12 @@ interface NavButtonProps extends NavLinkProps { } const NavButton = ({ - to, - end, svgPath, - viewBox = '0 0 512 512' + viewBox = '0 0 512 512', + ...rest }: NavButtonProps) => ( `btn btnMain socialNavInsideBtn ${ isActive ? 'socialNavInsideBtnActive' : '' diff --git a/src/pages/profile.tsx b/src/pages/profile.tsx index 017b57b..ca3d17d 100644 --- a/src/pages/profile.tsx +++ b/src/pages/profile.tsx @@ -499,8 +499,8 @@ export const ProfilePage = () => { )} - {tab === 1 && <>USER's BLOGS WIP} - {tab === 2 && <>USER's POSTS WIP} + {tab === 1 && <>WIP} + {tab === 2 && <>WIP}