Can now modify landing page. Fallback images for games and mods. Cached events. #29
@ -6,3 +6,9 @@ VITE_ADMIN_NPUBS= <A comma separated list of npubs>
|
|||||||
|
|
||||||
# A dedicated npub used for reporting mods, blogs, profile and etc.
|
# A dedicated npub used for reporting mods, blogs, profile and etc.
|
||||||
VITE_REPORTING_NPUB= <npub1...>
|
VITE_REPORTING_NPUB= <npub1...>
|
||||||
|
|
||||||
|
# if there's no featured image, or if the image breaks somewhere down the line, then it should default to this image
|
||||||
|
VITE_FALLBACK_MOD_IMAGE=https://image.nostr.build/1fa7afd3489302c2da8957993ac0fd6c4308eedd4b1b95b24ecfabe3651b2183.png
|
||||||
|
|
||||||
|
# if there's no image, or if the image breaks somewhere down the line, then it should default to this image
|
||||||
|
VITE_FALLBACK_GAME_IMAGE=https://image.nostr.build/1fa7afd3489302c2da8957993ac0fd6c4308eedd4b1b95b24ecfabe3651b2183.png
|
@ -25,6 +25,8 @@ jobs:
|
|||||||
echo "VITE_APP_RELAY=${{ vars.VITE_APP_RELAY }}" >> .env
|
echo "VITE_APP_RELAY=${{ vars.VITE_APP_RELAY }}" >> .env
|
||||||
echo "VITE_ADMIN_NPUBS=${{ vars.VITE_ADMIN_NPUBS }}" >> .env
|
echo "VITE_ADMIN_NPUBS=${{ vars.VITE_ADMIN_NPUBS }}" >> .env
|
||||||
echo "VITE_REPORTING_NPUB=${{ vars.VITE_REPORTING_NPUB }}" >> .env
|
echo "VITE_REPORTING_NPUB=${{ vars.VITE_REPORTING_NPUB }}" >> .env
|
||||||
|
echo "VITE_FALLBACK_MOD_IMAGE=${{ vars.VITE_FALLBACK_MOD_IMAGE }}" >> .env
|
||||||
|
echo "VITE_FALLBACK_GAME_IMAGE=${{ vars.VITE_FALLBACK_GAME_IMAGE }}" >> .env
|
||||||
cat .env
|
cat .env
|
||||||
|
|
||||||
- name: Create Build
|
- name: Create Build
|
||||||
|
@ -25,6 +25,8 @@ jobs:
|
|||||||
echo "VITE_APP_RELAY=${{ vars.VITE_APP_RELAY }}" >> .env
|
echo "VITE_APP_RELAY=${{ vars.VITE_APP_RELAY }}" >> .env
|
||||||
echo "VITE_ADMIN_NPUBS=${{ vars.VITE_ADMIN_NPUBS }}" >> .env
|
echo "VITE_ADMIN_NPUBS=${{ vars.VITE_ADMIN_NPUBS }}" >> .env
|
||||||
echo "VITE_REPORTING_NPUB=${{ vars.VITE_REPORTING_NPUB }}" >> .env
|
echo "VITE_REPORTING_NPUB=${{ vars.VITE_REPORTING_NPUB }}" >> .env
|
||||||
|
echo "VITE_FALLBACK_MOD_IMAGE=${{ vars.VITE_FALLBACK_MOD_IMAGE }}" >> .env
|
||||||
|
echo "VITE_FALLBACK_GAME_IMAGE=${{ vars.VITE_FALLBACK_GAME_IMAGE }}" >> .env
|
||||||
cat .env
|
cat .env
|
||||||
|
|
||||||
- name: Create Build
|
- name: Create Build
|
||||||
|
26
index.html
26
index.html
@ -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
19
package-lock.json
generated
@ -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",
|
||||||
|
@ -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"
|
||||||
},
|
},
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
import '../styles/cardGames.css'
|
import '../styles/cardGames.css'
|
||||||
|
import { handleGameImageError } from '../utils'
|
||||||
|
|
||||||
type GameCardProps = {
|
type GameCardProps = {
|
||||||
backgroundLink: string
|
title: string
|
||||||
|
imageUrl: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GameCard = ({ backgroundLink }: GameCardProps) => {
|
export const GameCard = ({ title, imageUrl }: GameCardProps) => {
|
||||||
return (
|
return (
|
||||||
<a className='cardGameMainWrapperLink' href='search.html'>
|
<a className='cardGameMainWrapperLink' href='search.html'>
|
||||||
<div
|
<div className='cardGameMainWrapper'>
|
||||||
className='cardGameMain'
|
<img
|
||||||
style={{
|
src={imageUrl}
|
||||||
background: `url("${backgroundLink}") center / cover no-repeat`
|
onError={handleGameImageError}
|
||||||
}}
|
className='cardGameMain'
|
||||||
></div>
|
/>
|
||||||
|
</div>
|
||||||
<div className='cardGameMainTitle'>
|
<div className='cardGameMainTitle'>
|
||||||
<p>This is a game title, the best game title</p>
|
<p>{title}</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import '../styles/cardMod.css'
|
import '../styles/cardMod.css'
|
||||||
|
import { handleModImageError } from '../utils'
|
||||||
|
|
||||||
type ModCardProps = {
|
type ModCardProps = {
|
||||||
title: string
|
title: string
|
||||||
summary: string
|
summary: string
|
||||||
backgroundLink: string
|
imageUrl: string
|
||||||
link: string
|
link: string
|
||||||
handleClick: () => void
|
handleClick: () => void
|
||||||
}
|
}
|
||||||
@ -11,7 +12,7 @@ type ModCardProps = {
|
|||||||
export const ModCard = ({
|
export const ModCard = ({
|
||||||
title,
|
title,
|
||||||
summary,
|
summary,
|
||||||
backgroundLink,
|
imageUrl,
|
||||||
link,
|
link,
|
||||||
handleClick
|
handleClick
|
||||||
}: ModCardProps) => {
|
}: ModCardProps) => {
|
||||||
@ -25,12 +26,13 @@ export const ModCard = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className='cardModMain'>
|
<div className='cardModMain'>
|
||||||
<div
|
<div className='cMMPictureWrapper'>
|
||||||
className='cMMPicture'
|
<img
|
||||||
style={{
|
src={imageUrl}
|
||||||
background: `url("${backgroundLink}") center / cover no-repeat`
|
onError={handleModImageError}
|
||||||
}}
|
className='cMMPicture'
|
||||||
></div>
|
/>
|
||||||
|
</div>
|
||||||
<div className='cMMBody'>
|
<div className='cMMBody'>
|
||||||
<h3 className='cMMBodyTitle'>{title}</h3>
|
<h3 className='cMMBodyTitle'>{title}</h3>
|
||||||
<p className='cMMBodyText'>{summary}</p>
|
<p className='cMMBodyText'>{summary}</p>
|
||||||
|
@ -1,2 +1,38 @@
|
|||||||
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: [
|
||||||
|
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5cek8pnrwc34xgknyv33xqkngc34xyknscfjxsknzvp38quxgc33vejnqvqhqecq8',
|
||||||
|
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5vpcxs6nwwp3x5knyd3evckngetxxcknjdfkx5kngdfhvgukvwfjxsunseqnend73',
|
||||||
|
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5d3excenzvf5xgkkvdny8qkngveex5knjcnxxqkn2efnx3jrxvpcxgukxdggsmal6',
|
||||||
|
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5dp4xsex2e3cxuknsdryvvkngc3sxcknjef4vcknvvmyvcukyd3kvd3rxdgnuver5',
|
||||||
|
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5vf5x9nrxcekxvknjvmzxvkngcfsx5kkzcf3xqknsvmrvgenwe3j8p3nzwgka59vj'
|
||||||
|
],
|
||||||
|
awesomeMods: [
|
||||||
|
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5d3excenzvf5xgkkvdny8qkngveex5knjcnxxqkn2efnx3jrxvpcxgukxdggsmal6',
|
||||||
|
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5df5xccngvtrxqkkydpexukngvp4xgknsvp4vskkgdrxvgmkxdmp8quxycgx78rpf',
|
||||||
|
'naddr1qvzqqqrkcgpzquuz5nxzzap2c034s8cuv5ayr7gjaxz7d22pgwfh0qpmsesy9eflqp4nxvp5xqer5den8qexzdrrvverzde5xfskxvm9xv6nsvtxx93nvdfnvy6rze3exyex2wfcx4jnvcfexscngveexvmnwwpsxd3rsd3kxq6ryef4xdnr5vrrvgmnjc33xuknwde4vskngvekxgknsenyxvkk2ctxvscrvenpvsmnxeqydygjx'
|
||||||
|
],
|
||||||
|
featuredGames: [
|
||||||
|
{
|
||||||
|
title: 'SUPERHOT',
|
||||||
|
imageUrl: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'The Bounce House',
|
||||||
|
imageUrl: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Immortal Guns',
|
||||||
|
imageUrl: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Magenta Horizon Act 1',
|
||||||
|
imageUrl: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'DEAD LETTER DEPT. Demo',
|
||||||
|
imageUrl: ''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
@ -14,6 +14,7 @@ import { MetadataController, UserRelaysType } from './metadata'
|
|||||||
*/
|
*/
|
||||||
export class RelayController {
|
export class RelayController {
|
||||||
private static instance: RelayController
|
private static instance: RelayController
|
||||||
|
private events = new Map<string, Event>()
|
||||||
private debug = true
|
private debug = true
|
||||||
public connectedRelays: Relay[] = []
|
public connectedRelays: Relay[] = []
|
||||||
|
|
||||||
@ -151,6 +152,16 @@ export class RelayController {
|
|||||||
// Wait for all publish operations to complete (either fulfilled or rejected)
|
// Wait for all publish operations to complete (either fulfilled or rejected)
|
||||||
await Promise.allSettled(publishPromises)
|
await Promise.allSettled(publishPromises)
|
||||||
|
|
||||||
|
if (publishedOnRelays.length > 0) {
|
||||||
|
// if the event was successfully published to relays then check if it contains the `aTag`
|
||||||
|
// if so, then cache the event
|
||||||
|
|
||||||
|
const aTag = event.tags.find((item) => item[0] === 'a')
|
||||||
|
if (aTag && aTag[1]) {
|
||||||
|
this.events.set(aTag[1], event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Return the list of relay URLs where the event was published
|
// Return the list of relay URLs where the event was published
|
||||||
return publishedOnRelays
|
return publishedOnRelays
|
||||||
}
|
}
|
||||||
@ -335,13 +346,35 @@ export class RelayController {
|
|||||||
filter: Filter,
|
filter: Filter,
|
||||||
relays: string[] = []
|
relays: string[] = []
|
||||||
): Promise<Event | null> => {
|
): Promise<Event | null> => {
|
||||||
|
// first check if event is present in cached map then return that
|
||||||
|
// otherwise query relays
|
||||||
|
if (filter['#a']) {
|
||||||
|
const aTag = filter['#a'][0]
|
||||||
|
const cachedEvent = this.events.get(aTag)
|
||||||
|
|
||||||
|
if (cachedEvent) return cachedEvent
|
||||||
|
}
|
||||||
|
|
||||||
const events = await this.fetchEvents(filter, relays)
|
const events = await this.fetchEvents(filter, relays)
|
||||||
|
|
||||||
// Sort events by creation date in descending order
|
// Sort events by creation date in descending order
|
||||||
events.sort((a, b) => b.created_at - a.created_at)
|
events.sort((a, b) => b.created_at - a.created_at)
|
||||||
|
|
||||||
// Return the most recent event, or null if no events were received
|
if (events.length > 0) {
|
||||||
return events[0] || null
|
const event = events[0]
|
||||||
|
|
||||||
|
// if the aTag was specified in filter then cache the fetched event before returning
|
||||||
|
if (filter['#a']) {
|
||||||
|
const aTag = filter['#a'][0]
|
||||||
|
this.events.set(aTag, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the event
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
// return null if event array is empty
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -358,6 +391,15 @@ export class RelayController {
|
|||||||
hexKey: string,
|
hexKey: string,
|
||||||
userRelaysType: UserRelaysType
|
userRelaysType: UserRelaysType
|
||||||
) => {
|
) => {
|
||||||
|
// first check if event is present in cached map then return that
|
||||||
|
// otherwise query relays
|
||||||
|
if (filter['#a']) {
|
||||||
|
const aTag = filter['#a'][0]
|
||||||
|
const cachedEvent = this.events.get(aTag)
|
||||||
|
|
||||||
|
if (cachedEvent) return cachedEvent
|
||||||
|
}
|
||||||
|
|
||||||
// Get an instance of the MetadataController, which manages user metadata and relays
|
// Get an instance of the MetadataController, which manages user metadata and relays
|
||||||
const metadataController = await MetadataController.getInstance()
|
const metadataController = await MetadataController.getInstance()
|
||||||
|
|
||||||
|
@ -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'>
|
||||||
|
@ -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,189 @@ 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 (
|
||||||
|
<>
|
||||||
|
<div className='IBMSMSCWSPicWrapper'>
|
||||||
|
<img
|
||||||
|
src={mod.featuredImageUrl}
|
||||||
|
onError={handleModImageError}
|
||||||
|
className='IBMSMSCWSPic'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -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)}
|
||||||
/>
|
/>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
.swiper-pagination-bullet-active {
|
.swiper-pagination-bullet-active {
|
||||||
background: rgba(255,255,255,0.5);
|
background: rgba(255, 255, 255, 0.5);
|
||||||
box-shadow: 0 0 4px 0 rgba(0,0,0,0.5);
|
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.simple-slider .swiper-slide {
|
.simple-slider .swiper-slide {
|
||||||
@ -22,16 +22,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.simple-slider .swiper-button-next, .simple-slider .swiper-button-prev {
|
.simple-slider .swiper-button-next,
|
||||||
|
.simple-slider .swiper-button-prev {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
margin-left: 00px;
|
margin-left: 00px;
|
||||||
margin-right: 00px;
|
margin-right: 00px;
|
||||||
color: rgba(255,255,255,0.5);
|
color: rgba(255, 255, 255, 0.5);
|
||||||
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);
|
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);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
height: 75px;
|
height: 75px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
box-shadow: 0 0 8px 0 rgb(0,0,0,0.1);
|
box-shadow: 0 0 8px 0 rgb(0, 0, 0, 0.1);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -39,19 +45,27 @@
|
|||||||
margin-top: -35px;
|
margin-top: -35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.simple-slider .swiper-button-next:hover, .simple-slider .swiper-button-prev:hover {
|
.simple-slider .swiper-button-next:hover,
|
||||||
background: linear-gradient(rgba(255,255,255,0.1), rgba(255,255,255,0.1)), linear-gradient(to top right, #262626, #292929, #262626), linear-gradient(to top right, #262626, #292929, #262626);
|
.simple-slider .swiper-button-prev:hover {
|
||||||
|
background: linear-gradient(
|
||||||
|
rgba(255, 255, 255, 0.1),
|
||||||
|
rgba(255, 255, 255, 0.1)
|
||||||
|
),
|
||||||
|
linear-gradient(to top right, #262626, #292929, #262626),
|
||||||
|
linear-gradient(to top right, #262626, #292929, #262626);
|
||||||
}
|
}
|
||||||
|
|
||||||
.swiper-button-next:after, .swiper-button-prev:after {
|
.swiper-button-next:after,
|
||||||
font-size: 18px;
|
.swiper-button-prev:after {
|
||||||
|
font-size: 18px!important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width:992px) {
|
@media (max-width: 992px) {
|
||||||
.simple-slider .swiper-button-next, .simple-slider .swiper-button-prev {
|
.simple-slider .swiper-button-next,
|
||||||
|
.simple-slider .swiper-button-prev {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
top: unset;
|
top: unset;
|
||||||
width: 48%;
|
width: 45%;
|
||||||
height: unset;
|
height: unset;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
@ -115,7 +129,12 @@
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
background: linear-gradient(rgba(255,255,255,0.15), rgba(255,255,255,0.15)), linear-gradient(to top right, #262626, #292929, #262626), linear-gradient(to top right, #262626, #292929, #262626);
|
background: linear-gradient(
|
||||||
|
rgba(255, 255, 255, 0.15),
|
||||||
|
rgba(255, 255, 255, 0.15)
|
||||||
|
),
|
||||||
|
linear-gradient(to top right, #262626, #292929, #262626),
|
||||||
|
linear-gradient(to top right, #262626, #292929, #262626);
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
@ -129,12 +148,15 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.swiper-container-horizontal > .swiper-pagination-bullets, .swiper-pagination-custom, .swiper-pagination-fraction {
|
.swiper-container-horizontal > .swiper-pagination-bullets,
|
||||||
|
.swiper-pagination-custom,
|
||||||
|
.swiper-pagination-fraction {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.swiper-button-next, .swiper-button-prev {
|
.swiper-button-next,
|
||||||
|
.swiper-button-prev {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,23 +183,35 @@
|
|||||||
.SliderWrapper {
|
.SliderWrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 50px 0;
|
padding: 50px 0;
|
||||||
background: rgba(0,0,0,0.1);
|
background: rgba(0, 0, 0, 0.1);
|
||||||
backdrop-filter: blur(5px);
|
backdrop-filter: blur(5px);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: -25px 0 0 0;
|
margin: -25px 0 0 0;
|
||||||
border-bottom: solid 1px rgba(255,255,255,0.05);
|
border-bottom: solid 1px rgba(255, 255, 255, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.IBMSMSCWSPic {
|
.IBMSMSCWSPic {
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: solid 1px rgba(255,255,255,0.05);
|
border: solid 1px rgba(255, 255, 255, 0.05);
|
||||||
padding-top: 50%;
|
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
box-shadow: 0 0 8px 0 rgba(0,0,0,0.25);
|
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.25);
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover; /* Ensures the image covers the container like a background image */
|
||||||
|
}
|
||||||
|
|
||||||
|
.IBMSMSCWSPicWrapper {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.IBMSMSCWSInfo {
|
.IBMSMSCWSInfo {
|
||||||
@ -187,9 +221,15 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 25px;
|
padding: 25px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background: linear-gradient(rgba(255,255,255,0), rgba(255,255,255,0)), linear-gradient(to top right, rgb(38,38,38), rgb(41,41,41), rgb(38,38,38));
|
background: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0)),
|
||||||
box-shadow: 0 0 8px 0 rgb(0,0,0,0.1);
|
linear-gradient(
|
||||||
border: solid 1px rgba(255,255,255,0.05);
|
to top right,
|
||||||
|
rgb(38, 38, 38),
|
||||||
|
rgb(41, 41, 41),
|
||||||
|
rgb(38, 38, 38)
|
||||||
|
);
|
||||||
|
box-shadow: 0 0 8px 0 rgb(0, 0, 0, 0.1);
|
||||||
|
border: solid 1px rgba(255, 255, 255, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
@ -212,7 +252,7 @@
|
|||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
color: rgba(255,255,255,0.75);
|
color: rgba(255, 255, 255, 0.75);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,7 +261,7 @@
|
|||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
-webkit-line-clamp: 8;
|
-webkit-line-clamp: 8;
|
||||||
color: rgba(255,255,255,0.5);
|
color: rgba(255, 255, 255, 0.5);
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
@ -235,6 +275,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.swiper-pagination {
|
.swiper-pagination {
|
||||||
|
display: none;
|
||||||
|
bottom: -10px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 992px) {
|
@media (max-width: 992px) {
|
||||||
@ -244,7 +286,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.swiper-pagination-bullet {
|
.swiper-pagination-bullet {
|
||||||
background: rgba(0,0,0,0.5);
|
background: rgba(0, 0, 0, 0.5);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
width: 12px;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
@ -252,6 +294,5 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.swiper-pagination-bullet.swiper-pagination-bullet-active {
|
.swiper-pagination-bullet.swiper-pagination-bullet-active {
|
||||||
background: rgba(128,0,255,0.5);
|
background: rgba(128, 0, 255, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,16 +17,24 @@
|
|||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cardGameMain {
|
.cardGameMainWrapper {
|
||||||
|
position: relative;
|
||||||
padding-top: 150%;
|
padding-top: 150%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardGameMain {
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
background: rgba(255,255,255,0.05);
|
box-shadow: 0 0 8px 0 rgb(0, 0, 0, 0.1);
|
||||||
box-shadow: 0 0 8px 0 rgb(0,0,0,0.1);
|
width: 100%;
|
||||||
|
object-fit: cover; /* Ensures the image covers the container like a background image */
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cardGameMainTitle {
|
.cardGameMainTitle {
|
||||||
transition: ease 0.4s;
|
transition: ease 0.4s;
|
||||||
color: rgba(255,255,255,0.5);
|
color: rgba(255, 255, 255, 0.5);
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
@ -36,4 +44,3 @@
|
|||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,11 +9,18 @@
|
|||||||
background: linear-gradient(to top right, #262626, #292929, #262626);
|
background: linear-gradient(to top right, #262626, #292929, #262626);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cMMPicture {
|
.cMMPictureWrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-top: 56.25%;
|
padding-top: 56.25%;
|
||||||
background: rgba(0, 0, 0, 0.1);
|
}
|
||||||
|
|
||||||
|
.cMMPicture {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
object-fit: cover; /* Ensures the image covers the container like a background image */
|
||||||
}
|
}
|
||||||
|
|
||||||
.cMMBody {
|
.cMMBody {
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
color: rgba(255, 255, 255, 0.5);
|
color: rgba(255, 255, 255, 0.5);
|
||||||
box-shadow: 0 0 8px 0 rgb(0, 0, 0, 0.1);
|
box-shadow: 0 0 8px 0 rgb(0, 0, 0, 0.1);
|
||||||
background: rgba(255, 255, 255, 0.05);
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
text-decoration: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.IBMSMSMBSSTagsTag:active {
|
.IBMSMSMBSSTagsTag:active {
|
||||||
|
@ -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
|
||||||
|
@ -123,3 +123,15 @@ export const abbreviateNumber = (value: number): string => {
|
|||||||
return value.toString()
|
return value.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const handleGameImageError = (
|
||||||
|
e: React.SyntheticEvent<HTMLImageElement, Event>
|
||||||
|
) => {
|
||||||
|
e.currentTarget.src = import.meta.env.VITE_FALLBACK_GAME_IMAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handleModImageError = (
|
||||||
|
e: React.SyntheticEvent<HTMLImageElement, Event>
|
||||||
|
) => {
|
||||||
|
e.currentTarget.src = import.meta.env.VITE_FALLBACK_MOD_IMAGE
|
||||||
|
}
|
||||||
|
2
src/vite-env.d.ts
vendored
2
src/vite-env.d.ts
vendored
@ -4,6 +4,8 @@ interface ImportMetaEnv {
|
|||||||
readonly VITE_APP_RELAY: string
|
readonly VITE_APP_RELAY: string
|
||||||
readonly VITE_ADMIN_NPUBS: string
|
readonly VITE_ADMIN_NPUBS: string
|
||||||
readonly VITE_REPORTING_NPUB: string
|
readonly VITE_REPORTING_NPUB: string
|
||||||
|
readonly VITE_FALLBACK_MOD_IMAGE: string
|
||||||
|
readonly VITE_FALLBACK_GAME_IMAGE: string
|
||||||
// more env variables...
|
// more env variables...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user