@@ -0,0 +1,341 @@
1+// All sections in one template
2+// Grid-columns-three
3+// Get content from content/ecosystem/spotlight, content/applications, content/marketplaces, content/organizations, content/podcasts, content/groups
4+import { useRouter } from "next/router";
5+import Head from "next/head";
6+import Meta from "../../components/Meta";
7+import {
8+ Container,
9+ SingleColumn,
10+ Section,
11+ IntraNav,
12+ Markdown,
13+} from "foundation-design-system";
14+import Header from "../../components/Header";
15+import Footer from "../../components/Footer";
16+import Sidebar from "../../components/Sidebar";
17+import Post from "../../components/ecosystem/Post";
18+import classnames from "classnames";
19+import Link from "next/link";
20+import { getAllPosts } from "../../lib/lib";
21+import fs from "fs";
22+import path from "path";
23+import Contact from "../../components/Contact";
24+import { generateDisplayDate, formatDate } from "../../lib/lib";
25+
26+export default function Ecosystem({
27+ search,
28+ post,
29+ posts,
30+ applications,
31+ marketplaces,
32+ organizations,
33+ podcasts,
34+}) {
35+ const router = useRouter();
36+
37+ const { type } = router.query;
38+
39+ if (type === "archive") {
40+ return <SpotlightArchive posts={posts} search={search} />;
41+ }
42+
43+ let title;
44+ if (type) {
45+ switch (type) {
46+ case "applications":
47+ title = "Applications";
48+ break;
49+ case "marketplaces":
50+ title = "Marketplaces";
51+ break;
52+ case "podcasts":
53+ title = "Podcasts";
54+ break;
55+ case "organizations":
56+ title = "Organizations";
57+ break;
58+ default:
59+ title = "Spotlight";
60+ }
61+ } else {
62+ title = "Spotlight";
63+ }
64+
65+ const data = {
66+ title: title,
67+ description: "Explore the Urbit ecosystem.",
68+ };
69+ return (
70+ <Container>
71+ <Head>
72+ <title>{title} • Urbit</title>
73+ {Meta(data)}
74+ </Head>
75+ <IntraNav ourSite="https://urbit.org" search={search} />
76+ <SingleColumn>
77+ <Header />
78+ <Section>
79+ <h1>Ecosystem</h1>
80+ </Section>
81+ <Section className="justify-center">
82+ <div className="flex justify-between sidebar">
83+ <Sidebar>
84+ <EcosystemSidebar />
85+ </Sidebar>
86+ <div
87+ className={classnames("w-full", {
88+ "max-w-prose": type === undefined,
89+ })}
90+ >
91+ {type === undefined && (
92+ <h3>{`Ecosystem Spotlight - ${post.title}`}</h3>
93+ )}
94+ {type === undefined && (
95+ <>
96+ <Post post={post} />
97+ <Link href="/ecosystem/?type=archive" passHref>
98+ <a className="button-lg bg-wall-600 text-white w-fit">
99+ Spotlight Archive
100+ </a>
101+ </Link>
102+ </>
103+ )}
104+ <div
105+ className={classnames("grid grid-cols-2 gap-12 w-full", {
106+ "md:grid-cols-3": type !== "podcasts",
107+ hidden: type === undefined,
108+ })}
109+ >
110+ {type === "organizations" &&
111+ organizations.map((org) => (
112+ <Link href={`/organizations/${org.slug}`}>
113+ <div className="flex flex-col space-y-4 justify-center items-center cursor-pointer">
114+ <img className="w-36" src={org.image} />
115+ <p className="text-center font-bold">{org.title}</p>
116+ </div>
117+ </Link>
118+ ))}
119+ {type === "applications" &&
120+ applications.map((app) => (
121+ <Link href={`/applications/${app.ship}/${app.slug}`}>
122+ <div className="flex flex-col space-y-4 justify-center items-center cursor-pointer">
123+ <div
124+ className="w-36 h-36 overflow-hidden rounded-xl"
125+ style={{
126+ backgroundColor: app?.bgColor || "rgba(0,0,0,0)",
127+ }}
128+ >
129+ {app?.image && (
130+ <img className="w-full h-full" src={app.image} />
131+ )}
132+ </div>
133+ <p className="text-center font-bold">{app.title}</p>
134+ </div>
135+ </Link>
136+ ))}
137+ {type === "marketplaces" &&
138+ marketplaces.map((market) => (
139+ <Link href={`/marketplaces/${market.slug}`}>
140+ <div className="flex flex-col space-y-4 justify-center items-center cursor-pointer">
141+ <img className="w-36" src={market.image} />
142+ <p className="text-center font-bold">{market.title}</p>
143+ </div>
144+ </Link>
145+ ))}
146+ {type === "podcasts" &&
147+ podcasts.map((pod) => (
148+ <Link href={`/podcasts/${pod.slug}`}>
149+ <div className="flex cursor-pointer space-x-4 items-center">
150+ <img className="w-36" src={pod.image} />
151+ <div className="flex flex-col space-y-4">
152+ <p className="font-bold">{pod.podcast}</p>
153+ <p>{pod.title}</p>
154+ <p className="text-wall-400">{pod.date}</p>
155+ </div>
156+ </div>
157+ </Link>
158+ ))}
159+ </div>
160+ </div>
161+ </div>
162+ <Contact emphasize className="pt-36 self-center w-fit mx-auto" />
163+ </Section>
164+ </SingleColumn>
165+ <Footer />
166+ </Container>
167+ );
168+}
169+
170+function ActiveLink({ children, href, className, currentPath }) {
171+ // special casing this behaviour which overloads one nav
172+ const archive = Boolean(
173+ currentPath === "/ecosystem?type=archive" && href === "/ecosystem"
174+ );
175+ const activeClassName = classnames({
176+ "text-wall-600": currentPath === href || archive,
177+ "text-wall-500": currentPath !== href && !archive,
178+ });
179+
180+ return (
181+ <Link href={href} passHref>
182+ <a className={`${className} ${activeClassName}`}>{children}</a>
183+ </Link>
184+ );
185+}
186+
187+function EcosystemSidebar() {
188+ const router = useRouter();
189+ let currentPath = router.asPath;
190+ return (
191+ <div className="flex flex-col space-y-4 pr-24">
192+ <ActiveLink
193+ currentPath={currentPath}
194+ className="type-ui"
195+ href="/ecosystem"
196+ >
197+ Spotlight
198+ </ActiveLink>
199+ <ActiveLink
200+ currentPath={currentPath}
201+ className="type-ui"
202+ href="/ecosystem?type=organizations"
203+ >
204+ Organizations
205+ </ActiveLink>
206+ <ActiveLink
207+ currentPath={currentPath}
208+ className="type-ui"
209+ href="/ecosystem?type=applications"
210+ >
211+ Applications
212+ </ActiveLink>
213+ <ActiveLink
214+ currentPath={currentPath}
215+ className="type-ui"
216+ href="/ecosystem?type=marketplaces"
217+ >
218+ Marketplaces
219+ </ActiveLink>
220+ <ActiveLink
221+ currentPath={currentPath}
222+ className="mr-5 type-ui"
223+ href="/ecosystem?type=podcasts"
224+ >
225+ Podcasts
226+ </ActiveLink>
227+ </div>
228+ );
229+}
230+
231+function SpotlightArchive({ posts, search }) {
232+ const post = {
233+ title: "Spotlight Archive",
234+ description: "The archive of ecosystem spotlights.",
235+ };
236+ return (
237+ <Container>
238+ <Head>
239+ <title>Archive • Spotlight • Urbit</title>
240+ {Meta(post)}
241+ </Head>
242+ <IntraNav ourSite="https://urbit.org" search={search} />
243+ <SingleColumn>
244+ <Header />
245+ <Section>
246+ <div className="measure">
247+ <h1>Ecosystem</h1>
248+ </div>
249+ </Section>
250+ <Section className="flex">
251+ <Sidebar>
252+ <EcosystemSidebar />
253+ </Sidebar>
254+ <div>
255+ <h3 className="mb-12">Spotlight Archive</h3>
256+ {posts.map((post) => {
257+ return (
258+ <div key={post.slug} className="mb-24 cursor-pointer">
259+ <Link href={`/ecosystem/spotlight/${post.slug}`}>
260+ <div>
261+ <h3 className="text-green-400">{post.title}</h3>
262+ <div className="type-ui mt-2">
263+ {["featured-1", "featured-2", "featured-3"].map(
264+ (feat, i) => {
265+ if (post?.[feat]) {
266+ return (
267+ <>
268+ {i ? ", " : ""}
269+ <span>{post[feat].title}</span>
270+ </>
271+ );
272+ }
273+ }
274+ )}
275+ </div>
276+ </div>
277+ </Link>
278+ </div>
279+ );
280+ })}
281+ </div>
282+ </Section>
283+ </SingleColumn>
284+ <Footer />
285+ </Container>
286+ );
287+}
288+
289+export async function getStaticProps({}) {
290+ const posts = getAllPosts(
291+ ["title", "date", "slug", "featured-1", "featured-2", "featured-3"],
292+ "ecosystem/spotlight",
293+ "date"
294+ );
295+ let post = getAllPosts(
296+ ["title", "date", "featured-1", "featured-2", "featured-3"],
297+ "ecosystem/spotlight",
298+ "date"
299+ )[0];
300+ ["featured-1", "featured-2", "featured-3"].forEach((feat) => {
301+ if (post?.[feat]) {
302+ post[feat].content = JSON.stringify(
303+ Markdown.parse({
304+ post: { content: post[feat].content },
305+ })
306+ );
307+ }
308+ });
309+ const marketplaces = getAllPosts(["title", "image", "slug"], "marketplaces");
310+ const podcasts = getAllPosts(
311+ ["title", "image", "date", "podcast", "slug"],
312+ "podcasts"
313+ );
314+ const organizations = getAllPosts(
315+ ["title", "image", "slug"],
316+ "organizations"
317+ );
318+ const applications = fs
319+ .readdirSync(path.join(process.cwd(), "content/applications"), {
320+ withFileTypes: true,
321+ })
322+ .filter((f) => f.isDirectory())
323+ .map((dir) =>
324+ getAllPosts(
325+ ["title", "bgColor", "image", "slug"],
326+ `applications/${dir.name}`
327+ )
328+ .map((e) => ({ ...e, ship: dir.name }))
329+ .flat()
330+ )
331+ .flat()
332+ .sort((a, b) => {
333+ const nameA = a.title.toLowerCase();
334+ const nameB = b.title.toLowerCase();
335+ return nameA < nameB ? -1 : 1;
336+ });
337+
338+ return {
339+ props: { posts, post, applications, marketplaces, podcasts, organizations },
340+ };
341+}