Repository: FilecoinFoundationWeb/filecoin-foundation
Pull request #1230: [UXIT-2359] FFDW Homepage Latest News Section [skip percy]
Author: login: barbaraperic
Created: 2025-03-13T13:52:45Z | Merged: 2025-03-14T15:25:27Z

## 📝 Description

This PR includes [Latest News Section on FFDW Homepage.](https://www.figma.com/design/P67wEFE8S7ay0b7FErfqPE/FFDweb.org-Prototypes?node-id=5677-30504&t=ReuLlvc7MQ1xVTkW-4)

- **Type:** New feature

## 📸 Screenshots

![Screenshot 2025-03-13 at 13 51 06](https://github.com/user-attachments/assets/b68c3993-6018-4c3c-9b4d-e591fba5f0ba)
![Screenshot 2025-03-13 at 13 51 38](https://github.com/user-attachments/assets/4512a89a-7e8f-405f-aa76-fe8adfdadc4c)




Diff:
diff --git a/apps/ff-site/src/app/(homepage)/page.tsx b/apps/ff-site/src/app/(homepage)/page.tsx
index 80c68d17a5..3e713c01bf 100644
--- a/apps/ff-site/src/app/(homepage)/page.tsx
+++ b/apps/ff-site/src/app/(homepage)/page.tsx
@@ -2,6 +2,7 @@ import { Button } from '@filecoin-foundation/ui/Button'
 import { CardGrid } from '@filecoin-foundation/ui/CardGrid'
 import { PageLayout } from '@filecoin-foundation/ui/PageLayout'
 import { StructuredDataScript } from '@filecoin-foundation/ui/StructuredDataScript'
+import { getFeaturedBlogPosts } from '@filecoin-foundation/utils/getFeaturedBlogPosts'
 
 import { PATHS } from '@/constants/paths'
 import { BASE_DOMAIN, FILECOIN_URLS } from '@/constants/siteMetadata'
@@ -27,8 +28,8 @@ import { FeaturedBlogPosts } from './components/FeaturedBlogPosts'
 import { FeaturedEcosystemProjects } from './components/FeaturedEcosystemProjects'
 import { NoBreadCrumbsLayout } from './components/NoBreadCrumbsLayout'
 import { FrontmatterSchema } from './schemas/FrontmatterSchema'
-import { getFeaturedBlogPosts } from './utils/getFeaturedBlogPosts'
 
+import { getBlogPostsData } from '@/blog/utils/getBlogPostData'
 import { getFeaturedEcosystemProjects } from '@/ecosystem-explorer/utils/getFeaturedEcosystemProjects'
 
 const {
@@ -47,7 +48,11 @@ export const metadata = createMetadata({
 })
 
 export default async function Home() {
-  const featuredBlogPosts = await getFeaturedBlogPosts()
+  const featuredBlogPosts = getFeaturedBlogPosts({
+    posts: await getBlogPostsData(),
+    limit: 4,
+  })
+
   const hasFeaturedBlogPosts = featuredBlogPosts.length > 0
 
   const featuredEcosystemProjects = await getFeaturedEcosystemProjects(
diff --git a/apps/ff-site/src/app/(homepage)/utils/getFeaturedBlogPosts.tsx b/apps/ff-site/src/app/(homepage)/utils/getFeaturedBlogPosts.tsx
deleted file mode 100644
index ffe670db67..0000000000
--- a/apps/ff-site/src/app/(homepage)/utils/getFeaturedBlogPosts.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { getBlogPostsData } from '@/blog/utils/getBlogPostData'
-import { sortPostsByDateDesc } from '@/blog/utils/sortBlogPosts'
-
-const MAX_FEATURED_POSTS = 4
-
-export async function getFeaturedBlogPosts() {
-  const blogPosts = await getBlogPostsData()
-  const sortedBlogPosts = sortPostsByDateDesc(blogPosts)
-  return sortedBlogPosts.slice(0, MAX_FEATURED_POSTS)
-}
diff --git a/apps/ff-site/src/app/_constants/sortConstants.ts b/apps/ff-site/src/app/_constants/sortConstants.ts
deleted file mode 100644
index 54dfda7faf..0000000000
--- a/apps/ff-site/src/app/_constants/sortConstants.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export const VALID_SORT_KEYS = [
-  'upcoming-events',
-  'past-events',
-  'all-events',
-  'newest',
-  'oldest',
-  'a-z',
-  'z-a',
-] as const
diff --git a/apps/ff-site/src/app/_types/sortTypes.ts b/apps/ff-site/src/app/_types/sortTypes.ts
index 3041149d7a..8316f56002 100644
--- a/apps/ff-site/src/app/_types/sortTypes.ts
+++ b/apps/ff-site/src/app/_types/sortTypes.ts
@@ -1,9 +1,11 @@
-import { VALID_SORT_KEYS } from '@/constants/sortConstants'
+import type { DateSortOrder } from '@filecoin-foundation/utils/types/sortTypes'
 
-export type ValidSortKey = (typeof VALID_SORT_KEYS)[number]
+type SortKeys = 'upcoming-events' | 'past-events' | 'all-events' | 'a-z' | 'z-a'
 
 export type SortConfig<Entry extends Record<string, any>> = {
-  key: ValidSortKey
+  key: FFSortKey
   label: string
   sortFn: (entries: Array<Entry>) => Array<Entry>
 }
+
+export type FFSortKey = DateSortOrder | SortKeys
diff --git a/apps/ff-site/src/app/blog/constants/sortConfigs.ts b/apps/ff-site/src/app/blog/constants/sortConfigs.ts
index 7c1637f0fe..5d58d207a5 100644
--- a/apps/ff-site/src/app/blog/constants/sortConfigs.ts
+++ b/apps/ff-site/src/app/blog/constants/sortConfigs.ts
@@ -1,7 +1,7 @@
 import {
   sortPostsByDateAsc,
   sortPostsByDateDesc,
-} from '@/blog/utils/sortBlogPosts'
+} from '@filecoin-foundation/utils/sortBlogPosts'
 
 export const blogSortConfigs = [
   {
diff --git a/apps/ff-site/src/app/blog/utils/sortBlogPosts.ts b/apps/ff-site/src/app/blog/utils/sortBlogPosts.ts
deleted file mode 100644
index 4cd6ff85ed..0000000000
--- a/apps/ff-site/src/app/blog/utils/sortBlogPosts.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { compareAsc, compareDesc } from 'date-fns'
-
-import type { ValidSortKey } from '@/types/sortTypes'
-
-import type { BlogPost } from '@/blog/types/blogPostType'
-
-export function sortPostsByDateDesc(blogPosts: Array<BlogPost>) {
-  return sortPostsByDate(blogPosts, 'newest')
-}
-
-export function sortPostsByDateAsc(blogPosts: Array<BlogPost>) {
-  return sortPostsByDate(blogPosts, 'oldest')
-}
-
-function sortPostsByDate(
-  blogPosts: Array<BlogPost>,
-  sortBy: Extract<ValidSortKey, 'newest' | 'oldest'>,
-) {
-  return [...blogPosts].sort((a, b) => {
-    const dateA = a.publishedOn
-    const dateB = b.publishedOn
-
-    switch (sortBy) {
-      case 'newest':
-        return compareDesc(dateA, dateB)
-      case 'oldest':
-        return compareAsc(dateA, dateB)
-      default:
-        return 0
-    }
-  })
-}
diff --git a/apps/ff-site/src/app/ecosystem-explorer/utils/sortEcosystemProjects.ts b/apps/ff-site/src/app/ecosystem-explorer/utils/sortEcosystemProjects.ts
index 366a8969d9..196f9d4c70 100644
--- a/apps/ff-site/src/app/ecosystem-explorer/utils/sortEcosystemProjects.ts
+++ b/apps/ff-site/src/app/ecosystem-explorer/utils/sortEcosystemProjects.ts
@@ -1,4 +1,4 @@
-import type { ValidSortKey } from '@/types/sortTypes'
+import type { FFSortKey } from '@/types/sortTypes'
 
 import type { EcosystemProject } from '../types/ecosystemProjectType'
 
@@ -16,7 +16,7 @@ export function sortEcosystemProjectsAlphabeticalDesc(
 
 function sortEcosystemProjectsAlphabetical(
   projects: Array<EcosystemProject>,
-  sortBy: Extract<ValidSortKey, 'a-z' | 'z-a'>,
+  sortBy: Extract<FFSortKey, 'a-z' | 'z-a'>,
 ) {
   return [...projects].sort((a, b) => {
     const titleA = a.title.toLowerCase()
diff --git a/apps/ffdweb-site/src/app/(homepage)/page.tsx b/apps/ffdweb-site/src/app/(homepage)/page.tsx
index 0e69670903..f4a920d215 100644
--- a/apps/ffdweb-site/src/app/(homepage)/page.tsx
+++ b/apps/ffdweb-site/src/app/(homepage)/page.tsx
@@ -1,19 +1,22 @@
-import { Card } from '@filecoin-foundation/ui/Card'
 import { CardGrid } from '@filecoin-foundation/ui/CardGrid'
 import { PageLayout } from '@filecoin-foundation/ui/PageLayout'
 import { StructuredDataScript } from '@filecoin-foundation/ui/StructuredDataScript'
 import { buildImageSizeProp } from '@filecoin-foundation/utils/buildImageSizeProp'
+import { formatDate } from '@filecoin-foundation/utils/dateUtils'
+import { getFeaturedBlogPosts } from '@filecoin-foundation/utils/getFeaturedBlogPosts'
 
 import { CARET_RIGHT } from '@/constants/cardCTAIcons'
 import { PATHS } from '@/constants/paths'
-import { BASE_DOMAIN, FFDW_URLS, SEO } from '@/constants/siteMetadata'
+import { FFDW_URLS, SEO } from '@/constants/siteMetadata'
 import { ORGANIZATION_SCHEMA_BASE } from '@/constants/structuredDataConstants'
 
 import { graphicsData } from '@/data/graphicsData'
 
 import { createMetadata } from '@/utils/createMetadata'
+import { getCategoryLabel } from '@/utils/getCategoryLabel'
 
 import { Button } from '@/components/Button'
+import { Card } from '@/components/Card'
 import { CTALink } from '@/components/CTALink'
 import { CTASection } from '@/components/CTASection'
 import { PageHeader } from '@/components/PageHeader'
@@ -25,8 +28,14 @@ import { FEATURED_PROJECTS } from './constants/featuredProjects'
 // import { learningResources } from './constants/learningResources'
 import { getFeaturedProjects } from './utils/getFeaturedProjects'
 
+import { getBlogPostsData } from '@/blog/utils/getBlogPostData'
+
 export default async function Home() {
-  const featuredProjects = await getFeaturedProjects([...FEATURED_PROJECTS])
+  const featuredProjects = await getFeaturedProjects(FEATURED_PROJECTS)
+  const featuredBlogPosts = getFeaturedBlogPosts({
+    posts: await getBlogPostsData(),
+    limit: 6,
+  })
 
   return (
     <PageLayout gap="large">
@@ -67,7 +76,6 @@ export default async function Home() {
                 cta={{
                   href: `${PATHS.PROJECTS.path}/${slug}`,
                   text: 'Read More',
-                  baseDomain: BASE_DOMAIN,
                   icon: CARET_RIGHT,
                 }}
                 image={{
@@ -128,7 +136,44 @@ export default async function Home() {
         kicker="Latest News"
         title="Updates from FFDW and DWeb Community"
       >
-        <div className="bg-brand-primary-800 grid h-96 w-full grid-cols-3 gap-4" />
+        <CardGrid cols="smTwoLgThree">
+          {featuredBlogPosts.map((post, i) => {
+            const { slug, category, title, description, image, publishedOn } =
+              post
+
+            const categoryLabel = getCategoryLabel({
+              collectionName: 'blog_posts',
+              category,
+            })
+
+            return (
+              <Card
+                key={slug}
+                title={title}
+                description={{ text: description, isClamped: true }}
+                metaData={[formatDate(publishedOn)]}
+                tags={[{ text: categoryLabel }]}
+                cta={{
+                  href: `${PATHS.BLOG.path}/${slug}`,
+                  text: 'Read Post',
+                  icon: CARET_RIGHT,
+                }}
+                image={{
+                  ...(image || graphicsData.imageFallback.data),
+                  alt: '',
+                  objectFit: 'cover',
+                  sizes: buildImageSizeProp({
+                    startSize: '100vw',
+                    sm: '350px',
+                    md: '470px',
+                    lg: '360px',
+                  }),
+                }}
+              />
+            )
+          })}
+        </CardGrid>
+
         <div className="flex justify-center">
           <Button href={PATHS.BLOG.path}>View All News</Button>
         </div>
diff --git a/apps/ffdweb-site/src/app/(homepage)/utils/getFeaturedProjects.ts b/apps/ffdweb-site/src/app/(homepage)/utils/getFeaturedProjects.ts
index 04b71d405a..15942dc7fd 100644
--- a/apps/ffdweb-site/src/app/(homepage)/utils/getFeaturedProjects.ts
+++ b/apps/ffdweb-site/src/app/(homepage)/utils/getFeaturedProjects.ts
@@ -1,6 +1,8 @@
 import { getProjectData } from '@/projects/utils/getProjectData'
 
-export async function getFeaturedProjects(slugs: string[]) {
+type Slugs = Array<string> | ReadonlyArray<string>
+
+export async function getFeaturedProjects(slugs: Slugs) {
   const projectPromises = slugs.map((slug) => getProjectData(slug))
   return Promise.all(projectPromises)
 }
diff --git a/apps/ffdweb-site/src/app/_components/PageSection.tsx b/apps/ffdweb-site/src/app/_components/PageSection.tsx
index 30069d667a..3368b52ad2 100644
--- a/apps/ffdweb-site/src/app/_components/PageSection.tsx
+++ b/apps/ffdweb-site/src/app/_components/PageSection.tsx
@@ -27,7 +27,7 @@ export function PageSection({
       <div
         className={clsx(
           'flex max-w-2xl flex-col gap-8',
-          isCentered && 'lg:mx-auto lg:text-center',
+          isCentered && 'max-w-xl lg:mx-auto lg:text-center',
         )}
       >
         <span className="inline-block font-bold text-neutral-200">
diff --git a/apps/ffdweb-site/src/app/blog/components/BlogContent.tsx b/apps/ffdweb-site/src/app/blog/components/BlogContent.tsx
index f25fe32c80..3028868c5d 100644
--- a/apps/ffdweb-site/src/app/blog/components/BlogContent.tsx
+++ b/apps/ffdweb-site/src/app/blog/components/BlogContent.tsx
@@ -12,8 +12,8 @@ import {
 } from '@filecoin-foundation/utils/constants/urlParamsConstants'
 import { formatDate } from '@filecoin-foundation/utils/dateUtils'
 import { normalizeQueryParam } from '@filecoin-foundation/utils/urlUtils'
-import { CaretRight } from '@phosphor-icons/react'
 
+import { CARET_RIGHT } from '@/constants/cardCTAIcons'
 import { PATHS } from '@/constants/paths'
 
 import { graphicsData } from '@/data/graphicsData'
@@ -67,12 +67,7 @@ export function BlogContent({ posts }: BlogContentProps) {
               cta={{
                 href: `${PATHS.BLOG.path}/${slug}`,
                 text: 'Read Post',
-                icon: {
-                  component: CaretRight,
-                  size: 16,
-                  position: 'trailing',
-                  weight: 'bold',
-                },
+                icon: CARET_RIGHT,
               }}
               image={{
                 ...(image || graphicsData.imageFallback.data),
diff --git a/packages/utils/package.json b/packages/utils/package.json
index 948e76cdcf..a14a70788b 100644
--- a/packages/utils/package.json
+++ b/packages/utils/package.json
@@ -19,12 +19,14 @@
     "./getCategoryLabel": "./src/getCategoryLabel.ts",
     "./getCMSFieldOptionsAndValidIds": "./src/getCMSFieldOptionsAndValidIds.ts",
     "./getDigestArticleData": "./src/getDigestArticleData.ts",
+    "./getFeaturedBlogPosts": "./src/getFeaturedBlogPosts.ts",
     "./getFeaturedEntry": "./src/getFeaturedEntry.ts",
     "./getMarkdownData": "./src/getMarkdownData.ts",
     "./generateShareArticleLinks": "./src/generateShareArticleLinks.ts",
     "./linkUtils": "./src/linkUtils.ts",
     "./mapCMSOptionsToListboxFormat": "./src/mapCMSOptionsToListboxFormat.ts",
     "./stringUtils": "./src/stringUtils.ts",
+    "./sortBlogPosts": "./src/sortBlogPosts.ts",
     "./urlUtils": "./src/urlUtils.ts",
     "./zodUtils": "./src/zodUtils.ts",
     "./constants/*": "./src/constants/*.ts",
diff --git a/packages/utils/src/getFeaturedBlogPosts.ts b/packages/utils/src/getFeaturedBlogPosts.ts
new file mode 100644
index 0000000000..c94b1a3967
--- /dev/null
+++ b/packages/utils/src/getFeaturedBlogPosts.ts
@@ -0,0 +1,14 @@
+import { sortPostsByDateDesc } from './sortBlogPosts'
+import type { WithPublishedOn } from './types/withPublishedOnType'
+
+type GetFeaturedBlogPostsProps<Entry extends WithPublishedOn> = {
+  posts: Array<Entry>
+  limit: number
+}
+
+export function getFeaturedBlogPosts<Entry extends WithPublishedOn>({
+  posts,
+  limit,
+}: GetFeaturedBlogPostsProps<Entry>) {
+  return sortPostsByDateDesc(posts).slice(0, limit)
+}
diff --git a/packages/utils/src/sortBlogPosts.ts b/packages/utils/src/sortBlogPosts.ts
new file mode 100644
index 0000000000..846b47a40b
--- /dev/null
+++ b/packages/utils/src/sortBlogPosts.ts
@@ -0,0 +1,34 @@
+import { compareAsc, compareDesc } from 'date-fns'
+import type { DateSortOrder } from './types/sortTypes'
+import type { WithPublishedOn } from './types/withPublishedOnType'
+
+export function sortPostsByDateDesc<Entry extends WithPublishedOn>(
+  posts: Array<Entry>,
+) {
+  return sortPostsByDate(posts, 'newest')
+}
+
+export function sortPostsByDateAsc<Entry extends WithPublishedOn>(
+  posts: Array<Entry>,
+) {
+  return sortPostsByDate(posts, 'oldest')
+}
+
+function sortPostsByDate<Entry extends WithPublishedOn>(
+  posts: Array<Entry>,
+  sortBy: DateSortOrder,
+) {
+  return posts.toSorted((a, b) => {
+    const dateA = a.publishedOn
+    const dateB = b.publishedOn
+
+    switch (sortBy) {
+      case 'newest':
+        return compareDesc(dateA, dateB)
+      case 'oldest':
+        return compareAsc(dateA, dateB)
+      default:
+        return 0
+    }
+  })
+}
diff --git a/packages/utils/src/types/sortTypes.ts b/packages/utils/src/types/sortTypes.ts
new file mode 100644
index 0000000000..eb4b83a18e
--- /dev/null
+++ b/packages/utils/src/types/sortTypes.ts
@@ -0,0 +1 @@
+export type DateSortOrder = 'newest' | 'oldest'
diff --git a/packages/utils/src/types/withPublishedOnType.ts b/packages/utils/src/types/withPublishedOnType.ts
new file mode 100644
index 0000000000..959ae34f65
--- /dev/null
+++ b/packages/utils/src/types/withPublishedOnType.ts
@@ -0,0 +1,3 @@
+export type WithPublishedOn = {
+  publishedOn: Date
+}


Reviews:
user: login: mirhamasala
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T14:05:29Z
user: login: barbaraperic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T15:20:13Z
user: login: mirhamasala
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T15:28:22Z
user: login: barbaraperic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T18:30:31Z
user: login: CharlyMartin
body: Review in 🙂
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T14:36:41Z

Review comments:
path: apps/ffdweb-site/src/app/(homepage)/utils/getFeaturedBlogPosts.ts
user: login: mirhamasala
body: @barbaraperic - Any reason why we can't have shared `getFeaturedBlogPosts` and `sortBlogPosts` utils?
created_at: 2025-03-13T14:05:29Z
author_association: CONTRIBUTOR
path: apps/ffdweb-site/src/app/(homepage)/utils/getFeaturedBlogPosts.ts
user: login: barbaraperic
body: Ah yes, it's tightly coupled with `sortTypes` we use in sorting events and blogs, so I skipped it for now to save time. But you have a point, I can look into generalising those?
created_at: 2025-03-13T15:20:12Z
author_association: CONTRIBUTOR
path: apps/ffdweb-site/src/app/(homepage)/utils/getFeaturedBlogPosts.ts
user: login: mirhamasala
body: If needed those can be duplicated like we do for `FFDWebCMSCollectionName` - If it's a similar situation?

```js
// apps/ffdweb-site/src/app/_types/cmsTypes.ts

import type { CMSConfig } from '@filecoin-foundation/utils/types/cmsConfig'

export type FFDWebCMSCollectionName =
  | 'blog_posts'
  | 'digest_articles'
  | 'learning_resources'
  | 'projects'

export type FFDWebCMSConfig = CMSConfig<FFDWebCMSCollectionName>
```

```js
// apps/ff-site/src/app/_types/cmsTypes.ts
import type { CMSConfig } from '@filecoin-foundation/utils/types/cmsConfig'

export type FFCMSCollectionName =
  | 'blog_posts'
  | 'ecosystem_projects'
  | 'event_entries'
  | 'digest_articles'

export type FFCMSConfig = CMSConfig<FFCMSCollectionName>
```
created_at: 2025-03-13T15:28:21Z
author_association: CONTRIBUTOR
path: apps/ffdweb-site/src/app/(homepage)/utils/getFeaturedBlogPosts.ts
user: login: barbaraperic
body: I've refactored, let me know thoughts :) 
created_at: 2025-03-13T18:30:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,34 @@
+import { compareAsc, compareDesc } from 'date-fns'
+import type { SortType } from './types/sortTypes'
+import type { WithPublishedOn } from './types/withPublishedOnType'
+
+export function sortPostsByDateDesc<Entry extends WithPublishedOn>(
+  posts: Array<Entry>,
+) {
+  return sortPostsByDate(posts, 'newest')
+}
+
+export function sortPostsByDateAsc<Entry extends WithPublishedOn>(
+  posts: Array<Entry>,
+) {
+  return sortPostsByDate(posts, 'oldest')
+}
+
+function sortPostsByDate<Entry extends WithPublishedOn>(
+  posts: Array<Entry>,
+  sortBy: SortType<'newest' | 'oldest'>,
path: packages/utils/src/sortBlogPosts.ts
user: login: CharlyMartin
body: Could this just be `'newest' | 'oldest'`?

What does `SortType` add in this implementation?

```suggestion
  sortBy: 'newest' | 'oldest'
```
created_at: 2025-03-14T14:12:08Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,34 @@
+import { compareAsc, compareDesc } from 'date-fns'
+import type { SortType } from './types/sortTypes'
+import type { WithPublishedOn } from './types/withPublishedOnType'
+
+export function sortPostsByDateDesc<Entry extends WithPublishedOn>(
+  posts: Array<Entry>,
+) {
+  return sortPostsByDate(posts, 'newest')
+}
+
+export function sortPostsByDateAsc<Entry extends WithPublishedOn>(
+  posts: Array<Entry>,
+) {
+  return sortPostsByDate(posts, 'oldest')
+}
+
+function sortPostsByDate<Entry extends WithPublishedOn>(
+  posts: Array<Entry>,
+  sortBy: SortType<'newest' | 'oldest'>,
+) {
+  return [...posts].sort((a, b) => {
path: packages/utils/src/sortBlogPosts.ts
user: login: CharlyMartin
body: Maybe this? We started migrating to `toSorted` in other functions

```suggestion
  return posts.toSorted((a, b) => {
```
created_at: 2025-03-14T14:14:03Z
author_association: COLLABORATOR
diff_hunk: @@ -122,7 +130,44 @@ export default async function Home() {
         kicker="Latest News"
         title="Updates from FFDW and DWeb Community"
       >
-        <div className="bg-brand-primary-800 grid h-96 w-full grid-cols-3 gap-4" />
+        <CardGrid cols="smTwo">
path: apps/ffdweb-site/src/app/(homepage)/page.tsx
user: login: CharlyMartin
body: On Figma this section uses a 3x3 layout (just flagging)

https://www.figma.com/design/P67wEFE8S7ay0b7FErfqPE/FFDweb.org-Prototypes?node-id=5386-9837&t=dGlK8a1hG2qN82qs-4
created_at: 2025-03-14T14:16:35Z
author_association: COLLABORATOR
diff_hunk: @@ -1,9 +1,11 @@
-import { VALID_SORT_KEYS } from '@/constants/sortConstants'
+import type { SortType } from '@filecoin-foundation/utils/types/sortTypes'
 
-export type ValidSortKey = (typeof VALID_SORT_KEYS)[number]
+type SortKeys = 'upcoming-events' | 'past-events' | 'all-events' | 'a-z' | 'z-a'
 
 export type SortConfig<Entry extends Record<string, any>> = {
-  key: ValidSortKey
+  key: FFSortKey
   label: string
   sortFn: (entries: Array<Entry>) => Array<Entry>
 }
+
+export type FFSortKey = SortType<SortKeys>
path: apps/ff-site/src/app/_types/sortTypes.ts
user: login: CharlyMartin
body: I find this a tad confusing.

If we want to centralise sorting type in the shared package, I think I would rather do this:
(type names TBD)

```ts
// in the shared package
type ChronologicalSort = "newest" | "oldest"

// in this file
export type FFSortKey = ChronologicalSort & SortKeys
```

What do you think?
created_at: 2025-03-14T14:25:17Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,14 @@
+import { sortPostsByDateDesc } from './sortBlogPosts'
+import type { WithPublishedOn } from './types/withPublishedOnType'
+
+type GetFeaturedBlogPostsProps<Entry extends WithPublishedOn> = {
+  posts: Array<Entry>
+  limit: number
+}
+
+export async function getFeaturedBlogPosts<Entry extends WithPublishedOn>({
path: packages/utils/src/getFeaturedBlogPosts.ts
user: login: CharlyMartin
body: Does `getFeaturedBlogPosts` need to be async? (The prop type suggests that it's not)
created_at: 2025-03-14T14:27:46Z
author_association: COLLABORATOR

PR comments:
user: login: notion-workspace[bot]
created_at: 2025-03-13T13:52:49Z
body: <a href="https://www.notion.so/FFDW-HomePage-Add-Latest-News-Section-1b57631f2825800aa1b2e2a82585bd8f?pvs=8&n=github_linkback">[FFDW] HomePage · Add Latest News Section</a>
author_association: NONE
user: login: vercel[bot]
created_at: 2025-03-13T13:52:51Z
body: [vc]: #6zRD6lWiOWkufkcLHUzQHNAeKem8W52vl4joItFV5dk=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJmaWxlY29pbi1mb3VuZGF0aW9uLXNpdGUiLCJyb290RGlyZWN0b3J5IjoiYXBwcy9mZi1zaXRlIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6ImZpbGVjb2luLWZvdW5kYXRpb24tc2ktZ2l0LTViMjI1MC1maWxlY29pbi1mb3VuZGF0aW9ucy1wcm9qZWN0cy52ZXJjZWwuYXBwIn0sImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9maWxlY29pbi1mb3VuZGF0aW9ucy1wcm9qZWN0cy9maWxlY29pbi1mb3VuZGF0aW9uLXNpdGUvMzJqUzVEcmc5dnZCTEp4ZUxTYXFoTmh1R3I0eSIsInByZXZpZXdVcmwiOiJmaWxlY29pbi1mb3VuZGF0aW9uLXNpLWdpdC01YjIyNTAtZmlsZWNvaW4tZm91bmRhdGlvbnMtcHJvamVjdHMudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCJ9LHsibmFtZSI6ImZmZHdlYi1zaXRlIiwicm9vdERpcmVjdG9yeSI6ImFwcHMvZmZkd2ViLXNpdGUiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiZmZkd2ViLXNpdGUtZ2l0LWJwLWZmZHctaG8tNjM5YTZlLWZpbGVjb2luLWZvdW5kYXRpb25zLXByb2plY3RzLnZlcmNlbC5hcHAifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2ZpbGVjb2luLWZvdW5kYXRpb25zLXByb2plY3RzL2ZmZHdlYi1zaXRlLzR0dlhTV2FZQ0E0ZlVHMUg0RmRpQmIzNlZreWsiLCJwcmV2aWV3VXJsIjoiZmZkd2ViLXNpdGUtZ2l0LWJwLWZmZHctaG8tNjM5YTZlLWZpbGVjb2luLWZvdW5kYXRpb25zLXByb2plY3RzLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQifV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **ffdweb-site** | ✅ Ready ([Inspect](https://vercel.com/filecoin-foundations-projects/ffdweb-site/4tvXSWaYCA4fUG1H4FdiBb36Vkyk)) | [Visit Preview](https://ffdweb-site-git-bp-ffdw-ho-639a6e-filecoin-foundations-projects.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/ffdweb-site-git-bp-ffdw-ho-639a6e-filecoin-foundations-projects.vercel.app?via=pr-comment-feedback-link) | Mar 14, 2025 3:06pm |
| **filecoin-foundation-site** | ✅ Ready ([Inspect](https://vercel.com/filecoin-foundations-projects/filecoin-foundation-site/32jS5Drg9vvBLJxeLSaqhNhuGr4y)) | [Visit Preview](https://filecoin-foundation-si-git-5b2250-filecoin-foundations-projects.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/filecoin-foundation-si-git-5b2250-filecoin-foundations-projects.vercel.app?via=pr-comment-feedback-link) | Mar 14, 2025 3:06pm |



author_association: NONE