@@ -11,17 +11,21 @@ import {
13 getPostBySlug,
13 getPostBySlug,
14− getAllPosts,
14+ getPage,
15 formatDate,
15 formatDate,
16} from "@urbit/foundation-design-system";
16} from "@urbit/foundation-design-system";
17import Header from "../../components/Header";
17import Header from "../../components/Header";
18import Footer from "../../components/Footer";
18import Footer from "../../components/Footer";
19+import MetadataBlock from "../../components/ecosystem/MetadataBlock";
20+import ResourceCard from "../../components/gateway/ResourceCard";
21+import JoinGroup from "../../components/JoinGroup";
19import ob from "urbit-ob";
22import ob from "urbit-ob";
20import { DateTime } from "luxon";
23import { DateTime } from "luxon";
21import fs from 'fs';
24import fs from 'fs';
22−import { join } from 'path';
25+import path, { join } from 'path';
26+import { getGrantYear } from "../../lib/lib";
24−export default function Grant({ post, markdown, search }) {
28+export default function Grant({ post, markdown, match, search }) {
25 const router = useRouter();
29 const router = useRouter();
26 if (!router.isFallback && !post?.slug) {
30 if (!router.isFallback && !post?.slug) {
27 return <ErrorPage />;
31 return <ErrorPage />;
@@ -65,6 +69,45 @@ export default function Grant({ post, markdown, search }) {
75+ } else if (post?.extra?.completed) {
77+ } else if (post?.extra?.canceled) {
80+ status = "In Progress"
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",
91+ let deliverable = null;
92+ if (post.extra.deliverable) {
94+ deliverable = <ResourceCard
95+ title={match.data.title}
96+ image={match.data.image}
97+ color={match.data.bgColor || "#000000"}
98+ description={match.data?.description || ""}
100+ shortcode={post.extra.deliverable}
104+ else if (post.extra.deliverable.startsWith("~")) {
105+ deliverable = <JoinGroup emphasize groupName={post.extra.deliverable} />
107+ deliverable = <a className="font-medium text-green-400" href={post.extra.deliverable}>{post.extra.deliverable}</a>
68 const metaPost = Object.assign({}, post);
111 const metaPost = Object.assign({}, post);
69 metaPost.title = `${post.title} - Grant`;
112 metaPost.title = `${post.title} - Grant`;
@@ -79,38 +122,8 @@ export default function Grant({ post, markdown, search }) {
79 <Header search={search} />
122 <Header search={search} />
80 <Section narrow short>
123 <Section narrow short>
81 <h1>{post.title}</h1>
124 <h1>{post.title}</h1>
82− {post.extra.assignee ? (
83− <div className="type-ui text-wall-500 mt-4">
84− Grantee(s): {assigneeLink}
87− {post.extra.mentor ? (
88− <div className="type-ui text-wall-500 mt-4">
92− {post.extra.champion ? (
93− <div className="type-ui text-wall-500 mt-4">
94− Champion: {mentorLink}
98− <Link href={`/ids/${post.extra.ship}`} passHref>
99− <a className="type-sub-bold text-wall-500 font-mono">
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 ? (
108− <p className="ml-1 text-wall-500 type-ui">• ID:</p>
109− <p className="font-mono type-ui"> {post.extra.grant_id}</p>
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">
114 {post.taxonomies.grant_type.map((category) => {
127 {post.taxonomies.grant_type.map((category) => {
115 const className = classnames({
128 const className = classnames({
116 "bg-blue-400 text-white": category === "Proposal",
129 "bg-blue-400 text-white": category === "Proposal",
@@ -129,6 +142,46 @@ export default function Grant({ post, markdown, search }) {
145+ <div className="flex flex-wrap md:flex-nowrap justify-between">
146+ {post?.extra?.grant_id ? (
150+ content={post.extra.grant_id}
156+ content={formatDate(DateTime.fromISO(post.date))}
158+ {post.extra.mentor ? (
161+ content={mentorLink}
164+ {post.extra.champion ? (
167+ content={mentorLink}
170+ {post.extra.assignee ? (
173+ content={assigneeLink}
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} />
181+ {deliverable && <div className="flex flex-col my-4 space-y-2">
182+ <p className="text-wall-400 font-semibold">Deliverable</p>
132 </Section>
185 </Section>
133 <Section narrow className="markdown">
186 <Section narrow className="markdown">
134 <Markdown.render content={JSON.parse(markdown)} />
187 <Markdown.render content={JSON.parse(markdown)} />
@@ -149,12 +202,8 @@ export default function Grant({ post, markdown, search }) {
151export async function getStaticProps({ params }) {
204export async function getStaticProps({ params }) {
152− const basePath = join(process.cwd(), 'content/grants')
205+ // Gets post in any year folder sharing the slug name in the param
153− const years = fs.readdirSync(basePath, { 'withFileTypes': true }).filter((f) => f.isDirectory());
206+ const dir = getGrantYear(params.slug);
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`)
158 const post = getPostBySlug(
207 const post = getPostBySlug(
159 params.slug,
208 params.slug,
160 ["title", "slug", "date", "description", "content", "extra", "taxonomies"],
209 ["title", "slug", "date", "description", "content", "extra", "taxonomies"],
@@ -162,12 +211,28 @@ export async function getStaticProps({ params }) {
164 const markdown = JSON.stringify(Markdown.parse({ post }));
213 const markdown = JSON.stringify(Markdown.parse({ post }));
215+ // Matches deliverable to page
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(
224+ "content/applications",
225+ post.extra.deliverable
166− props: { post, markdown },
230+ props: { post, markdown, match },
170export async function getStaticPaths() {
234export async function getStaticPaths() {
235+ // Flatten all year subfolders into a flat array of slugs
171 const basePath = join(process.cwd(), 'content/grants')
236 const basePath = join(process.cwd(), 'content/grants')
172 const years = fs.readdirSync(basePath, { 'withFileTypes': true });
237 const years = fs.readdirSync(basePath, { 'withFileTypes': true });
173 const posts = years.filter((year) => year.isDirectory()).map((year) => {
238 const posts = years.filter((year) => year.isDirectory()).map((year) => {