@@ -11,17 +11,21 @@ import {
1111 Section,
1212 IntraNav,
1313 getPostBySlug,
14− getAllPosts,
14+ getPage,
1515 formatDate,
1616} from "@urbit/foundation-design-system";
1717import Header from "../../components/Header";
1818import Footer from "../../components/Footer";
19+import MetadataBlock from "../../components/ecosystem/MetadataBlock";
20+import ResourceCard from "../../components/gateway/ResourceCard";
21+import JoinGroup from "../../components/JoinGroup";
1922import ob from "urbit-ob";
2023import { DateTime } from "luxon";
2124import fs from 'fs';
22−import { join } from 'path';
25+import path, { join } from 'path';
26+import { getGrantYear } from "../../lib/lib";
2327
24−export default function Grant({ post, markdown, search }) {
28+export default function Grant({ post, markdown, match, search }) {
2529 const router = useRouter();
2630 if (!router.isFallback && !post?.slug) {
2731 return <ErrorPage />;
@@ -65,6 +69,45 @@ export default function Grant({ post, markdown, search }) {
6569 );
6670 });
6771
72+ let status = "";
73+ if (isOpen) {
74+ status = "Open"
75+ } else if (post?.extra?.completed) {
76+ status = "Completed"
77+ } else if (post?.extra?.canceled) {
78+ status = "Canceled"
79+ } else {
80+ status = "In Progress"
81+ }
82+ const statusBadge = <div className={
83+ classnames("badge-sm mr-1 my-1", {
84+ "bg-red": status === "Canceled",
85+ "bg-green-400 text-white": status === "Completed" || status === "Open",
86+ "bg-wall-300": status === "In Progress",
87+ })}>
88+ {status}
89+ </div>;
90+
91+ let deliverable = null;
92+ if (post.extra.deliverable) {
93+ if (match) {
94+ deliverable = <ResourceCard
95+ title={match.data.title}
96+ image={match.data.image}
97+ color={match.data.bgColor || "#000000"}
98+ description={match.data?.description || ""}
99+ type="application"
100+ shortcode={post.extra.deliverable}
101+ full
102+ />
103+ }
104+ else if (post.extra.deliverable.startsWith("~")) {
105+ deliverable = <JoinGroup emphasize groupName={post.extra.deliverable} />
106+ } else {
107+ deliverable = <a className="font-medium text-green-400" href={post.extra.deliverable}>{post.extra.deliverable}</a>
108+ }
109+ }
110+
68111 const metaPost = Object.assign({}, post);
69112 metaPost.title = `${post.title} - Grant`;
70113
@@ -79,38 +122,8 @@ export default function Grant({ post, markdown, search }) {
79122 <Header search={search} />
80123 <Section narrow short>
81124 <h1>{post.title}</h1>
82− {post.extra.assignee ? (
83− <div className="type-ui text-wall-500 mt-4">
84− Grantee(s): {assigneeLink}
85− </div>
86− ) : null}
87− {post.extra.mentor ? (
88− <div className="type-ui text-wall-500 mt-4">
89− Mentor: {mentorLink}
90− </div>
91− ) : null}
92− {post.extra.champion ? (
93− <div className="type-ui text-wall-500 mt-4">
94− Champion: {mentorLink}
95− </div>
96− ) : null}
97− {post.extra.ship ? (
98− <Link href={`/ids/${post.extra.ship}`} passHref>
99− <a className="type-sub-bold text-wall-500 font-mono">
100− {post.extra.ship}
101− </a>
102− </Link>
103− ) : null}
104− <div className="type-ui text-wall-500 mt-4 md:mt-8 lg:mt-10 flex items-center space-x-1">
105− {formatDate(DateTime.fromISO(post.date))}
106− {post?.extra?.grant_id ? (
107− <>
108− <p className="ml-1 text-wall-500 type-ui">• ID:</p>
109− <p className="font-mono type-ui"> {post.extra.grant_id}</p>
110− </>
111− ) : null}
112− </div>
113− <div className="flex items-center flex-wrap mt-4 md:mt-8 lg:mt-10">
125+ <div className="flex items-center flex-wrap my-4">
126+ {statusBadge}
114127 {post.taxonomies.grant_type.map((category) => {
115128 const className = classnames({
116129 "bg-blue-400 text-white": category === "Proposal",
@@ -129,6 +142,46 @@ export default function Grant({ post, markdown, search }) {
129142 </div>
130143 ))}
131144 </div>
145+ <div className="flex flex-wrap md:flex-nowrap justify-between">
146+ {post?.extra?.grant_id ? (
147+ <>
148+ <MetadataBlock
149+ title="ID"
150+ content={post.extra.grant_id}
151+ />
152+ </>
153+ ) : null}
154+ <MetadataBlock
155+ title="Date"
156+ content={formatDate(DateTime.fromISO(post.date))}
157+ />
158+ {post.extra.mentor ? (
159+ <MetadataBlock
160+ title="Mentor"
161+ content={mentorLink}
162+ />
163+ ) : null}
164+ {post.extra.champion ? (
165+ <MetadataBlock
166+ title="Champion"
167+ content={mentorLink}
168+ />
169+ ) : null}
170+ {post.extra.assignee ? (
171+ <MetadataBlock
172+ title="Grantee(s)"
173+ content={assigneeLink}
174+ />
175+ ) : null}
176+ </div>
177+ {post?.extra?.link?.startsWith("~") && <div className="flex flex-col my-4 space-y-2">
178+ <p className="text-wall-400 font-semibold">Group</p>
179+ <JoinGroup emphasize groupName={post.extra.link} />
180+ </div>}
181+ {deliverable && <div className="flex flex-col my-4 space-y-2">
182+ <p className="text-wall-400 font-semibold">Deliverable</p>
183+ {deliverable}
184+ </div>}
132185 </Section>
133186 <Section narrow className="markdown">
134187 <Markdown.render content={JSON.parse(markdown)} />
@@ -149,12 +202,8 @@ export default function Grant({ post, markdown, search }) {
149202}
150203
151204export async function getStaticProps({ params }) {
152− const basePath = join(process.cwd(), 'content/grants')
153− const years = fs.readdirSync(basePath, { 'withFileTypes': true }).filter((f) => f.isDirectory());
154− const dir = years.find((year) => {
155− const yearDir = fs.readdirSync(join(basePath, year.name), { 'withFileTypes': true });
156− return yearDir.some((file) => file.name === `${params.slug}.md`)
157− });
205+ // Gets post in any year folder sharing the slug name in the param
206+ const dir = getGrantYear(params.slug);
158207 const post = getPostBySlug(
159208 params.slug,
160209 ["title", "slug", "date", "description", "content", "extra", "taxonomies"],
@@ -162,12 +211,28 @@ export async function getStaticProps({ params }) {
162211 )
163212
164213 const markdown = JSON.stringify(Markdown.parse({ post }));
214+
215+ // Matches deliverable to page
216+ let match = null;
217+ if (post?.extra?.deliverable?.startsWith("~")) {
218+ const ship = post.extra.deliverable.split("/")[0];
219+ const application = post.extra.deliverable.split("/")[1];
220+ const fileExists = fs.existsSync(path.join(process.cwd(), "content/applications", ship, `${application}.md`));
221+ match = fileExists ? getPage(
222+ join(
223+ process.cwd(),
224+ "content/applications",
225+ post.extra.deliverable
226+ )
227+ ) : null
228+ }
165229 return {
166− props: { post, markdown },
230+ props: { post, markdown, match },
167231 };
168232}
169233
170234export async function getStaticPaths() {
235+ // Flatten all year subfolders into a flat array of slugs
171236 const basePath = join(process.cwd(), 'content/grants')
172237 const years = fs.readdirSync(basePath, { 'withFileTypes': true });
173238 const posts = years.filter((year) => year.isDirectory()).map((year) => {