@@ -6,7 +6,6 @@ import {
6 SingleColumn,
6 SingleColumn,
10 getAllPosts,
9 getAllPosts,
11 generateDisplayDate,
10 generateDisplayDate,
12 formatDate,
11 formatDate,
@@ -14,12 +13,15 @@ import {
14import Header from "../../components/Header";
13import Header from "../../components/Header";
15import Footer from "../../components/Footer";
14import Footer from "../../components/Footer";
16import Sidebar from "../../components/Sidebar";
15import Sidebar from "../../components/Sidebar";
16+import EcosystemSidebar from "../../components/ecosystem/EcosystemSidebar";
17+import SpotlightArchive from "../../components/ecosystem/SpotlightArchive";
17import Post from "../../components/ecosystem/Post";
18import Post from "../../components/ecosystem/Post";
18import classnames from "classnames";
19import classnames from "classnames";
19import Link from "next/link";
20import Link from "next/link";
20import fs from "fs";
21import fs from "fs";
21import path from "path";
22import path from "path";
22import Contact from "../../components/Contact";
23import Contact from "../../components/Contact";
24+import { matchEcosystemPost } from "../../lib/lib";
24export default function Ecosystem({
26export default function Ecosystem({
@@ -36,10 +38,15 @@ export default function Ecosystem({
37 const { type } = router.query;
39 const { type } = router.query;
41+ // Route ?type=archive to below component;
42+ // otherwise, show directory of ecosystem type
43+ // If no param, we show the spotlight for the month
39 if (type === "archive") {
44 if (type === "archive") {
40 return <SpotlightArchive posts={posts} search={search} />;
45 return <SpotlightArchive posts={posts} search={search} />;
48+ const spotlight = type === undefined;
43 let title;
50 let title;
44 if (type) {
51 if (type) {
45 switch (type) {
52 switch (type) {
@@ -91,21 +98,19 @@ export default function Ecosystem({
91 </Sidebar>
98 </Sidebar>
93 className={classnames("w-full", {
100 className={classnames("w-full", {
94− "max-w-prose": type === undefined,
101+ "max-w-prose": spotlight,
97− {type === undefined && (
104+ {/* Default layout */}
98− <h3>{`Ecosystem Spotlight - ${post.title}`}</h3>
105+ {spotlight && (
100− {type === undefined && (
107+ <h3>{`Ecosystem Spotlight - ${post.title}`}</h3>
102 <Post post={post} />
108 <Post post={post} />
103 <Link href="/ecosystem/?type=archive" passHref>
109 <Link href="/ecosystem/?type=archive" passHref>
104 <a className="button-lg bg-wall-600 text-white w-fit">
110 <a className="button-lg bg-wall-600 text-white w-fit">
105 Spotlight Archive
111 Spotlight Archive
109 <div className="pt-36">
114 <div className="pt-36">
110 <h3 className="pb-2">Urbit Monthly</h3>
115 <h3 className="pb-2">Urbit Monthly</h3>
111 <p className="pb-8">
116 <p className="pb-8">
@@ -116,126 +121,36 @@ export default function Ecosystem({
119−
124+ {/* Ecosystem type layouts */}
121 className={classnames("grid gap-12 w-full", {
126 className={classnames("grid gap-12 w-full", {
122 "grid-cols-2 md:grid-cols-3": type == "organizations" || type == "marketplaces",
127 "grid-cols-2 md:grid-cols-3": type == "organizations" || type == "marketplaces",
123 "grid-cols-1": type === "podcasts" || type === "articles",
128 "grid-cols-1": type === "podcasts" || type === "articles",
124 "grid-cols-1 md:grid-cols-1 lg:grid-cols-1 xl:grid-cols-2":
129 "grid-cols-1 md:grid-cols-1 lg:grid-cols-1 xl:grid-cols-2":
125 type === "applications" || type === "groups",
130 type === "applications" || type === "groups",
126− hidden: type === undefined,
131+ hidden: spotlight,
129 {type === "organizations" &&
134 {type === "organizations" &&
130− organizations.map((org) => (
131− <Link href={`/organizations/${org.slug}`}>
135+ <Organizations dir={organizations} />
132− <div className="flex flex-col space-y-4 items-center cursor-pointer hover:opacity-90">
133− <img className="w-36 rounded-xl" src={org.image} />
134− <p className="text-center font-bold">{org.title}</p>
139 {type === "applications" &&
138 {type === "applications" &&
140− applications.map((app) => (
139+ <Applications dir={applications} />
141− <Link href={`/applications/${app.ship}/${app.slug}`}>
142− <div className="flex space-x-4 items-center cursor-pointer hover:opacity-90">
144− className="h-36 w-36 rounded-xl items-center justify-center shrink-0 overflow-hidden"
146− backgroundColor: app?.bgColor || "rgba(0,0,0,0)",
151− className="items-center h-36 w-36"
157− <p className="text-left font-bold">{app.title}</p>
158− <p className="text-left text-wall-400 font-light">
165 {type === "groups" &&
141 {type === "groups" &&
166− groups.map((group) => (
142+ <Groups dir={groups} />
167− <Link href={`/groups/${group.ship}/${group.slug}`}>
168− <div className="flex space-x-4 items-center cursor-pointer hover:opacity-90">
170− className="h-36 w-36 rounded-xl items-center justify-center shrink-0 overflow-hidden"
172− backgroundColor: group?.tile?.startsWith("#")
177− {!group?.tile?.startsWith("#") && (
179− className="items-center h-36 w-36"
185− <p className="text-left font-bold">{group.title}</p>
186− <p className="text-left text-wall-400 font-light">
193 {type === "marketplaces" &&
144 {type === "marketplaces" &&
194− marketplaces.map((market) => (
195− <Link href={`/marketplaces/${market.slug}`}>
145+ <Marketplaces dir={marketplaces} />
196− <div className="flex flex-col space-y-4 justify-center items-center cursor-pointer hover:opacity-90">
197− <img className="w-36 rounded-xl" src={market.image} />
198− <p className="text-center font-bold">{market.title}</p>
202 {type === "podcasts" &&
147 {type === "podcasts" &&
203− podcasts.map((pod) => (
148+ <Podcasts dir={podcasts} />
204− <Link href={`/podcasts/${pod.slug}`}>
205− <div className="flex cursor-pointer space-x-4 items-center hover:opacity-90">
206− <img className="w-28" src={pod.image} />
207− <div className="flex flex-col space-y-2 min-w-0">
208− <p className="font-bold">{pod.podcast}</p>
209− <p className="min-w-0 min-h-0 leading-5 ">
212− <p className="text-wall-400">
213− {formatDate(generateDisplayDate(pod.date))}
220 {type === "articles" &&
151 {type === "articles" &&
221− articles.map((article) => (
152+ <Articles dir={articles} />
222− <Link href={`/articles/${article.slug}`}>
153+ }
223− <div className="flex cursor-pointer space-x-4 items-center hover:opacity-90">
224− <img className="w-28" src={article.image} />
225− <div className="flex flex-col space-y-2 min-w-0">
226− <p className="font-bold">{article.publication}</p>
227− <p className="min-w-0 min-h-0 leading-5 ">
230− <p className="text-wall-400">
231− {formatDate(generateDisplayDate(article.date))}
@@ -246,153 +161,136 @@ export default function Ecosystem({
249−function ActiveLink({ children, href, className, currentPath }) {
164+const Organizations = ({ dir }) => {
250− // special casing this behaviour which overloads one nav
251− const archive = Boolean(
252− currentPath === "/ecosystem?type=archive" && href === "/ecosystem"
254− const activeClassName = classnames({
255− "text-wall-800": currentPath === href || archive,
256− "text-wall-400": currentPath !== href && !archive,
259− return (
165+ return dir.map((org) => (
260− <Link href={href} passHref>
166+ <Link href={`/organizations/${org.slug}`}>
261− <a className={`${className} ${activeClassName}`}>{children}</a>
167+ <div className="flex flex-col space-y-4 items-center cursor-pointer hover:opacity-90">
168+ <img className="w-36 rounded-xl" src={org.image} />
169+ <p className="text-center font-bold">{org.title}</p>
266−function EcosystemSidebar() {
175+const Applications = ({ dir }) => {
267− const router = useRouter();
268− let currentPath = router.asPath;
269− return (
176+ return dir.map((app) => (
177+ <Link href={`/applications/${app.ship}/${app.slug}`}>
270− <div className="flex flex-col space-y-4 pr-24">
178+ <div className="flex space-x-4 items-center cursor-pointer hover:opacity-90">
271− <ActiveLink
179+ <div
272− currentPath={currentPath}
180+ className="h-36 w-36 rounded-xl items-center justify-center shrink-0 overflow-hidden"
273− className="type-ui"
181+ style={{
274− href="/ecosystem"
182+ backgroundColor: app?.bgColor || "rgba(0,0,0,0)",
277− </ActiveLink>
185+ {app?.image && (
278− <ActiveLink
186+ <img
279− currentPath={currentPath}
281− href="/ecosystem?type=organizations"
286− currentPath={currentPath}
287− className="type-ui"
187+ className="items-center h-36 w-36"
288− href="/ecosystem?type=applications"
188+ src={app.image}
291− </ActiveLink>
191+ </div>
293− currentPath={currentPath}
294− className="type-ui"
192+ <div className="">
295− href="/ecosystem?type=groups"
193+ <p className="text-left font-bold">{app.title}</p>
296− >
194+ <p className="text-left text-wall-400 font-light">
297− Groups
195+ {app.description}
300− currentPath={currentPath}
302− href="/ecosystem?type=marketplaces"
307− currentPath={currentPath}
308− className="mr-5 type-ui"
309− href="/ecosystem?type=podcasts"
312− </ActiveLink>
198+ </div>
314− <ActiveLink
203+const Groups = ({ dir }) => {
315− currentPath={currentPath}
204+ return dir.map((group) => (
316− className="mr-5 type-ui"
205+ <Link href={`/groups/${group.ship}/${group.slug}`}>
317− href="/ecosystem?type=articles"
206+ <div className="flex space-x-4 items-center cursor-pointer hover:opacity-90">
208+ className="h-36 w-36 rounded-xl items-center justify-center shrink-0 overflow-hidden"
210+ backgroundColor: group?.tile?.startsWith("#")
215+ {!group?.tile?.startsWith("#") && (
217+ className="items-center h-36 w-36"
223+ <p className="text-left font-bold">{group.title}</p>
224+ <p className="text-left text-wall-400 font-light">
320− </ActiveLink>
228+ </div>
233+const Marketplaces = ({ dir }) => {
234+ return dir.map((market) => (
235+ <Link href={`/marketplaces/${market.slug}`}>
236+ <div className="flex flex-col space-y-4 justify-center items-center cursor-pointer hover:opacity-90">
237+ <img className="w-36 rounded-xl" src={market.image} />
238+ <p className="text-center font-bold">{market.title}</p>
244+const Podcasts = ({ dir }) => {
245+ return dir.map((pod) => (
246+ <Link href={`/podcasts/${pod.slug}`}>
247+ <div className="flex cursor-pointer space-x-4 items-center hover:opacity-90">
248+ <img className="w-28" src={pod.image} />
249+ <div className="flex flex-col space-y-2 min-w-0">
250+ <p className="font-bold">{pod.podcast}</p>
251+ <p className="min-w-0 min-h-0 leading-5 ">
254+ <p className="text-wall-400">
255+ {formatDate(generateDisplayDate(pod.date))}
327−function SpotlightArchive({ posts, search }) {
328− const post = {
263+const Articles = ({ dir }) => {
329− title: "Spotlight Archive",
264+ return dir.map((article) => (
330− description: "The archive of ecosystem spotlights.",
265+ <Link href={`/articles/${article.slug}`}>
335− <title>Archive • Spotlight • Urbit</title>
338− <IntraNav ourSite="https://urbit.org" search={search} />
340− <Header search={search} />
342− <div className="measure">
346− <Section className="flex">
348− <EcosystemSidebar />
351− <h3 className="mb-12">Spotlight Archive</h3>
352− {posts.map((post) => {
354− <div key={post.slug} className="mb-24 cursor-pointer">
266+ <div className="flex cursor-pointer space-x-4 items-center hover:opacity-90">
355− <Link href={`/ecosystem/spotlight/${post.slug}`}>
267+ <img className="w-28" src={article.image} />
356− <div>
268+ <div className="flex flex-col space-y-2 min-w-0">
269+ <p className="font-bold">{article.publication}</p>
270+ <p className="min-w-0 min-h-0 leading-5 ">
357− <h3 className="text-green-400">{post.title}</h3>
273+ <p className="text-wall-400">
358− <div className="type-ui mt-2">
274+ {formatDate(generateDisplayDate(article.date))}
359− {["featured-1", "featured-2", "featured-3"].map(
275+ </p>
360− (feat, i) => {
276+ </div>
361− if (post?.[feat]) {
277+ </div>
362− return (
278+ </Link>
365− <span>{post[feat].title}</span>
385−export async function getStaticProps({}) {
283+export async function getStaticProps() {
386 const posts = getAllPosts(
284 const posts = getAllPosts(
387 ["title", "date", "slug", "featured-1", "featured-2", "featured-3"],
285 ["title", "date", "slug", "featured-1", "featured-2", "featured-3"],
388 "ecosystem/spotlight",
286 "ecosystem/spotlight",
391− let post = getAllPosts(
289+ const post = matchEcosystemPost(getAllPosts(
392 ["title", "date", "featured-1", "featured-2", "featured-3"],
290 ["title", "date", "featured-1", "featured-2", "featured-3"],
393 "ecosystem/spotlight",
291 "ecosystem/spotlight",
396 const marketplaces = getAllPosts(["title", "image", "slug"], "marketplaces");
294 const marketplaces = getAllPosts(["title", "image", "slug"], "marketplaces");
397 const podcasts = getAllPosts(
295 const podcasts = getAllPosts(
398 ["title", "image", "date", "podcast", "slug"],
296 ["title", "image", "date", "podcast", "slug"],
@@ -447,27 +345,6 @@ export async function getStaticProps({}) {
447 return nameA < nameB ? -1 : 1;
345 return nameA < nameB ? -1 : 1;
450− ["featured-1", "featured-2", "featured-3"].forEach((feat) => {
452− const matchedPost = [
453− ...applications.map((e) => ({ ...e, type: "Application" })),
454− ...organizations.map((e) => ({ ...e, type: "Organization" })),
455− ...groups.map((e) => ({ ...e, type: "Group" })),
456− ...podcasts.map((e) => ({ ...e, type: "Podcast" })),
457− ...articles.map((e) => ({ ...e, type: "Article" })),
458− ...marketplaces.map((e) => ({ ...e, type: "Marketplace" })),
459− ].filter((e) => e.title === post[feat].title)?.[0];
460− post[feat].image = post[feat]?.image || matchedPost?.image || null;
461− post[feat].type = matchedPost?.type || "Podcast";
462− post[feat].matchedPost = matchedPost || null;
463− post[feat].content = JSON.stringify(
465− post: { content: post[feat].content },