feat: blogs #118
@ -20,7 +20,8 @@ export const LANDING_PAGE_DATA = {
|
|||||||
'Cyberpunk 2077',
|
'Cyberpunk 2077',
|
||||||
'ELDEN RING',
|
'ELDEN RING',
|
||||||
'The Coffin of Andy and Leyley'
|
'The Coffin of Andy and Leyley'
|
||||||
]
|
],
|
||||||
|
featuredBlogPosts: []
|
||||||
}
|
}
|
||||||
// we use this object to check if a user has reacted positively or negatively to a post
|
// we use this object to check if a user has reacted positively or negatively to a post
|
||||||
// reactions are kind 7 events and their content is either emoji icon or emoji shortcode
|
// reactions are kind 7 events and their content is either emoji icon or emoji shortcode
|
||||||
|
@ -17,11 +17,11 @@ export const blogRouteLoader =
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const filter = filterForEventsTaggingId(naddr)
|
const filter = filterForEventsTaggingId(naddr)
|
||||||
|
|
||||||
if (!filter) {
|
if (!filter) {
|
||||||
log(true, LogType.Error, 'Unable to create filter from blog naddr.')
|
log(true, LogType.Error, 'Unable to create filter from blog naddr.')
|
||||||
return redirect(appRoutes.blogs)
|
return redirect(appRoutes.blogs)
|
||||||
}
|
}
|
||||||
|
|
||||||
const event = await ndkContext.fetchEvent(filter)
|
const event = await ndkContext.fetchEvent(filter)
|
||||||
if (!event) {
|
if (!event) {
|
||||||
log(true, LogType.Error, 'Unable to fetch the blog event.')
|
log(true, LogType.Error, 'Unable to fetch the blog event.')
|
||||||
@ -29,7 +29,6 @@ export const blogRouteLoader =
|
|||||||
}
|
}
|
||||||
|
|
||||||
const blogDetails = extractBlogDetails(event)
|
const blogDetails = extractBlogDetails(event)
|
||||||
|
|
||||||
return blogDetails
|
return blogDetails
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log(
|
log(
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { NDKFilter } from '@nostr-dev-kit/ndk'
|
import { NDKFilter } from '@nostr-dev-kit/ndk'
|
||||||
import { NDKContextType } from 'contexts/NDKContext'
|
import { NDKContextType } from 'contexts/NDKContext'
|
||||||
import { kinds } from 'nostr-tools'
|
import { kinds } from 'nostr-tools'
|
||||||
import { toast } from 'react-toastify'
|
|
||||||
import { log, LogType, npubToHex } from 'utils'
|
import { log, LogType, npubToHex } from 'utils'
|
||||||
import { extractBlogCardDetails } from 'utils/blog'
|
import { extractBlogCardDetails } from 'utils/blog'
|
||||||
|
|
||||||
@ -31,7 +30,6 @@ export const blogsRouteLoader = (ndkContext: NDKContextType) => async () => {
|
|||||||
'An error occurred in fetching blog details from relays',
|
'An error occurred in fetching blog details from relays',
|
||||||
error
|
error
|
||||||
)
|
)
|
||||||
toast.error('An error occurred in fetching blog details from relays')
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { nip19 } from 'nostr-tools'
|
import { kinds, nip19 } from 'nostr-tools'
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { Link, useNavigate } from 'react-router-dom'
|
||||||
import { A11y, Autoplay, Navigation, Pagination } from 'swiper/modules'
|
import { A11y, Autoplay, Navigation, Pagination } from 'swiper/modules'
|
||||||
import { Swiper, SwiperSlide } from 'swiper/react'
|
import { Swiper, SwiperSlide } from 'swiper/react'
|
||||||
import { BlogCard } from '../components/BlogCard'
|
import { BlogCard } from '../components/BlogCard'
|
||||||
@ -15,19 +15,25 @@ import {
|
|||||||
useNSFWList
|
useNSFWList
|
||||||
} from '../hooks'
|
} from '../hooks'
|
||||||
import { appRoutes, getModPageRoute } from '../routes'
|
import { appRoutes, getModPageRoute } from '../routes'
|
||||||
import { ModDetails } from '../types'
|
import { BlogCardDetails, ModDetails } from '../types'
|
||||||
import { extractModData, handleModImageError, log, LogType } from '../utils'
|
import {
|
||||||
|
extractModData,
|
||||||
|
handleModImageError,
|
||||||
|
log,
|
||||||
|
LogType,
|
||||||
|
npubToHex
|
||||||
|
} 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 styles
|
||||||
import { NDKFilter } from '@nostr-dev-kit/ndk'
|
import { filterForEventsTaggingId, NDKFilter } from '@nostr-dev-kit/ndk'
|
||||||
import 'swiper/css'
|
import 'swiper/css'
|
||||||
import 'swiper/css/navigation'
|
import 'swiper/css/navigation'
|
||||||
import 'swiper/css/pagination'
|
import 'swiper/css/pagination'
|
||||||
import placeholder from '../assets/img/DEGMods Placeholder Img.png'
|
import { extractBlogCardDetails } from 'utils/blog'
|
||||||
|
|
||||||
export const HomePage = () => {
|
export const HomePage = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@ -114,27 +120,7 @@ export const HomePage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DisplayLatestMods />
|
<DisplayLatestMods />
|
||||||
<div className='IBMSecMain IBMSMListWrapper'>
|
<DisplayLatestBlogs />
|
||||||
<div className='IBMSMTitleMain'>
|
|
||||||
<h2 className='IBMSMTitleMainHeading'>Blog Posts (WIP)</h2>
|
|
||||||
</div>
|
|
||||||
<div className='IBMSMList'>
|
|
||||||
<BlogCard image={placeholder} />
|
|
||||||
<BlogCard image={placeholder} />
|
|
||||||
<BlogCard image={placeholder} />
|
|
||||||
<BlogCard image={placeholder} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='IBMSMAction'>
|
|
||||||
<a
|
|
||||||
className='btn btnMain IBMSMActionBtn'
|
|
||||||
role='button'
|
|
||||||
href='blog.html'
|
|
||||||
>
|
|
||||||
View All
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -327,3 +313,105 @@ const Spinner = () => {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DisplayLatestBlogs = () => {
|
||||||
|
const [blogs, setBlogs] = useState<Partial<BlogCardDetails>[]>()
|
||||||
|
const { fetchEvents } = useNDKContext()
|
||||||
|
|
||||||
|
useDidMount(() => {
|
||||||
|
const fetchBlogs = async () => {
|
||||||
|
try {
|
||||||
|
// Show maximum of 4 blog posts
|
||||||
|
// 2 should be featured and the most recent 2 from blog npubs
|
||||||
|
|
||||||
|
// Populate the filter from known naddr (constants.ts)
|
||||||
|
const filters: NDKFilter[] = []
|
||||||
|
for (let i = 0; i < LANDING_PAGE_DATA.featuredBlogPosts.length; i++) {
|
||||||
|
try {
|
||||||
|
const naddr = LANDING_PAGE_DATA.featuredBlogPosts[i]
|
||||||
|
const filterId = filterForEventsTaggingId(naddr)
|
||||||
|
if (filterId) {
|
||||||
|
filters.push(filterId)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Silently ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Create a single filter based on multiple #a's
|
||||||
|
const filter = filters.reduce(
|
||||||
|
(filter, id) => {
|
||||||
|
const a = id['#a']
|
||||||
|
if (a) {
|
||||||
|
filter['#a']?.push(a[0])
|
||||||
|
}
|
||||||
|
return filter
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'#a': []
|
||||||
|
} as NDKFilter
|
||||||
|
)
|
||||||
|
// Fetch featured blogs posts
|
||||||
|
const featuredBlogPosts = await fetchEvents(filter)
|
||||||
|
|
||||||
|
// Fetch latest blog npubs posts
|
||||||
|
const blogNpubs = import.meta.env.VITE_BLOG_NPUBS.split(',')
|
||||||
|
const blogHexkeys = blogNpubs
|
||||||
|
.map(npubToHex)
|
||||||
|
.filter((hexkey) => hexkey !== null)
|
||||||
|
|
||||||
|
// We fetch 4 posts in case of duplicates (from featured)
|
||||||
|
const latestBlogPosts = await fetchEvents({
|
||||||
|
authors: blogHexkeys,
|
||||||
|
kinds: [kinds.LongFormArticle],
|
||||||
|
limit: 4
|
||||||
|
})
|
||||||
|
|
||||||
|
// Remove duplicates
|
||||||
|
const unique = Array.from(
|
||||||
|
[...featuredBlogPosts, ...latestBlogPosts]
|
||||||
|
.reduce((map, obj) => {
|
||||||
|
map.set(obj.id, obj)
|
||||||
|
return map
|
||||||
|
}, new Map())
|
||||||
|
.values()
|
||||||
|
)
|
||||||
|
const latest = unique.slice(0, 4)
|
||||||
|
|
||||||
|
setBlogs(latest.map(extractBlogCardDetails))
|
||||||
|
} catch (error) {
|
||||||
|
log(
|
||||||
|
true,
|
||||||
|
LogType.Error,
|
||||||
|
'An error occurred in fetching blog details from relays',
|
||||||
|
error
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchBlogs()
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='IBMSecMain IBMSMListWrapper'>
|
||||||
|
<div className='IBMSMTitleMain'>
|
||||||
|
<h2 className='IBMSMTitleMainHeading'>Blog Posts</h2>
|
||||||
|
</div>
|
||||||
|
<div className='IBMSMList'>
|
||||||
|
{blogs?.map((b) => (
|
||||||
|
<BlogCard key={b.id} {...b} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='IBMSMAction'>
|
||||||
|
<Link
|
||||||
|
className='btn btnMain IBMSMActionBtn'
|
||||||
|
role='button'
|
||||||
|
to={appRoutes.blogs}
|
||||||
|
>
|
||||||
|
View All
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user