Skip to content

Instantly share code, notes, and snippets.

@longseespace
Last active September 7, 2024 10:02
Show Gist options
  • Save longseespace/7adb9e27d0dba2848a6496e76e75454e to your computer and use it in GitHub Desktop.
Save longseespace/7adb9e27d0dba2848a6496e76e75454e to your computer and use it in GitHub Desktop.
BoltAI.com Changelog Page (NextJS on Cloudflare Pages, data pulled from Canny)
CANNY_API_KEY=<your_canny_api_key>
import Head from 'next/head'
import { Container } from '@/components/Container'
import { Header } from '@/components/Header'
import { Footer } from '@/components/Footer'
import { format } from 'date-fns'
import Markdown from 'react-markdown'
import rehypeRaw from 'rehype-raw'
export const runtime = 'experimental-edge'
export const getServerSideProps = async ({ req, res, query }) => {
const apiKey = process.env.CANNY_API_KEY
const page = Math.max(query.page || 1, 1)
const skip = (page - 1) * 10
const response = await fetch(
`https://canny.io/api/v1/entries/list?skip=${skip}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
apiKey: apiKey,
}),
}
)
const data = await response.json()
return {
props: {
data,
page,
},
}
}
function convertToIframe(markdownContent) {
let updatedContent = markdownContent
// Regular expression to match YouTube video links
const youtubeRegex = /!\[([^\]]*)\]\(https:\/\/youtu\.be\/([^\)]+)\)/g
// Replace YouTube links with embedded iframes
updatedContent = updatedContent.replace(
youtubeRegex,
(match, title, videoId) => {
return `<iframe width="560" height="315" src="https://www.youtube.com/embed/${videoId}" title="${title}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>`
}
)
return updatedContent
}
export default function ChangelogPage({ data, page }) {
const entries = data.entries.filter((entry) => entry.status === 'published')
const hasMore = data.hasMore
const hasPrev = page > 1
const prevPageLink = `/changelog?page=${page - 1}`
const nextPageLink = `/changelog?page=${page + 1}`
return (
<>
<Head>
<title>Changelog | BoltAI</title>
<meta
property="og:image"
content="https://image.social/get?url=boltai.com/changelog"
/>
</Head>
<Header />
<main>
<Container>
<article className="prose lg:prose-xl overflow-hidden py-20 sm:py-32 lg:pb-32 xl:py-32 min-h-screen">
<h1>Changelog</h1>
{entries.map((entry) => (
<div key={entry.id}>
<span className="text-sm font-semibold text-gray-500">
{format(new Date(entry.publishedAt), 'MMMM do, yyyy')}
</span>
{/* I'm sorry for this :( */}
<h2 className="!mt-0 !mb-2">{entry.title}</h2>
<div className="flex gap-2 mt-4">
{entry.types.includes('new') && (
<span className="inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20">
New
</span>
)}
{entry.types.includes('improved') && (
<span className="inline-flex items-center rounded-md bg-blue-50 px-2 py-1 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-600/20">
Improved
</span>
)}
{entry.types.includes('fixed') && (
<span className="inline-flex items-center rounded-md bg-purple-50 px-2 py-1 text-xs font-medium text-purple-700 ring-1 ring-inset ring-purple-600/20">
Fixed
</span>
)}
</div>
<Markdown rehypePlugins={[rehypeRaw]}>
{convertToIframe(entry.markdownDetails)}
</Markdown>
<hr />
</div>
))}
<div className="flex gap-3">
{hasPrev && (
<a href={prevPageLink} className="text-sm text-blue-600">
&larr; Previous Page
</a>
)}
{hasMore && (
<a href={nextPageLink} className="text-sm text-blue-600">
Next Page &rarr;
</a>
)}
</div>
</article>
</Container>
</main>
<Footer />
</>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment