From 56ec37e57ba3566fda7ff41dea7262d8543d86ce Mon Sep 17 00:00:00 2001 From: daniyal Date: Mon, 2 Sep 2024 13:47:16 +0500 Subject: [PATCH] feat: display data on landing page --- index.html | 26 ++- package-lock.json | 19 ++ package.json | 1 + src/constants.ts | 35 ++++ src/pages/games.tsx | 25 ++- src/pages/home.tsx | 445 ++++++++++++++++++++++-------------------- src/pages/mods.tsx | 14 +- src/styles/styles.css | 25 +++ src/utils/mod.ts | 20 +- 9 files changed, 379 insertions(+), 231 deletions(-) diff --git a/index.html b/index.html index fef57eb..5d13bc4 100644 --- a/index.html +++ b/index.html @@ -6,7 +6,10 @@ - + @@ -14,24 +17,33 @@ - + - + - - + DEG Mods - Liberating Game Mods
- - + diff --git a/package-lock.json b/package-lock.json index 3d378b3..6526152 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "react-router-dom": "^6.24.1", "react-toastify": "10.0.5", "react-window": "1.8.10", + "swiper": "11.1.11", "uuid": "10.0.0", "webln": "0.3.2" }, @@ -4932,6 +4933,24 @@ "node": ">=4" } }, + "node_modules/swiper": { + "version": "11.1.11", + "resolved": "https://registry.npmjs.org/swiper/-/swiper-11.1.11.tgz", + "integrity": "sha512-077Aw3OrlZpkkBRf/6+44bGh/HZY/vsLEyate2db2KkJgYUIR5TvDgvvhcJtW/puXzw79w5KBc30DauEX6GZYQ==", + "funding": [ + { + "type": "patreon", + "url": "https://www.patreon.com/swiperjs" + }, + { + "type": "open_collective", + "url": "http://opencollective.com/swiper" + } + ], + "engines": { + "node": ">= 4.7.0" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", diff --git a/package.json b/package.json index f4cec95..7fd0014 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "react-router-dom": "^6.24.1", "react-toastify": "10.0.5", "react-window": "1.8.10", + "swiper": "11.1.11", "uuid": "10.0.0", "webln": "0.3.2" }, diff --git a/src/constants.ts b/src/constants.ts index d141103..2cba654 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,2 +1,37 @@ export const T_TAG_VALUE = 'GameMod' export const MOD_FILTER_LIMIT = 20 +export const LANDING_PAGE_DATA = { + featuredSlider: [ + 'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5d3excenzvf5xgkkvdny8qkngveex5knjcnxxqkn2efnx3jrxvpcxgukxdggsmal6', + 'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5df5xccngvtrxqkkydpexukngvp4xgknsvp4vskkgdrxvgmkxdmp8quxycgx78rpf', + 'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5vrrvgmnjc33xuknwde4vskngvekxgknsenyxvkk2ctxvscrvenpvsmnxeqydygjx', + 'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5vf5x9nrxcekxvknjvmzxvkngcfsx5kkzcf3xqknsvmrvgenwe3j8p3nzwgka59vj' + ], + awesomeMods: [ + 'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5d3excenzvf5xgkkvdny8qkngveex5knjcnxxqkn2efnx3jrxvpcxgukxdggsmal6', + 'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5df5xccngvtrxqkkydpexukngvp4xgknsvp4vskkgdrxvgmkxdmp8quxycgx78rpf', + 'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5vrrvgmnjc33xuknwde4vskngvekxgknsenyxvkk2ctxvscrvenpvsmnxeqydygjx' + ], + featuredGames: [ + { + title: 'Immortal Guns', + imageUrl: '' + }, + { + title: 'The Bounce House', + imageUrl: '' + }, + { + title: 'Immortal Guns', + imageUrl: '' + }, + { + title: 'Magenta Horizon Act 1', + imageUrl: '' + }, + { + title: 'DEAD LETTER DEPT. Demo', + imageUrl: '' + } + ] +} diff --git a/src/pages/games.tsx b/src/pages/games.tsx index bdd5dc9..d2ed7bc 100644 --- a/src/pages/games.tsx +++ b/src/pages/games.tsx @@ -35,11 +35,26 @@ export const GamesPage = () => {
- - - - - + + + + +
diff --git a/src/pages/home.tsx b/src/pages/home.tsx index 9a9cef3..b570374 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -1,123 +1,58 @@ +import { Filter, kinds, nip19 } from 'nostr-tools' +import { useState } from 'react' +import { useNavigate } from 'react-router-dom' +import { A11y, Navigation, Pagination } from 'swiper/modules' +import { Swiper, SwiperSlide } from 'swiper/react' import { BlogCard } from '../components/BlogCard' import { GameCard } from '../components/GameCard' import { ModCard } from '../components/ModCard' +import { LANDING_PAGE_DATA } from '../constants' +import { RelayController } from '../controllers' +import { useDidMount } from '../hooks' +import { appRoutes, getModsInnerPageRoute } from '../routes' +import { ModDetails } from '../types' +import { + extractModData, + fetchMods, + handleModImageError, + log, + LogType +} from '../utils' + import '../styles/cardLists.css' import '../styles/SimpleSlider.css' import '../styles/styles.css' +// Import Swiper styles +import 'swiper/css' +import 'swiper/css/navigation' +import 'swiper/css/pagination' + export const HomePage = () => { + const navigate = useNavigate() return (
-
-
-
-
-
-

Placeholder

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. - Integer nec odio. Praesent libero. Sed cursus ante - dapibus diam. Sed nisi. Nulla quis sem at nibh elementum - imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce - nec tellus sed augue semper porta. Mauris massa. - Vestibulum lacinia arcu eget nulla. className aptent - taciti sociosqu ad litora torquent per conubia nostra, - per inceptos himenaeos. Curabitur sodales ligula in - libero. -
-

- -
-
-
-
-
-

Placeholder

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. - Integer nec odio. Praesent libero. Sed cursus ante - dapibus diam. Sed nisi. Nulla quis sem at nibh elementum - imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce - nec tellus sed augue semper porta. Mauris massa. - Vestibulum lacinia arcu eget nulla. className aptent - taciti sociosqu ad litora torquent per conubia nostra, - per inceptos himenaeos. Curabitur sodales ligula in - libero. -
-

- -
-
-
-
-
-

Placeholder

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. - Integer nec odio. Praesent libero. Sed cursus ante - dapibus diam. Sed nisi. Nulla quis sem at nibh elementum - imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce - nec tellus sed augue semper porta. Mauris massa. - Vestibulum lacinia arcu eget nulla. className aptent - taciti sociosqu ad litora torquent per conubia nostra, - per inceptos himenaeos. Curabitur sodales ligula in - libero. -
-

- -
-
-
-
-
-
-
+ + {LANDING_PAGE_DATA.featuredSlider.map((naddr) => ( + + + + ))} +
@@ -126,20 +61,18 @@ export const HomePage = () => {
-

Cool Games (WIP)

+

Cool Games

- - - - - + {LANDING_PAGE_DATA.featuredGames.map((game) => ( + + ))}
navigate(appRoutes.games)} > View All @@ -147,114 +80,24 @@ export const HomePage = () => {
-

Awesome Mods (WIP)

+

Awesome Mods

- { - alert( - 'these are dummy mods. So navigation on these are not implemented yet' - ) - }} - /> - { - alert( - 'these are dummy mods. So navigation on these are not implemented yet' - ) - }} - /> - { - alert( - 'these are dummy mods. So navigation on these are not implemented yet' - ) - }} - /> + {LANDING_PAGE_DATA.awesomeMods.map((naddr) => ( + + ))}
-
-
-
-

