feat: blogs #118

Merged
enes merged 20 commits from feature/blogs into staging 2024-11-11 12:00:59 +00:00
4 changed files with 118 additions and 32 deletions
Showing only changes of commit a3bec707b0 - Show all commits

View File

@ -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

View File

@ -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(

View File

@ -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
} }
} }

View File

@ -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>
)
}