|
| 1 | +import fs from "fs/promises" |
| 2 | +import path from "path" |
| 3 | +import matter from "gray-matter" |
1 | 4 | import type { Metadata } from "next" |
2 | | -import Link from "next/link" |
3 | | -import { ArrowRight, CalendarDays, ExternalLink, MapPin } from "lucide-react" |
4 | | - |
| 5 | +import { MarkdownRenderer } from "@/components/markdown-renderer" |
5 | 6 | import { PageHero } from "@/components/page-hero" |
6 | | -import { Linkedin } from "lucide-react" |
7 | 7 | import { getSiteUrl } from "@/lib/site" |
8 | | -import { |
9 | | - editions, |
10 | | - opportunityOpenSource as oos, |
11 | | - type ConferenceEdition, |
12 | | -} from "@/data/opportunity-open-source" |
| 8 | + |
| 9 | +const FILE_PATH = path.join( |
| 10 | + process.cwd(), |
| 11 | + "contents", |
| 12 | + "pages", |
| 13 | + "opportunity-open-source.md" |
| 14 | +) |
13 | 15 |
|
14 | 16 | export const metadata: Metadata = { |
15 | 17 | title: "Opportunity Open Source | OpenPrinting", |
16 | 18 | description: |
17 | | - "Opportunity Open Source is OpenPrinting's annual FOSS conference in India — talks, workshops and hackathons that bring students into open source. See past editions and the next one.", |
| 19 | + "OpenPrinting's annual free and open source software conference in India — the mission, past editions, and the upcoming one.", |
18 | 20 | alternates: { canonical: getSiteUrl("/opportunity-open-source/") }, |
19 | 21 | openGraph: { |
20 | 22 | title: "Opportunity Open Source | OpenPrinting", |
21 | 23 | description: |
22 | | - "OpenPrinting's annual free and open source software conference in India. Past editions and the upcoming one.", |
| 24 | + "OpenPrinting's annual free and open source software conference in India.", |
23 | 25 | url: getSiteUrl("/opportunity-open-source/"), |
24 | 26 | type: "website", |
25 | 27 | }, |
26 | 28 | } |
27 | 29 |
|
28 | | -function EditionCard({ edition }: { edition: ConferenceEdition }) { |
29 | | - return ( |
30 | | - <div className="flex h-full flex-col rounded-xl border border-border bg-card p-6 shadow-sm"> |
31 | | - <div className="flex items-center justify-between"> |
32 | | - <span className="text-2xl font-bold tracking-tight text-foreground"> |
33 | | - {oos.shortName} {edition.edition} |
34 | | - </span> |
35 | | - {edition.upcoming ? ( |
36 | | - <span className="inline-flex items-center rounded-full border border-emerald-500/30 bg-emerald-500/10 px-2.5 py-1 text-xs font-medium text-emerald-700 dark:text-emerald-300"> |
37 | | - Upcoming |
38 | | - </span> |
39 | | - ) : ( |
40 | | - <span className="text-sm text-muted-foreground">{edition.year}</span> |
41 | | - )} |
42 | | - </div> |
| 30 | +export default async function OpportunityOpenSourcePage() { |
| 31 | + const raw = await fs.readFile(FILE_PATH, "utf8") |
| 32 | + const { data } = matter(raw) |
43 | 33 |
|
44 | | - <dl className="mt-4 space-y-2 text-sm"> |
45 | | - <div className="flex items-center gap-2 text-foreground"> |
46 | | - <MapPin className="h-4 w-4 shrink-0 text-muted-foreground" /> |
47 | | - <span> |
48 | | - {edition.venue} <span className="text-muted-foreground">· {edition.location}</span> |
49 | | - </span> |
50 | | - </div> |
51 | | - <div className="flex items-center gap-2 text-foreground"> |
52 | | - <CalendarDays className="h-4 w-4 shrink-0 text-muted-foreground" /> |
53 | | - <span>{edition.dates}</span> |
54 | | - </div> |
55 | | - </dl> |
56 | | - |
57 | | - {edition.summary ? ( |
58 | | - <p className="mt-4 text-sm leading-6 text-muted-foreground">{edition.summary}</p> |
59 | | - ) : null} |
60 | | - |
61 | | - <div className="mt-auto flex flex-wrap gap-x-5 gap-y-2 pt-5 text-sm"> |
62 | | - {edition.recapUrl ? ( |
63 | | - <Link |
64 | | - href={edition.recapUrl} |
65 | | - className="inline-flex items-center gap-1 font-medium text-primary hover:underline" |
66 | | - > |
67 | | - {edition.upcoming ? "Announcement" : "Read the recap"} |
68 | | - <ArrowRight className="h-4 w-4" /> |
69 | | - </Link> |
70 | | - ) : null} |
71 | | - {edition.scheduleUrl ? ( |
72 | | - <a |
73 | | - href={edition.scheduleUrl} |
74 | | - target="_blank" |
75 | | - rel="noopener noreferrer" |
76 | | - className="inline-flex items-center gap-1 text-muted-foreground hover:text-foreground hover:underline" |
77 | | - > |
78 | | - Schedule |
79 | | - <ExternalLink className="h-3.5 w-3.5" /> |
80 | | - </a> |
81 | | - ) : null} |
82 | | - </div> |
83 | | - </div> |
84 | | - ) |
85 | | -} |
86 | | - |
87 | | -export default function OpportunityOpenSourcePage() { |
88 | | - const next = editions.find((e) => e.upcoming) |
89 | | - const past = editions.filter((e) => !e.upcoming) |
| 34 | + const title = |
| 35 | + typeof data.title === "string" ? data.title : "Opportunity Open Source" |
90 | 36 |
|
91 | 37 | return ( |
92 | 38 | <> |
93 | 39 | <PageHero |
94 | | - title="Opportunity Open Source" |
| 40 | + title={title} |
95 | 41 | description="OpenPrinting's annual free and open source software conference in India." |
96 | 42 | /> |
97 | 43 |
|
98 | | - <main className="min-h-screen bg-background text-foreground pt-16 pb-20"> |
99 | | - <div className="mx-auto max-w-5xl px-4 sm:px-6 lg:px-8 space-y-16"> |
100 | | - <section className="space-y-4"> |
101 | | - <p className="text-base leading-relaxed text-foreground sm:text-lg">{oos.mission}</p> |
102 | | - <p className="text-sm leading-relaxed text-muted-foreground">{oos.origin}</p> |
103 | | - <a |
104 | | - href={oos.linkedin} |
105 | | - target="_blank" |
106 | | - rel="noopener noreferrer" |
107 | | - className="inline-flex items-center gap-2 text-sm font-medium text-primary hover:underline" |
108 | | - > |
109 | | - <Linkedin className="h-4 w-4" /> |
110 | | - Follow Opportunity Open Source on LinkedIn |
111 | | - <ExternalLink className="h-3.5 w-3.5" /> |
112 | | - </a> |
113 | | - </section> |
114 | | - |
115 | | - {next ? ( |
116 | | - <section> |
117 | | - <h2 className="mb-4 text-sm font-semibold uppercase tracking-[0.18em] text-muted-foreground"> |
118 | | - Next edition |
119 | | - </h2> |
120 | | - <div className="relative overflow-hidden rounded-2xl border border-border bg-card p-8 shadow-sm"> |
121 | | - <div className="absolute inset-0 grid-pattern opacity-40" aria-hidden /> |
122 | | - <div className="relative"> |
123 | | - <div className="flex flex-wrap items-center gap-3"> |
124 | | - <h3 className="text-3xl font-bold tracking-tight"> |
125 | | - {oos.shortName} {next.edition} |
126 | | - </h3> |
127 | | - <span className="inline-flex items-center rounded-full border border-emerald-500/30 bg-emerald-500/10 px-2.5 py-1 text-xs font-medium text-emerald-700 dark:text-emerald-300"> |
128 | | - Upcoming |
129 | | - </span> |
130 | | - </div> |
131 | | - <div className="mt-4 flex flex-col gap-2 text-sm sm:flex-row sm:gap-8"> |
132 | | - <span className="inline-flex items-center gap-2"> |
133 | | - <MapPin className="h-4 w-4 text-muted-foreground" /> |
134 | | - {next.venue} · {next.location} |
135 | | - </span> |
136 | | - <span className="inline-flex items-center gap-2"> |
137 | | - <CalendarDays className="h-4 w-4 text-muted-foreground" /> |
138 | | - {next.dates} |
139 | | - </span> |
140 | | - </div> |
141 | | - {next.summary ? ( |
142 | | - <p className="mt-4 max-w-2xl text-sm leading-6 text-muted-foreground"> |
143 | | - {next.summary} |
144 | | - </p> |
145 | | - ) : null} |
146 | | - {next.recapUrl ? ( |
147 | | - <Link |
148 | | - href={next.recapUrl} |
149 | | - className="mt-6 inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline" |
150 | | - > |
151 | | - Read the announcement |
152 | | - <ArrowRight className="h-4 w-4" /> |
153 | | - </Link> |
154 | | - ) : null} |
155 | | - </div> |
156 | | - </div> |
157 | | - </section> |
158 | | - ) : null} |
159 | | - |
160 | | - <section> |
161 | | - <h2 className="mb-6 text-2xl font-bold tracking-tight">Past editions</h2> |
162 | | - <div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3"> |
163 | | - {past.map((edition) => ( |
164 | | - <EditionCard key={edition.edition} edition={edition} /> |
165 | | - ))} |
166 | | - </div> |
167 | | - </section> |
168 | | - |
169 | | - <section className="rounded-2xl border border-border bg-muted/30 p-8"> |
170 | | - <h2 className="text-2xl font-bold tracking-tight">Get involved</h2> |
171 | | - <p className="mt-3 max-w-2xl text-sm leading-6 text-muted-foreground"> |
172 | | - Want to speak, host a future edition, sponsor the conference, or just attend? Follow the |
173 | | - conference on LinkedIn for announcements and calls for proposals, or get in touch with the |
174 | | - OpenPrinting team. |
175 | | - </p> |
176 | | - <div className="mt-6 flex flex-wrap gap-3 text-sm"> |
177 | | - <a |
178 | | - href={oos.linkedin} |
179 | | - target="_blank" |
180 | | - rel="noopener noreferrer" |
181 | | - className="inline-flex items-center gap-2 rounded-full border border-border bg-background px-4 py-2 font-medium text-foreground hover:bg-accent" |
182 | | - > |
183 | | - <Linkedin className="h-4 w-4" /> |
184 | | - LinkedIn |
185 | | - </a> |
186 | | - <Link |
187 | | - href="/contact" |
188 | | - className="inline-flex items-center gap-2 rounded-full border border-border bg-background px-4 py-2 font-medium text-foreground hover:bg-accent" |
189 | | - > |
190 | | - Contact us |
191 | | - </Link> |
192 | | - <Link |
193 | | - href="/gsoc" |
194 | | - className="inline-flex items-center gap-2 rounded-full border border-border bg-background px-4 py-2 font-medium text-foreground hover:bg-accent" |
195 | | - > |
196 | | - Google Summer of Code |
197 | | - </Link> |
198 | | - <Link |
199 | | - href="/sponsors" |
200 | | - className="inline-flex items-center gap-2 rounded-full border border-border bg-background px-4 py-2 font-medium text-foreground hover:bg-accent" |
201 | | - > |
202 | | - Sponsors & donations |
203 | | - </Link> |
204 | | - </div> |
205 | | - </section> |
| 44 | + <main className="min-h-screen bg-background text-foreground pt-24 pb-16"> |
| 45 | + <div className="max-w-4xl mx-auto px-4"> |
| 46 | + <MarkdownRenderer content={raw} showMeta={false} noCard={true} /> |
206 | 47 | </div> |
207 | 48 | </main> |
208 | 49 | </> |
|
0 commit comments