Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"extends": ["next/core-web-vitals", "next/typescript"],
"ignorePatterns": [
".source/**/*",
"node_modules/**/*",
".next/**/*",
"out/**/*"
],
"rules": {
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_"
}
],
"react/no-unescaped-entities": "error",
"@next/next/no-img-element": "warn"
}
}
151 changes: 75 additions & 76 deletions app/announcements/page.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,17 @@
import { announcementDocs, announcementMeta } from "@/.source"
import { loader } from "fumadocs-core/source"
import { createMDXSource } from "fumadocs-mdx"
import { useMemo } from "react"
import { announcementDocs } from "@/.source"
import { formatDate } from "@/lib/utils"

const source = loader({
baseUrl: "/announcements",
source: createMDXSource(announcementDocs, announcementMeta),
})

interface AnnouncementData {
title: string
date: string
priority?: "high" | "medium" | "low"
tags?: string[]
body: React.ComponentType
}

interface AnnouncementPage {
url: string
data: AnnouncementData
interface AnnouncementDoc {
data: {
title: string
date: string
priority?: "high" | "medium" | "low"
tags?: string[]
body: React.ComponentType
}
info: {
path: string
}
}

const getPriorityColor = (priority?: string) => {
Expand All @@ -36,14 +28,16 @@ const getPriorityColor = (priority?: string) => {
}

export default function AnnouncementsPage() {
const sortedAnnouncements = useMemo(() => {
const allPages = source.getPages() as AnnouncementPage[]
return allPages.sort((a, b) => {
// Cast announcementDocs to proper type
const announcementDocsTyped = announcementDocs as unknown as AnnouncementDoc[]

const sortedAnnouncements = announcementDocsTyped
.filter((doc) => doc?.data?.date)
.sort((a, b) => {
const dateA = new Date(a.data.date).getTime()
const dateB = new Date(b.data.date).getTime()
return dateB - dateA
})
}, [])

return (
<div className="min-h-screen bg-background relative pb-20">
Expand All @@ -64,69 +58,74 @@ export default function AnnouncementsPage() {
{/* Timeline */}
<div className="max-w-5xl mx-auto px-6 lg:px-10 pb-20">
<div className="relative">
{sortedAnnouncements.map((announcement, index) => {
const MDX = announcement.data.body
const date = new Date(announcement.data.date)
const formattedDate = formatDate(date)
const isLast = index === sortedAnnouncements.length - 1
{sortedAnnouncements.length === 0 ? (
<div className="text-center py-12">
<p className="text-muted-foreground">No announcements yet.</p>
</div>
) : (
sortedAnnouncements.map((announcement) => {
const MDX = announcement.data.body
const date = new Date(announcement.data.date)
const formattedDate = formatDate(date)

return (
<div key={announcement.url} className="relative mb-16 last:mb-0">
<div className="flex flex-col md:flex-row gap-y-6">
{/* Left side - Date and Priority */}
<div className="md:w-48 flex-shrink-0">
<div className="md:sticky md:top-24">
<time className="text-sm font-medium text-muted-foreground block mb-3">
{formattedDate}
</time>
return (
<div key={announcement.info.path} className="relative mb-16 last:mb-0">
<div className="flex flex-col md:flex-row gap-y-6">
{/* Left side - Date and Priority */}
<div className="md:w-48 flex-shrink-0">
<div className="md:sticky md:top-24">
<time className="text-sm font-medium text-muted-foreground block mb-3">
{formattedDate}
</time>

{announcement.data.priority && (
<div className={`inline-flex relative z-10 items-center justify-center px-3 py-1 text-xs font-medium rounded-full ${getPriorityColor(announcement.data.priority)}`}>
{announcement.data.priority.toUpperCase()}
</div>
)}
{announcement.data.priority && (
<div className={`inline-flex relative z-10 items-center justify-center px-3 py-1 text-xs font-medium rounded-full ${getPriorityColor(announcement.data.priority)}`}>
{announcement.data.priority.toUpperCase()}
</div>
)}
</div>
</div>
</div>

{/* Right side - Content */}
<div className="flex-1 md:pl-8 relative">
{/* Vertical timeline line */}
<div className="hidden md:block absolute top-2 left-0 w-px h-full bg-border">
{/* Timeline dot */}
<div className="hidden md:block absolute -translate-x-1/2 size-3 bg-primary rounded-full z-10" />
</div>
{/* Right side - Content */}
<div className="flex-1 md:pl-8 relative">
{/* Vertical timeline line */}
<div className="hidden md:block absolute top-2 left-0 w-px h-full bg-border">
{/* Timeline dot */}
<div className="hidden md:block absolute -translate-x-1/2 size-3 bg-primary rounded-full z-10" />
</div>

<div className="space-y-8 md:space-y-10">
<div className="relative z-10 flex flex-col gap-2">
<h2 className="text-2xl font-semibold tracking-tight text-balance">
{announcement.data.title}
</h2>
<div className="space-y-8 md:space-y-10">
<div className="relative z-10 flex flex-col gap-2">
<h2 className="text-2xl font-semibold tracking-tight text-balance">
{announcement.data.title}
</h2>

{/* Tags */}
{announcement.data.tags &&
announcement.data.tags.length > 0 && (
<div className="flex flex-wrap gap-2">
{announcement.data.tags.map((tag: string) => (
<span
key={tag}
className="h-6 w-fit px-2 text-xs font-medium bg-muted text-muted-foreground rounded-full border flex items-center justify-center"
>
{tag}
</span>
))}
</div>
)}
</div>
{/* Tags */}
{announcement.data.tags &&
announcement.data.tags.length > 0 && (
<div className="flex flex-wrap gap-2">
{announcement.data.tags.map((tag: string) => (
<span
key={tag}
className="h-6 w-fit px-2 text-xs font-medium bg-muted text-muted-foreground rounded-full border flex items-center justify-center"
>
{tag}
</span>
))}
</div>
)}
</div>

<div className="prose dark:prose-invert max-w-none prose-headings:scroll-mt-8 prose-headings:font-semibold prose-a:no-underline prose-headings:tracking-tight prose-p:tracking-tight">
<MDX/>
<div className="prose dark:prose-invert max-w-none prose-headings:scroll-mt-8 prose-headings:font-semibold prose-a:no-underline prose-headings:tracking-tight prose-p:tracking-tight">
<MDX/>
</div>
</div>
</div>
</div>
</div>
</div>
)
})}
)
})
)}
</div>
</div>
</div>
Expand Down
Loading