diff --git a/components/SpotlightCard.tsx b/components/SpotlightCard.tsx new file mode 100644 index 000000000..19248cfbc --- /dev/null +++ b/components/SpotlightCard.tsx @@ -0,0 +1,123 @@ +import { formatDate } from 'pliny/utils/formatDate' +import { CoreContent } from 'pliny/utils/contentlayer' +import type { Blog } from 'contentlayer/generated' +import Image from '@/components/Image' +import Link from '@/components/Link' +import siteMetadata from '@/data/siteMetadata' +import { + getSpotlightOgImage, + getSpotlightHeroImage, +} from '@/components/post/postShared' + +interface Props { + post: CoreContent + featured?: boolean +} + +const cardClasses = + 'group block overflow-hidden rounded-lg border-2 border-gray-200 border-opacity-60 transition-colors hover:border-primary-500 dark:border-gray-700 dark:hover:border-primary-400' + +function CompactCard({ post }: { post: CoreContent }) { + const { path, date, title, summary, images } = post + const ogImage = getSpotlightOgImage(images) + + return ( + +
+ {ogImage && ( + + )} +
+
+ +

+ {title} +

+ {summary && ( +

{summary}

+ )} +
+ + ) +} + +function HeroCard({ post }: { post: CoreContent }) { + const { path, date, title, summary, images } = post + const heroImage = getSpotlightHeroImage(images) ?? getSpotlightOgImage(images) + + return ( + +
+ {heroImage && ( + + )} +
+
+

+ Developer Spotlight +

+ +

+ {title} +

+ {summary && ( +

+ {summary} +

+ )} +
+
+ + ) +} + +export default function SpotlightCard({ post, featured = false }: Props) { + if (!featured) { + return + } + + return ( + <> +
+ +
+
+ +
+ + ) +} diff --git a/components/post/postShared.ts b/components/post/postShared.ts index 41d500c4d..3fd25ef86 100644 --- a/components/post/postShared.ts +++ b/components/post/postShared.ts @@ -22,6 +22,9 @@ export const discussUrl = () => /** Spotlight posts: images[0] = OG/social, images[1] = cover hero. */ export const getSpotlightHeroImage = (images?: string[]) => images?.[1] +/** Spotlight posts: images[0] = OG/social preview. */ +export const getSpotlightOgImage = (images?: string[]) => images?.[0] + /** Keep in sync with SectionContainer horizontal layout. */ export const postSectionClasses = 'mx-2 max-w-3xl px-4 sm:px-6 md:mx-auto lg:mx-auto xl:max-w-5xl xl:px-0' diff --git a/next.config.js b/next.config.js index 22f0b12ab..a18a23536 100644 --- a/next.config.js +++ b/next.config.js @@ -150,11 +150,6 @@ module.exports = () => { destination: '/faq/grantee', permanent: true, }, - { - source: '/spotlight', - destination: '/tags/spotlight', - permanent: true, - }, { source: '/heartbeat', destination: 'https://heartbeat.opensats.org/', diff --git a/pages/spotlight/index.tsx b/pages/spotlight/index.tsx new file mode 100644 index 000000000..cddf34dbc --- /dev/null +++ b/pages/spotlight/index.tsx @@ -0,0 +1,107 @@ +import Head from 'next/head' +import { PageSEO } from '@/components/SEO' +import siteMetadata from '@/data/siteMetadata' +import Link from '@/components/Link' +import SpotlightCard from '@/components/SpotlightCard' +import { kebabCase } from 'pliny/utils/kebabCase' +import { sortedBlogPost, allCoreContent } from 'pliny/utils/contentlayer' +import { InferGetStaticPropsType } from 'next' +import { allBlogs } from 'contentlayer/generated' +import type { Blog } from 'contentlayer/generated' + +const PAGE_DESCRIPTION = + 'Meet the developers OpenSats supports and the open-source work they are building.' + +export const getStaticProps = async () => { + const posts = allCoreContent( + sortedBlogPost( + allBlogs.filter( + (post) => + post.draft !== true && + post.tags.map((tag) => kebabCase(tag)).includes('spotlight') + ) + ) as Blog[] + ) + return { props: { posts } } +} + +export default function Spotlight({ + posts, +}: InferGetStaticPropsType) { + const [featured, ...rest] = posts + + const structuredData = { + '@context': 'https://schema.org', + '@type': 'CollectionPage', + name: 'Developer Spotlights', + description: PAGE_DESCRIPTION, + url: `${siteMetadata.siteUrl}/spotlight`, + mainEntity: { + '@type': 'ItemList', + itemListElement: posts.map((post, index) => ({ + '@type': 'ListItem', + position: index + 1, + url: `${siteMetadata.siteUrl}/${post.path}`, + name: post.title, + })), + }, + } + + return ( + <> + + +