Can now modify landing page. Fallback images for games and mods. Cached events. #29

Merged
freakoverse merged 16 commits from staging into master 2024-09-02 13:28:18 +00:00
9 changed files with 379 additions and 231 deletions
Showing only changes of commit 56ec37e57b - Show all commits

View File

@ -6,7 +6,10 @@
<!-- Open Graph Meta Tags --> <!-- Open Graph Meta Tags -->
<meta property="og:title" content="DEG Mods - Liberating Game Mods" /> <meta property="og:title" content="DEG Mods - Liberating Game Mods" />
<meta property="og:description" content="Never get your game mods censored, get banned, lose your history, nor lose the connection between game mod creators and fans. Download your mods freely." /> <meta
property="og:description"
content="Never get your game mods censored, get banned, lose your history, nor lose the connection between game mod creators and fans. Download your mods freely."
/>
<meta property="og:image" content="/assets/img/DEGM%20Thumb.png" /> <meta property="og:image" content="/assets/img/DEGM%20Thumb.png" />
<meta property="og:url" content="https://degmods.com" /> <meta property="og:url" content="https://degmods.com" />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
@ -14,24 +17,33 @@
<!-- Twitter Card Meta Tags --> <!-- Twitter Card Meta Tags -->
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="DEG Mods - Liberating Game Mods" /> <meta name="twitter:title" content="DEG Mods - Liberating Game Mods" />
<meta name="twitter:description" content="Never get your game mods censored, get banned, lose your history, nor lose the connection between game mod creators and fans. Download your mods freely." /> <meta
name="twitter:description"
content="Never get your game mods censored, get banned, lose your history, nor lose the connection between game mod creators and fans. Download your mods freely."
/>
<meta name="twitter:image" content="/assets/img/DEGM%20Thumb.png" /> <meta name="twitter:image" content="/assets/img/DEGM%20Thumb.png" />
<!-- Other Meta Tags --> <!-- Other Meta Tags -->
<meta name="description" content="Never get your game mods censored, get banned, lose your history, nor lose the connection between game mod creators and fans. Download your mods freely." /> <meta
name="description"
content="Never get your game mods censored, get banned, lose your history, nor lose the connection between game mod creators and fans. Download your mods freely."
/>
<!-- Links and Stylesheets --> <!-- Links and Stylesheets -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/6.4.8/swiper-bundle.min.css" />
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css" /> <link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css" />
<link rel="stylesheet" href="/assets/fonts/fontawesome-all.min.css" /> <link rel="stylesheet" href="/assets/fonts/fontawesome-all.min.css" />
<link rel="icon" type="image/png" sizes="935x934" href="/assets/img/Logo%20with%20circle.png" /> <link
rel="icon"
type="image/png"
sizes="935x934"
href="/assets/img/Logo%20with%20circle.png"
/>
<title>DEG Mods - Liberating Game Mods</title> <title>DEG Mods - Liberating Game Mods</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/main.tsx"></script> <script type="module" src="/src/main.tsx"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/6.4.8/swiper-bundle.min.js"></script> <script src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
</body> </body>
</html> </html>

19
package-lock.json generated
View File

@ -34,6 +34,7 @@
"react-router-dom": "^6.24.1", "react-router-dom": "^6.24.1",
"react-toastify": "10.0.5", "react-toastify": "10.0.5",
"react-window": "1.8.10", "react-window": "1.8.10",
"swiper": "11.1.11",
"uuid": "10.0.0", "uuid": "10.0.0",
"webln": "0.3.2" "webln": "0.3.2"
}, },
@ -4932,6 +4933,24 @@
"node": ">=4" "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": { "node_modules/text-table": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",

View File

@ -36,6 +36,7 @@
"react-router-dom": "^6.24.1", "react-router-dom": "^6.24.1",
"react-toastify": "10.0.5", "react-toastify": "10.0.5",
"react-window": "1.8.10", "react-window": "1.8.10",
"swiper": "11.1.11",
"uuid": "10.0.0", "uuid": "10.0.0",
"webln": "0.3.2" "webln": "0.3.2"
}, },

View File

@ -1,2 +1,37 @@
export const T_TAG_VALUE = 'GameMod' export const T_TAG_VALUE = 'GameMod'
export const MOD_FILTER_LIMIT = 20 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: ''
}
]
}