Latest Mods (WIP)

-
-
- { - alert( - 'these are dummy mods. So navigation on these are not implemented yet' - ) - }} - /> - { - alert( - 'these are dummy mods. So navigation on these are not implemented yet' - ) - }} - /> - { - alert( - 'these are dummy mods. So navigation on these are not implemented yet' - ) - }} - /> - { - alert( - 'these are dummy mods. So navigation on these are not implemented yet' - ) - }} - /> -
- -
+

Blog Posts (WIP)

@@ -281,3 +124,187 @@ export const HomePage = () => {
) } + +type SlideContentProps = { + naddr: string +} + +const SlideContent = ({ naddr }: SlideContentProps) => { + const navigate = useNavigate() + const [mod, setMod] = useState() + + useDidMount(() => { + const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`) + const { identifier, kind, pubkey, relays = [] } = decoded.data + + const filter: Filter = { + '#a': [identifier], + authors: [pubkey], + kinds: [kind] + } + + RelayController.getInstance() + .fetchEvent(filter, relays) + .then((event) => { + if (event) { + const extracted = extractModData(event) + setMod(extracted) + } + }) + .catch((err) => { + log( + true, + LogType.Error, + 'An error occurred in fetching mod details from relays', + err + ) + }) + }) + + if (!mod) return + + return ( + <> + +
+

{mod.title}

+

+ {mod.summary} +
+

+ +
+ + ) +} + +type DisplayModProps = { + naddr: string +} + +const DisplayMod = ({ naddr }: DisplayModProps) => { + const navigate = useNavigate() + const [mod, setMod] = useState() + + useDidMount(() => { + const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`) + const { identifier, kind, pubkey, relays = [] } = decoded.data + + const filter: Filter = { + '#a': [identifier], + authors: [pubkey], + kinds: [kind] + } + + RelayController.getInstance() + .fetchEvent(filter, relays) + .then((event) => { + if (event) { + const extracted = extractModData(event) + setMod(extracted) + } + }) + .catch((err) => { + log( + true, + LogType.Error, + 'An error occurred in fetching mod details from relays', + err + ) + }) + }) + + if (!mod) return + + const route = getModsInnerPageRoute(naddr) + + return ( + navigate(route)} + /> + ) +} + +const DisplayLatestMods = () => { + const navigate = useNavigate() + const [isFetchingLatestMods, setIsFetchingLatestMods] = useState(true) + const [latestMods, setLatestMods] = useState([]) + + useDidMount(() => { + fetchMods({ source: window.location.host, limit: 4 }) + .then((res) => { + setLatestMods(res) + }) + .finally(() => { + setIsFetchingLatestMods(false) + }) + }) + + return ( +
+
+

Latest Mods

+
+
+ {isFetchingLatestMods ? ( + + ) : ( + latestMods.map((mod) => { + const route = getModsInnerPageRoute( + nip19.naddrEncode({ + identifier: mod.aTag, + pubkey: mod.author, + kind: kinds.ClassifiedListing + }) + ) + + return ( + navigate(route)} + /> + ) + }) + )} +
+ + +
+ ) +} + +const Spinner = () => { + return ( +
+
+
+ ) +} diff --git a/src/pages/mods.tsx b/src/pages/mods.tsx index e61be15..0c3506c 100644 --- a/src/pages/mods.tsx +++ b/src/pages/mods.tsx @@ -91,7 +91,7 @@ export const ModsPage = () => { useEffect(() => { setIsFetching(true) - fetchMods(filterOptions.source) + fetchMods({ source: filterOptions.source }) .then((res) => { setMods(res) }) @@ -106,7 +106,10 @@ export const ModsPage = () => { const until = mods.length > 0 ? mods[mods.length - 1].published_at - 1 : undefined - fetchMods(filterOptions.source, until) + fetchMods({ + source: filterOptions.source, + until + }) .then((res) => { setMods(res) setPage((prev) => prev + 1) @@ -121,7 +124,10 @@ export const ModsPage = () => { const since = mods.length > 0 ? mods[0].published_at + 1 : undefined - fetchMods(filterOptions.source, undefined, since) + fetchMods({ + source: filterOptions.source, + since + }) .then((res) => { setMods(res) setPage((prev) => prev - 1) @@ -215,7 +221,7 @@ export const ModsPage = () => { key={mod.id} title={mod.title} summary={mod.summary} - backgroundLink={mod.featuredImageUrl} + imageUrl={mod.featuredImageUrl} link={`#${route}`} handleClick={() => navigate(route)} /> diff --git a/src/styles/styles.css b/src/styles/styles.css index 6c11cf6..d9a719f 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -655,3 +655,28 @@ a:hover { .errorMainText { } + +.spinner { + display: flex; + justify-content: center; + align-items: center; + height: 100%; +} + +.spinnerCircle { + border: 4px solid #f3f3f3; + border-top: 4px solid #3498db; + border-radius: 50%; + width: 40px; + height: 40px; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/src/utils/mod.ts b/src/utils/mod.ts index ac2a9cc..6e3511d 100644 --- a/src/utils/mod.ts +++ b/src/utils/mod.ts @@ -134,6 +134,13 @@ export const initializeFormState = ( ] }) +interface FetchModsOptions { + source?: string + until?: number + since?: number + limit?: number +} + /** * Fetches a list of mods based on the provided source. * @@ -144,15 +151,16 @@ export const initializeFormState = ( * @returns A promise that resolves to an array of `ModDetails` objects. In case of an error, * it logs the error and shows a notification, then returns an empty array. */ -export const fetchMods = async ( - source: string, - until?: number, - since?: number -): Promise => { +export const fetchMods = async ({ + source, + until, + since, + limit +}: FetchModsOptions): Promise => { // Define the filter criteria for fetching mods const filter: Filter = { kinds: [kinds.ClassifiedListing], // Specify the kind of events to fetch - limit: MOD_FILTER_LIMIT, // Limit the number of events fetched to 20 + limit: limit || MOD_FILTER_LIMIT, // Limit the number of events fetched to 20 '#t': [T_TAG_VALUE], until, // Optional filter to fetch events until this timestamp since // Optional filter to fetch events from this timestamp