View File

@ -35,11 +35,26 @@ export const GamesPage = () => {
</div> </div>
<div className='IBMSecMain IBMSMListWrapper'> <div className='IBMSecMain IBMSMListWrapper'>
<div className='IBMSMList IBMSMListFeaturedAlt'> <div className='IBMSMList IBMSMListFeaturedAlt'>
<GameCard backgroundLink='/assets/img/DEGMods%20Placeholder%20Img.png' /> <GameCard
<GameCard backgroundLink='/assets/img/DEGMods%20Placeholder%20Img.png' /> title='This is a game title, the best game title'
<GameCard backgroundLink='/assets/img/DEGMods%20Placeholder%20Img.png' /> imageUrl='/assets/img/DEGMods%20Placeholder%20Img.png'
<GameCard backgroundLink='/assets/img/DEGMods%20Placeholder%20Img.png' /> />
<GameCard backgroundLink='/assets/img/DEGMods%20Placeholder%20Img.png' /> <GameCard
title='This is a game title, the best game title'
imageUrl='/assets/img/DEGMods%20Placeholder%20Img.png'
/>
<GameCard
title='This is a game title, the best game title'
imageUrl='/assets/img/DEGMods%20Placeholder%20Img.png'
/>
<GameCard
title='This is a game title, the best game title'
imageUrl='/assets/img/DEGMods%20Placeholder%20Img.png'
/>
<GameCard
title='This is a game title, the best game title'
imageUrl='/assets/img/DEGMods%20Placeholder%20Img.png'
/>
</div> </div>
</div> </div>
<div className='IBMSecMain'> <div className='IBMSecMain'>

View File

@ -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 { BlogCard } from '../components/BlogCard'
import { GameCard } from '../components/GameCard' import { GameCard } from '../components/GameCard'
import { ModCard } from '../components/ModCard' 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/cardLists.css'
import '../styles/SimpleSlider.css' import '../styles/SimpleSlider.css'
import '../styles/styles.css' import '../styles/styles.css'
// Import Swiper styles
import 'swiper/css'
import 'swiper/css/navigation'
import 'swiper/css/pagination'
export const HomePage = () => { export const HomePage = () => {
const navigate = useNavigate()
return ( return (
<div className='InnerBodyMain'> <div className='InnerBodyMain'>
<div className='SliderWrapper'> <div className='SliderWrapper'>
<div className='ContainerMain'> <div className='ContainerMain'>
<div className='IBMSecMain'> <div className='IBMSecMain'>
<div className='simple-slider IBMSMSlider'> <div className='simple-slider IBMSMSlider'>
<div className='swiper-container IBMSMSliderContainer'> <Swiper
<div className='swiper-wrapper IBMSMSliderContainerWrapper'> className='swiper-container IBMSMSliderContainer'
<div className='swiper-slide IBMSMSliderContainerWrapperSlider'> wrapperClass='swiper-wrapper IBMSMSliderContainerWrapper'
<div modules={[Navigation, Pagination, A11y]}
className='IBMSMSCWSPic' pagination={{ clickable: true, dynamicBullets: true }}
style={{ slidesPerView={1}
background: autoplay={{ delay: 5000 }}
'url("/assets/img/DEGMods%20Placeholder%20Img.png") center / cover no-repeat' speed={1000}
}} navigation
></div> loop
<div className='IBMSMSCWSInfo'> >
<h3 className='IBMSMSCWSInfoHeading'>Placeholder</h3> {LANDING_PAGE_DATA.featuredSlider.map((naddr) => (
<p className='IBMSMSCWSInfoText'> <SwiperSlide className='swiper-slide IBMSMSliderContainerWrapperSlider'>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. <SlideContent naddr={naddr} />
Integer nec odio. Praesent libero. Sed cursus ante </SwiperSlide>
dapibus diam. Sed nisi. Nulla quis sem at nibh elementum ))}
imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce </Swiper>
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.
<br />
</p>
<div className='IBMSMSliderContainerWrapperSliderAction'>
<a
className='btn btnMain IBMSMSliderContainerWrapperSliderActionbtn'
role='button'
href='mods-inner.html'
>
Check it out
</a>
</div>
</div>
</div>
<div className='swiper-slide IBMSMSliderContainerWrapperSlider'>
<div
className='IBMSMSCWSPic'
style={{
background:
'url("/assets/img/DEGMods%20Placeholder%20Img.png") center / cover no-repeat'
}}
></div>
<div className='IBMSMSCWSInfo'>
<h3 className='IBMSMSCWSInfoHeading'>Placeholder</h3>
<p className='IBMSMSCWSInfoText'>
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.
<br />
</p>
<div className='IBMSMSliderContainerWrapperSliderAction'>
<a
className='btn btnMain IBMSMSliderContainerWrapperSliderActionbtn'
role='button'
href='mods-inner.html'
>
Check it out
</a>
</div>
</div>
</div>
<div className='swiper-slide IBMSMSliderContainerWrapperSlider'>
<div
className='IBMSMSCWSPic'
style={{
background:
'url("/assets/img/DEGMods%20Placeholder%20Img.png") center / cover no-repeat'
}}
></div>
<div className='IBMSMSCWSInfo'>
<h3 className='IBMSMSCWSInfoHeading'>Placeholder</h3>
<p className='IBMSMSCWSInfoText'>
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.
<br />
</p>
<div className='IBMSMSliderContainerWrapperSliderAction'>
<a
className='btn btnMain IBMSMSliderContainerWrapperSliderActionbtn'
role='button'
href='mods-inner.html'
>
Check it out
</a>
</div>
</div>
</div>
</div>
<div className='swiper-pagination'></div>
<div className='swiper-button-prev'></div>
<div className='swiper-button-next'></div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -126,20 +61,18 @@ export const HomePage = () => {
<div className='IBMSecMainGroup IBMSecMainGroupAlt'> <div className='IBMSecMainGroup IBMSecMainGroupAlt'>
<div className='IBMSecMain IBMSMListWrapper'> <div className='IBMSecMain IBMSMListWrapper'>
<div className='IBMSMTitleMain'> <div className='IBMSMTitleMain'>
<h2 className='IBMSMTitleMainHeading'>Cool Games (WIP)</h2> <h2 className='IBMSMTitleMainHeading'>Cool Games</h2>
</div> </div>
<div className='IBMSMList IBMSMListFeaturedAlt'> <div className='IBMSMList IBMSMListFeaturedAlt'>
<GameCard backgroundLink='assets/img/DEGMods%20Placeholder%20Img.png' /> {LANDING_PAGE_DATA.featuredGames.map((game) => (
<GameCard backgroundLink='assets/img/DEGMods%20Placeholder%20Img.png' /> <GameCard title={game.title} imageUrl={game.imageUrl} />
<GameCard backgroundLink='assets/img/DEGMods%20Placeholder%20Img.png' /> ))}
<GameCard backgroundLink='assets/img/DEGMods%20Placeholder%20Img.png' />
<GameCard backgroundLink='/assets/img/DEGMods%20Placeholder%20Img.png' />
</div> </div>
<div className='IBMSMAction'> <div className='IBMSMAction'>
<a <a
className='btn btnMain IBMSMActionBtn' className='btn btnMain IBMSMActionBtn'
role='button' role='button'
href='blog.html' onClick={() => navigate(appRoutes.games)}
> >
View All View All
</a> </a>
@ -147,114 +80,24 @@ export const HomePage = () => {
</div> </div>
<div className='IBMSecMain IBMSMListWrapper'> <div className='IBMSecMain IBMSMListWrapper'>
<div className='IBMSMTitleMain'> <div className='IBMSMTitleMain'>
<h2 className='IBMSMTitleMainHeading'>Awesome Mods (WIP)</h2> <h2 className='IBMSMTitleMainHeading'>Awesome Mods</h2>
</div> </div>
<div className='IBMSMList IBMSMListAlt'> <div className='IBMSMList IBMSMListAlt'>
<ModCard {LANDING_PAGE_DATA.awesomeMods.map((naddr) => (
title='Placeholder' <DisplayMod key={naddr} naddr={naddr} />
summary='Lorem ipsum dolor sit amet, consectetur adipiscing elit.' ))}
backgroundLink='/assets/img/DEGMods%20Placeholder%20Img.png'
link=''
handleClick={() => {
alert(
'these are dummy mods. So navigation on these are not implemented yet'
)
}}
/>
<ModCard
title='Placeholder'
summary='Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
backgroundLink='/assets/img/DEGMods%20Placeholder%20Img.png'
link=''
handleClick={() => {
alert(
'these are dummy mods. So navigation on these are not implemented yet'
)
}}
/>
<ModCard
title='Placeholder'
summary='Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
backgroundLink='/assets/img/DEGMods%20Placeholder%20Img.png'
link=''
handleClick={() => {
alert(
'these are dummy mods. So navigation on these are not implemented yet'
)
}}
/>
</div> </div>
<div className='IBMSMAction'> <div className='IBMSMAction'>
<a <a
className='btn btnMain IBMSMActionBtn' className='btn btnMain IBMSMActionBtn'
role='button' role='button'
href='blog.html' onClick={() => navigate(appRoutes.mods)}
>
View All
</a>
</div>
</div>
<div className='IBMSecMain IBMSMListWrapper'>
<div className='IBMSMTitleMain'>
<h2 className='IBMSMTitleMainHeading'>Latest Mods (WIP)</h2>
</div>
<div className='IBMSMList'>
<ModCard
title='Placeholder'
summary='Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
backgroundLink='/assets/img/DEGMods%20Placeholder%20Img.png'
link=''
handleClick={() => {
alert(
'these are dummy mods. So navigation on these are not implemented yet'
)
}}
/>
<ModCard
title='Placeholder'
summary='Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
backgroundLink='/assets/img/DEGMods%20Placeholder%20Img.png'
link=''
handleClick={() => {
alert(
'these are dummy mods. So navigation on these are not implemented yet'
)
}}
/>
<ModCard
title='Placeholder'
summary='Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
backgroundLink='/assets/img/DEGMods%20Placeholder%20Img.png'
link=''
handleClick={() => {
alert(
'these are dummy mods. So navigation on these are not implemented yet'
)
}}
/>
<ModCard
title='Placeholder'
summary='Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
backgroundLink='/assets/img/DEGMods%20Placeholder%20Img.png'
link=''
handleClick={() => {
alert(
'these are dummy mods. So navigation on these are not implemented yet'
)
}}
/>
</div>
<div className='IBMSMAction'>
<a
className='btn btnMain IBMSMActionBtn'
role='button'
href='blog.html'
> >
View All View All
</a> </a>
</div> </div>
</div> </div>
<DisplayLatestMods />
<div className='IBMSecMain IBMSMListWrapper'> <div className='IBMSecMain IBMSMListWrapper'>
<div className='IBMSMTitleMain'> <div className='IBMSMTitleMain'>
<h2 className='IBMSMTitleMainHeading'>Blog Posts (WIP)</h2> <h2 className='IBMSMTitleMainHeading'>Blog Posts (WIP)</h2>
@ -281,3 +124,187 @@ export const HomePage = () => {
</div> </div>
) )
} }
type SlideContentProps = {
naddr: string
}
const SlideContent = ({ naddr }: SlideContentProps) => {
const navigate = useNavigate()
const [mod, setMod] = useState<ModDetails>()
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 <Spinner />
return (
<>
<img
src={mod.featuredImageUrl}
onError={handleModImageError}
className='IBMSMSCWSPic'
/>
<div className='IBMSMSCWSInfo'>
<h3 className='IBMSMSCWSInfoHeading'>{mod.title}</h3>
<p className='IBMSMSCWSInfoText'>
{mod.summary}
<br />
</p>
<div className='IBMSMSliderContainerWrapperSliderAction'>
<a
className='btn btnMain IBMSMSliderContainerWrapperSliderActionbtn'
role='button'
onClick={() => navigate(getModsInnerPageRoute(naddr))}
>
Check it out
</a>
</div>
</div>
</>
)
}
type DisplayModProps = {
naddr: string
}
const DisplayMod = ({ naddr }: DisplayModProps) => {
const navigate = useNavigate()
const [mod, setMod] = useState<ModDetails>()
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 <Spinner />
const route = getModsInnerPageRoute(naddr)
return (
<ModCard
title={mod.title}
summary={mod.summary}
imageUrl={mod.featuredImageUrl}
link={`#${route}`}
handleClick={() => navigate(route)}
/>
)
}
const DisplayLatestMods = () => {
const navigate = useNavigate()
const [isFetchingLatestMods, setIsFetchingLatestMods] = useState(true)
const [latestMods, setLatestMods] = useState<ModDetails[]>([])
useDidMount(() => {
fetchMods({ source: window.location.host, limit: 4 })
.then((res) => {
setLatestMods(res)
})
.finally(() => {
setIsFetchingLatestMods(false)
})
})
return (
<div className='IBMSecMain IBMSMListWrapper'>
<div className='IBMSMTitleMain'>
<h2 className='IBMSMTitleMainHeading'>Latest Mods</h2>
</div>
<div className='IBMSMList'>
{isFetchingLatestMods ? (
<Spinner />
) : (
latestMods.map((mod) => {
const route = getModsInnerPageRoute(
nip19.naddrEncode({
identifier: mod.aTag,
pubkey: mod.author,
kind: kinds.ClassifiedListing
})
)
return (
<ModCard
key={mod.id}
title={mod.title}
summary={mod.summary}
imageUrl={mod.featuredImageUrl}
link={`#${route}`}
handleClick={() => navigate(route)}
/>
)
})
)}
</div>
<div className='IBMSMAction'>
<a
className='btn btnMain IBMSMActionBtn'
role='button'
onClick={() => navigate(appRoutes.mods)}
>
View All
</a>
</div>
</div>
)
}
const Spinner = () => {
return (
<div className='spinner'>
<div className='spinnerCircle'></div>
</div>
)
}

View File

@ -91,7 +91,7 @@ export const ModsPage = () => {
useEffect(() => { useEffect(() => {
setIsFetching(true) setIsFetching(true)
fetchMods(filterOptions.source) fetchMods({ source: filterOptions.source })
.then((res) => { .then((res) => {
setMods(res) setMods(res)
}) })
@ -106,7 +106,10 @@ export const ModsPage = () => {
const until = const until =
mods.length > 0 ? mods[mods.length - 1].published_at - 1 : undefined mods.length > 0 ? mods[mods.length - 1].published_at - 1 : undefined
fetchMods(filterOptions.source, until) fetchMods({
source: filterOptions.source,
until
})
.then((res) => { .then((res) => {
setMods(res) setMods(res)
setPage((prev) => prev + 1) setPage((prev) => prev + 1)
@ -121,7 +124,10 @@ export const ModsPage = () => {
const since = mods.length > 0 ? mods[0].published_at + 1 : undefined const since = mods.length > 0 ? mods[0].published_at + 1 : undefined
fetchMods(filterOptions.source, undefined, since) fetchMods({
source: filterOptions.source,
since
})
.then((res) => { .then((res) => {
setMods(res) setMods(res)
setPage((prev) => prev - 1) setPage((prev) => prev - 1)
@ -215,7 +221,7 @@ export const ModsPage = () => {
key={mod.id} key={mod.id}
title={mod.title} title={mod.title}
summary={mod.summary} summary={mod.summary}
backgroundLink={mod.featuredImageUrl} imageUrl={mod.featuredImageUrl}
link={`#${route}`} link={`#${route}`}
handleClick={() => navigate(route)} handleClick={() => navigate(route)}
/> />

View File

@ -655,3 +655,28 @@ a:hover {
.errorMainText { .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);
}
}

View File

@ -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. * 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, * @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. * it logs the error and shows a notification, then returns an empty array.
*/ */
export const fetchMods = async ( export const fetchMods = async ({
source: string, source,
until?: number, until,
since?: number since,
): Promise<ModDetails[]> => { limit
}: FetchModsOptions): Promise<ModDetails[]> => {
// Define the filter criteria for fetching mods // Define the filter criteria for fetching mods
const filter: Filter = { const filter: Filter = {
kinds: [kinds.ClassifiedListing], // Specify the kind of events to fetch 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], '#t': [T_TAG_VALUE],
until, // Optional filter to fetch events until this timestamp until, // Optional filter to fetch events until this timestamp
since // Optional filter to fetch events from this timestamp since // Optional filter to fetch events from this timestamp