on master help sign in
grants: add RFP type and initial content, show in-progress grants (#2056)

* change filtering logic to accomodate 'request' grant_type * update copy and links on main grants page * add overview page for requests * add pastebin rfp * add 5 initial RFPs * updated copy in response to feedback * adding back in apprenticeships and restyling requests * added apprenticeship buttons back in * updated foujndation design system * make default status filter open + wip * Update grants.js try out some changes * Update grants.js * Update grants.js * Update grants.js * Update grants.js * Update grants.js * Update grants.js * changed it properly * add RFP type to grants --------- Co-authored-by: ~poldec-tonteg <anthony@urbit.org> Co-authored-by: thomasurbit <99678438+thomasurbit@users.noreply.github.com>

commit0411ea4fb1baeaee9d02d7374f2e7eeefb2cbcdf authorJosh Lehman <josh@urbit.org> dateWed 22 Mar 2023, 16:27 (4 years ago) parent4c90121
19 files changed+363−112
components/GrantPreview.js+2−1 content/grants/2022/chess-bounty.md+1−1 content/grants/2022/digital-storefront.md+2−2 content/grants/2023/network-weather.md+24 content/grants/2023/pastebin.md+24 content/grants/2023/text-search.md+24 content/grants/2023/tinygrad.md+24 content/grants/2023/treesitter.md+24 content/grants/2023/update-mass.md+26 content/privacy.md+1−1 content/rfps.md+40 content/updates/2018-7-30-update.md+1−1 content/updates/2019-2-11-update.md+1−1 package-lock.json+1−1 package.json+1−1 pages/grants.js+70−52 pages/grants/[slug].js+67−50 pages/grants/apprenticeships.js+1−1 pages/grants/rfps.js+29
components/GrantPreview.js+2−1
@@ -8,7 +8,7 @@ export default function GrantPreview({ grant }) {
88
99 const className = classnames({
1010 "bg-lightBlue": type.includes("Proposal") && isOpen,
11− "bg-green-200": type.includes("Apprenticeship") && isOpen,
11+ "bg-purple-200": type.includes("RFP") && isOpen,
1212 "bg-yellow-100": type.includes("Bounty") && isOpen,
1313 });
1414
@@ -38,6 +38,7 @@ export default function GrantPreview({ grant }) {
3838 {grant.taxonomies.grant_type.map((category) => {
3939 const className = classnames({
4040 "bg-blue-400 text-white": category === "Proposal",
41+ "bg-purple text-white": category === "RFP",
4142 "bg-green-400 text-white": category === "Apprenticeship",
4243 "bg-yellow-300": category === "Bounty",
4344 });
content/grants/2022/chess-bounty.md+1−1
@@ -20,7 +20,7 @@ work_request_link = "https://airtable.com/shr4qt9t9kz7RaOIa?prefill_Grant+ID=B01
2020+++
2121
2222# Rationale
23−~finmep-lanteb is sponsoring the improvement of the Chess app to create an Urbit Chess experience comparable to that of any modern Chess website. The current Chess app meets the basic requirements to play a game with a friend, but lacks various quality of life features: move notifications, undo requests, %pals integration, etc. In addition, only unrated, correspondence games are currently possible. These missing features & improvements offer a great opportunity for a team of 3 novice Urbit developers (preferably three Hoon School Live and App School Live graduates) to work together to complete this bounty, each earning a star in the process.
23+[Omitted long matching line]
2424
2525The goals of this bounty are to resolve the quality of life issues present in the Chess app, as well as enable timed and rated games.
2626
content/grants/2022/digital-storefront.md+2−2
@@ -63,9 +63,9 @@ Shop:
6363 ==
6464```
6565## User Stories
66−- John Murphy is a social media influencer who wants to sell his jazz album on the web. He looked at selling it using Shopify, but was turned off at the idea that he'd be dependent on Shopify and wouldn't be able to export his data if he ever wanted to move to a different webstore provider. John wants an online store solution that allows him to run the webstore himself on the server at his house without having to fiddle with it. John decides to download Tirrel Studio, because he heard they just added online store functionality. He sets up his website, adds his product details, decides how much it should cost, and then requests to be a merchant through their payment processor. John feels good knowing that he owns all of his shop's data and software, and that his only external dependency is on a payment processor that he could switch out for a different one.
66+[Omitted long matching line]
6767
68−- Timothy Gunn is an online conspiracy theorist who writes articles about the new world order. He has an avid online following, but sometimes his posts get too juicy for conventional platforms. He doesn't trust Substack and wants to host his newsletter himself but still get paid. His audience is a little too normie to use Bitcoin, and Bitcoin isn't that convenient for payments these days anyway. Timothy sets up his newsletter site via Studio, imports his mailing list from his old Mailchimp newsletter, requests to be a merchant, and then adds pay-walls in front of his best posts. Now he's ready to make money on his own terms.
68+[Omitted long matching line]
6969
7070- Jimmy Takeria is a big calisthenics guy who teaches other guys how to get jacked. He makes informational videos and posts them to YouTube as well as running a Discord community about calisthenics. Jimmy doesn't like YouTube and Discord's new misinformation policies because he has unconventional ideas about nutrition. Jimmy decides to move his community off of Discord and into Groups on Urbit, and sets up a Studio webstore where he sells access to his latest workout videos and cutting edge nutritional info on a subscription basis. He's satisfied that he can manage his entire online business using Urbit.
7171
content/grants/2023/network-weather.mdnew file+24
@@ -0,0 +1,24 @@
1++++
2+
3+title = "Network Weather Simulator for %pyro/%aqua"
4+date = "2023-03-02"
5+
6+[taxonomies]
7+grant_type = ["RFP"]
8+grant_category = ["Core Dev"]
9+
10+[extra]
11+image = ""
12+description = "Network Weather Simulator for `%pyro`/`%aqua"
13+reward = "TBD"
14+assignee = [""]
15+champion = [""]
16+grant_id = "P1004"
17+completed = false
18+canceled = false
19++++
20+
21+`%pyro` and `%aqua` are virtualization tools that allow developers to run one or many virtual ships within their ship. This opens up many testing possibilities, since it's possible to spin up an entire fleet of virtual ships with state and orchestrate them within your ship. But since all of hte ships run one one computer, the network conditions are unrealistically good. In the real world, network connections fail, time out and generally perform in unexpected ways.
22+
23+Write a library that allows developers to easily add realistic "network weather" to fleet testing in `%pyro`/`%aqua`
24+
content/grants/2023/pastebin.mdnew file+24
@@ -0,0 +1,24 @@
1++++
2+
3+title = "Pastebin on Urbit"
4+date = "2023-03-02"
5+
6+[taxonomies]
7+grant_type = ["RFP"]
8+grant_category = ["App Dev"]
9+
10+[extra]
11+image = ""
12+description = "Share plaintext snippets on Urbit"
13+reward = "TBD"
14+assignee = [""]
15+champion = [""]
16+grant_id = "P1000"
17+completed = false
18+canceled = false
19+
20++++
21+
22+Tools like pastebin and Github gists allow users to easily share plain text snippets. This is useful for sharing code or simple writeups.
23+
24+Implement minimal plaintext snippet sharing for Urbit, with support for both public (anyone with the path can read the snippet) and private (only invited individuals can read snippets).
content/grants/2023/text-search.mdnew file+24
@@ -0,0 +1,24 @@
1++++
2+
3+title = "Generic Text Search Tool for Urbit"
4+date = "2023-03-02"
5+
6+[taxonomies]
7+grant_type = ["RFP"]
8+grant_category = ["Core Dev"]
9+
10+[extra]
11+image = ""
12+description = "Build Urbit's answer to grep."
13+reward = "TBD"
14+assignee = [""]
15+champion = [""]
16+grant_id = "P1002"
17+completed = false
18+canceled = false
19+
20++++
21+
22+Text search is a key part of many different use cases, but Urbit currently has no equivalent to `grep`, the general purpose GNU search tool.
23+
24+Build Urbit's answer to `grep`.
content/grants/2023/tinygrad.mdnew file+24
@@ -0,0 +1,24 @@
1++++
2+
3+title = "Tinygrad ML Library in Hoon"
4+date = "2023-03-02"
5+
6+[taxonomies]
7+grant_type = ["RFP"]
8+grant_category = ["Core Dev"]
9+
10+[extra]
11+image = ""
12+description = "Implement a small machine learning library (and jets!) in hoon."
13+reward = "TBD"
14+assignee = [""]
15+champion = [""]
16+grant_id = "P1003"
17+completed = false
18+canceled = false
19+
20++++
21+
22+Geohot has published the [Tinygrad library](https://github.com/geohot/tinygrad) for doing basic machine learning. This library is small and simple which makes it a potentially good target for jetting. Doing so would give basic ML functionality in Urbit.
23+
24+Implement Tinygrad (with jets) in Urbit
content/grants/2023/treesitter.mdnew file+24
@@ -0,0 +1,24 @@
1++++
2+
3+title = "Treesitter Hoon Parser"
4+date = "2023-03-02"
5+
6+[taxonomies]
7+grant_type = ["RFP"]
8+grant_category = ["Core Dev"]
9+
10+[extra]
11+image = ""
12+description = "Create a better hoon parser with Treesitter"
13+reward = "TBD"
14+assignee = [""]
15+champion = [""]
16+grant_id = "P1001"
17+completed = false
18+canceled = false
19+
20++++
21+
22+[Tree-sitter](https://github.com/tree-sitter/tree-sitter) represents the state of the art for multi-language parsing. It is used by Atom and Github to parse any language, on every keystroke, in an editor.
23+
24+Use tree-sitter to write a superior hoon parser.
content/grants/2023/update-mass.mdnew file+26
@@ -0,0 +1,26 @@
1++++
2+
3+title = "Modernize |mass"
4+date = "2023-03-02"
5+
6+[taxonomies]
7+grant_type = ["RFP"]
8+grant_category = ["Core Dev"]
9+
10+[extra]
11+image = ""
12+description = "Update the |mass utility to reinject the output into arvo from the runtime."
13+reward = "TBD"
14+assignee = [""]
15+champion = [""]
16+grant_id = "P1001"
17+completed = false
18+canceled = false
19+
20++++
21+
22+`|mass` is a utility for measuring a pier's resource usage. It is used for debugging and development. Currently, the output of this utility is a mixture of data from within arvo and from the runtime. This heterogeneity means that what is produced is not a noun but interleaved print statements to the terminal.
23+
24+Once `|mass` produces a noun, it can be sent to other ships over `%ames` for bug reports, collected in a central location or analyzed for further action by arvo.
25+
26+Update the `|mass` utility to reinject the output into arvo from the runtime.
content/privacy.md+1−1
@@ -76,7 +76,7 @@ We do not knowingly collect, maintain, or use personal information from children
7676
7777### International visitors
7878
79−The Services are hosted in the United States and are intended for visitors located within the United States. If you choose to use the Services from the European Union or other regions of the world with laws governing data collection and use that may differ from U.S. law, then please note that you are transferring your personal information outside of those regions to the United States for storage and processing. Also, we may transfer your data from the U.S. to other countries or regions in connection with storage and processing of data, fulfilling your requests, and operating the Services. By providing any information, including personal information, on or to the Services, you consent to such transfer, storage, and processing.
79+[Omitted long matching line]
8080
8181### Changes to this Privacy Policy
8282
content/rfps.mdnew file+40
@@ -0,0 +1,40 @@
1++++
2+title = "Requests for Proposals"
3+date = "2023-03-02"
4+weight = 1
5+description = "Everything you need to know about fulfilling a request for proposal or RFP"
6++++
7+
8+The imagination of people building on Urbit often outpaces the available development hours. For this case, we have Requests for Proposals (RFPs).
9+
10+These are ideas that the community would like to see come to fruition, but whose details haven't been completely ironed out. Since they're not fully fleshed out, they're a great opportunity for contributors to add their own ideas and creative approaches, while learning about developing and shipping on Urbit.
11+
12+If any of these RFPs sound compelling, we encourage you to come by the [Urbit Grants office hours](https://app.gather.town/app/xAYeiPI2XDYhRM9t/urbit-hacker-house?spawnToken=ufV1APwWTGmYVTgzyx0z) or DM [~poldec-tonteg](https://urbit.org/ids/~poldec-tonteg) to discuss drafting and submitting a proposal.
13+
14+# Jump in
15+
16+The Hacker House is a UF-hosted gather.town instance where contributors hang out, pair program and discuss all things Urbit. At the Office Hours, members from the UF team can help refine the Request idea and give you context about what needs to be built, potential use cases and architectural precedents in the codebase.
17+
18+Once you've got your mind around this, the next step is to draft the Proposal
19+
20+# Drafting a proposal
21+
22+All of the best practices outlined on the [Proposals](https://urbit.org/grants/proposals) section of the website apply equally to Proposals that respond to a Request.
23+
24+Good proposals all include the following:
25+
26+- A **detailed and clear description of the proposal**. If you're proposing something technical, user stories are a good idea.
27+- An overview of **why you are the right person for the job**. A description of your background, familiarity with the project, and professional/education experience are all good starts.
28+- Your estimate for **date of completion**.
29+- The **amount of funding** you'd like for the project, denominated in stars.
30+- What **specific deliverables** will look like.
31+
32+It's recommended to break your project into _milestones_, each of which must have its own completion dates, funding amounts and deliverables. In general, proposals should target a first deliverable within two months of the start of the project. Proposals should have a maximum of five milestones as scoping a project beyond that is impractical, and each milestone should constitute significant enough work to warrant the reward of a full star.
33+
34+Feel free to come to multiple office hours or connect on network to develop the proposal further. Once it's in a good state, submit it through [this form](https://airtable.com/shrCi54rEDxgSZr3z). Submitted proposals are then reviewed internall twice a week by the full grants team.
35+
36+The main distinction between a self-originating Proposal and one that responds to a Request is that the latter doesn't need to go through the further step of getting a champion, as the originator of the Request will fulfill that role.
37+
38+# Getting paid
39+
40+A good proposal will have clear success criteria for each milestone. At the completion of each milestone, submit an invoice to the UF for that milestones star compensation and your champion will have the opportunity to review work.
content/updates/2018-7-30-update.md+1−1
@@ -53,7 +53,7 @@ latest backend Landscape API's that _Isaac_ `~davtyr-nimren` has been improving.
5353Urbit is becoming more usable by the day.
5454
5555Last but not least, the Documentation team continued doing `~zod`'s work helping
56−teach Urbit to the world. _Josh_ `~taglux-nidsep` extended his new Hoon docs to cover Hoon cores, an introduction to the Hoon standard library, and a couple of Hoon examples with very detailed walkthrough commentary (drafts [here](https://github.com/joshuareagan/doc-drafts)). And _Robert_ `~lodleb-ritrul` extended the current Hoon-standard-library documentation on random atom generation and atom scrambling, as well as the Hoon cores for computing modular and signed-integer arithmetic (`urbit/docs` pull requests [here](https://github.com/urbit/docs/pulls)). Very soon you'll actually be able to understand how Urbit works.
56+[Omitted long matching line]
5757
5858Also: we'll be hosting another party this month on Friday, August 17th. Save the date! Link: [meetup.com: Urbit IRL ~2018.8](https://www.meetup.com/urbit-sf/events/253292678/)
5959
content/updates/2019-2-11-update.md+1−1
@@ -26,7 +26,7 @@ To stay up to date on network status, follow [@zodisok](https://twitter.com/zodi
2626
2727## Team
2828
29−_Ted_ `~rovnys-ricfer` and _Joe_ `~master-morzod` got an `%ames` rewrite to compile and are working on documentation and unit tests. _Elliot_ `~littel-ponys` is rewriting `%eyre`, our webserver, which is some of the gnarliest code in Arvo. Outbound web requests are now working, which has allowed us to delete old `%eyre` from the [lighter-than-eyre branch](https://github.com/urbit/arvo/tree/lighter-than-eyre), and work continues porting individual `%gall` applications over to `%light`. We’re looking forward to moving Landscape onto new `%eyre` soon. _Logan_ `~tacryt-socryp` is rewriting the HTTP client in Vere (Arvo’s interpreter) to better support HTTP/2. This will allow us to provide push notifications in the heretofore rumored Landscape iOS app. Finally, _Gavin_ `ridlur-figbud` and _Jimmy_ `fallyn-balfus` are working on a new design system to enable fast UI prototyping. We’re very excited to share more about that in the near future!
29+[Omitted long matching line]
3030
3131Shout-out to _Philip_ `wicdev-wisryt` for [this heroic weekend PR](https://github.com/urbit/arvo/pull/1072) that fixed persistent connectivity issues on the network after [Thursday’s breach](https://twitter.com/zodisok/status/1096225302210957312).
3232
package-lock.json+1−1
@@ -12,7 +12,7 @@
1212 "@socialgouv/matomo-next": "^1.3.0",
1313 "@tailwindcss/aspect-ratio": "^0.4.0",
1414 "@tlon/sigil-js": "^1.4.5",
15− "@urbit/foundation-design-system": "0.7.1",
15+ "@urbit/foundation-design-system": "0.7.3",
1616 "@urbit/markdoc": "^0.1.4",
1717 "axios": "^0.26.1",
1818 "buffer": "^6.0.3",
package.json+1−1
@@ -20,7 +20,7 @@
2020 "@socialgouv/matomo-next": "^1.3.0",
2121 "@tailwindcss/aspect-ratio": "^0.4.0",
2222 "@tlon/sigil-js": "^1.4.5",
23− "@urbit/foundation-design-system": "0.7.1",
23+ "@urbit/foundation-design-system": "0.7.3",
2424 "@urbit/markdoc": "^0.1.4",
2525 "axios": "^0.26.1",
2626 "buffer": "^6.0.3",
pages/grants.js+70−52
@@ -33,7 +33,7 @@ export default function Grants({ posts, categories, types, search }) {
3333 const router = useRouter();
3434 let { status, type, category } = router.query;
3535 if (status === undefined) {
36− status = "open";
36+ status = ["open", "wip"];
3737 }
3838
3939 function push(toQuery) {
@@ -82,8 +82,8 @@ export default function Grants({ posts, categories, types, search }) {
8282 const byStatus = (post) => {
8383 return (
8484 (isOrIsIn("open", status) ? post.status === "open" : false) ||
85− (isOrIsIn("complete", status) ? post.status === "completed" : false) ||
86− (isOrIsIn("wip", status) ? post.status === "wip" : false)
85+ (isOrIsIn("complete", status) ? post.status === "completed" : false) ||
86+ (isOrIsIn("wip", status) ? post.status === "wip" : false)
8787 );
8888 };
8989
@@ -91,10 +91,10 @@ export default function Grants({ posts, categories, types, search }) {
9191
9292 const filteredPosts = postsByStatus.filter((post) => {
9393 const hasCategory = category
94− ? isArray(category)
95− ? post.taxonomies.grant_category.some((cat) => category.includes(cat))
96− : post.taxonomies.grant_category.includes(category)
97− : true;
94+ ? isArray(category)
95+ ? post.taxonomies.grant_category.some((cat) => category.includes(cat))
96+ : post.taxonomies.grant_category.includes(category)
97+ : true;
9898
9999 const notCanceled = !post.extra.canceled;
100100 const hasType = type ? post.taxonomies.grant_type.includes(type) : true;
@@ -107,6 +107,9 @@ export default function Grants({ posts, categories, types, search }) {
107107 Bounty: postsByStatus.filter((post) =>
108108 post.taxonomies.grant_type.includes("Bounty")
109109 ).length,
110+ RFP: postsByStatus.filter((post) =>
111+ post.taxonomies.grant_type.includes("RFP")
112+ ).length,
110113 Apprenticeship: postsByStatus.filter((post) =>
111114 post.taxonomies.grant_type.includes("Apprenticeship")
112115 ).length,
@@ -122,7 +125,7 @@ export default function Grants({ posts, categories, types, search }) {
122125 {Meta({
123126 title: "Grants",
124127 description:
125− "Contribute to the Urbit project while earning address space.",
128+ "Contribute to the Urbit project while earning address space.",
126129 })}
127130 </Head>
128131 <IntraNav ourSite="https://urbit.org" search={search} />
@@ -157,11 +160,18 @@ export default function Grants({ posts, categories, types, search }) {
157160 fund all kinds projects, not strictly technical ones, so don't
158161 hesitate to pitch your idea!
159162 </p>
160− <Link href="/grants/proposals" passHref>
161− <a className="button-sm bg-blue-400 text-white mb-8 max-w-fit">
162− Submit a Proposal
163− </a>
164− </Link>
163+ <div className="flex flex-wrap mb-8">
164+ <Link href="/grants/proposals" passHref>
165+ <a className="button-sm bg-blue-400 text-white mb-8 max-w-fit mr-2">
166+ Submit a Proposal
167+ </a>
168+ </Link>
169+ <Link href="/grants?program=rfps&type=RFP#view-grants" passHref>
170+ <a className="button-sm bg-purple text-white mb-8 max-w-fit">
171+ Get Ideas
172+ </a>
173+ </Link>
174+ </div>
165175
166176 {/* Apprenticeships */}
167177 <h3>Apprenticeships</h3>
@@ -197,15 +207,7 @@ export default function Grants({ posts, categories, types, search }) {
197207 </a>
198208 </Link>
199209 </div>
200−
201−
202− {/* Approval */}
203− <h3>Approval</h3>
204− <p className="mb-4">
205− Grant applications and proposals are approved on a bi-weekly basis. The next deadlines are <b>March 14th & 28th</b>.
206− </p>
207− <p>If you any questions regarding grants, feel free to email grants@urbit.org.</p>
208− </div>
210+ </div>
209211 </div>
210212 </div>
211213 </Section>
@@ -235,6 +237,12 @@ export default function Grants({ posts, categories, types, search }) {
235237 by those that post them.
236238 </p>
237239 </li>
240+ <li className="mb-2">
241+ <p>
242+ <Link href="/grants/rfps">RFPs</Link>{" "}
243+ are suitable for people experienced with hoon development but who need some ideas on what to work on.
244+ </p>
245+ </li>
238246 <li className="mb-2">
239247 <p>
240248 <Link href="/grants/apprenticeships">Apprenticeships</Link>{" "}
@@ -248,9 +256,16 @@ export default function Grants({ posts, categories, types, search }) {
248256 <div className="md:basis-1/2">
249257 <p className="mb-8">
250258 Whether you're gearing up to submit your proposal or just
251− thinking about it, the best way to get started is to join the <Link href="/groups/~halbex-palheb/uf-public">Urbit Foundation</Link> group.
259+ thinking about it, the best way to get started is to join the <Link href="/groups/~halbex-palheb/uf-public">Urbit Foundation</Link> group, ask questions there or come find our grants office hours in the group.
260+ </p>
261+ <p className="mb-8">
262+ Feel free to contact <a href="/ids/~poldec-tonteg">~poldec-tonteg</a> with general questions or <a href="/ids/~marfun-pacpet">~marfun-pacpet</a> regarding administrative matters.
263+ </p>
264+ <p className="mb-8">
265+ <h4>Approval</h4>
266+ </p>
267+ <p className="mb-8">Grant applications and proposals are approved on a bi-weekly basis. The next deadlines are <b>March 14th & 28th</b>.
252268 </p>
253− <p className="mb-8">Feel free to contact <a href="/ids/~poldec-tonteg">~poldec-tonteg</a> with general questions or <a href="/ids/~marfun-pacpet">~marfun-pacpet</a> regarding administrative matters.</p>
254269 </div>
255270 </div>
256271 </Section>
@@ -272,17 +287,20 @@ export default function Grants({ posts, categories, types, search }) {
272287 {types.map((each) => {
273288 let activeBg;
274289 switch (each) {
275− case "Proposal":
276− activeBg = "bg-blue-400 text-white";
277− break;
278− case "Bounty":
279− activeBg = "bg-yellow-300 text-white";
280− break;
281− case "Apprenticeship":
282− activeBg = "bg-green-400 text-white";
283− break;
284− default:
285− activeBg = "bg-black";
290+ case "Proposal":
291+ activeBg = "bg-blue-400 text-white";
292+ break;
293+ case "Bounty":
294+ activeBg = "bg-yellow-300 text-white";
295+ break;
296+ case "RFP":
297+ activeBg = "bg-purple-400 text-white";
298+ break;
299+ case "Apprenticeship":
300+ activeBg = "bg-green-400 text-white";
301+ break;
302+ default:
303+ activeBg = "bg-black";
286304 }
287305 return (
288306 <a
@@ -302,8 +320,8 @@ export default function Grants({ posts, categories, types, search }) {
302320 <div className="flex flex-wrap mb-12">
303321 {categories.map((each) => {
304322 const active = isOrIsIn(each, category)
305− ? "bg-green-400 text-white"
306− : "bg-wall-100 text-wall-500";
323+ ? "bg-green-400 text-white"
324+ : "bg-wall-100 text-wall-500";
307325 const categoryQuery = pushOrDropQuery("category", category, each);
308326 return (
309327 <button
@@ -319,20 +337,20 @@ export default function Grants({ posts, categories, types, search }) {
319337 {["open", "complete", "wip"].map((each) => {
320338 let title;
321339 switch (each) {
322− case "open":
323− title = isOrIsIn(each, status)
324− ? "Exclude Open"
325− : "Include Open";
326− break;
327− case "complete":
328− title = isOrIsIn(each, status)
329− ? "Exclude Completed"
330− : "Include Completed";
331− break;
332− case "wip":
333− title = isOrIsIn(each, status)
334− ? "Exclude In Progress"
335− : "Include In Progress";
340+ case "open":
341+ title = isOrIsIn(each, status)
342+ ? "Exclude Open"
343+ : "Include Open";
344+ break;
345+ case "complete":
346+ title = isOrIsIn(each, status)
347+ ? "Exclude Completed"
348+ : "Include Completed";
349+ break;
350+ case "wip":
351+ title = isOrIsIn(each, status)
352+ ? "Exclude In Progress"
353+ : "Include In Progress";
336354 }
337355 const statusQuery = pushOrDropQuery("status", status, each);
338356 return (
@@ -371,7 +389,7 @@ export async function getStaticProps() {
371389 "date"
372390 )
373391 }).flat()
374− .sort((a, b) => new Date(b.date) - new Date(a.date));
392+ .sort((a, b) => new Date(b.date) - new Date(a.date));
375393
376394 return {
377395 props: {
pages/grants/[slug].js+67−50
@@ -35,39 +35,39 @@ export default function Grant({ post, markdown, match, search }) {
3535 const assignee = post?.extra?.assignee;
3636 const mentor = post.extra.mentor || post.extra.champion;
3737 const assigneeLink =
38− assignee &&
39− assignee.map((each, index) => {
40− return ob.isValidPatp(each) ? (
41− <>
42− {index ? ", " : ""}
43− <Link href={`/ids/${each}`} passHref>
44− <a className="text-green-400">{each}</a>
45− </Link>
46− </>
47− ) : (
48− <>
49− {index ? ", " : ""}
50− {each}
51− </>
52− );
53− });
38+ assignee &&
39+ assignee.map((each, index) => {
40+ return ob.isValidPatp(each) ? (
41+ <>
42+ {index ? ", " : ""}
43+ <Link href={`/ids/${each}`} passHref>
44+ <a className="text-green-400">{each}</a>
45+ </Link>
46+ </>
47+ ) : (
48+ <>
49+ {index ? ", " : ""}
50+ {each}
51+ </>
52+ );
53+ });
5454 const mentorLink =
55− mentor &&
56− mentor.map((each, index) => {
57− return ob.isValidPatp(each) ? (
58− <>
59− {index ? ", " : ""}
60− <Link href={`/ids/${each}`} passHref>
61− <a className="text-green-400">{each}</a>
62− </Link>
63− </>
64− ) : (
65− <>
66− {index ? ", " : ""}
67− {each}
68− </>
69− );
70− });
55+ mentor &&
56+ mentor.map((each, index) => {
57+ return ob.isValidPatp(each) ? (
58+ <>
59+ {index ? ", " : ""}
60+ <Link href={`/ids/${each}`} passHref>
61+ <a className="text-green-400">{each}</a>
62+ </Link>
63+ </>
64+ ) : (
65+ <>
66+ {index ? ", " : ""}
67+ {each}
68+ </>
69+ );
70+ });
7171
7272 let status = "";
7373 if (isOpen) {
@@ -85,21 +85,21 @@ export default function Grant({ post, markdown, match, search }) {
8585 "bg-green-400 text-white": status === "Completed" || status === "Open",
8686 "bg-wall-300": status === "In Progress",
8787 })}>
88− {status}
89− </div>;
88+ {status}
89+ </div>;
9090
9191 let deliverable = null;
9292 if (post.extra.deliverable) {
9393 if (match) {
9494 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− />
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+/>
103103 }
104104 else if (post.extra.deliverable.startsWith("~")) {
105105 deliverable = <JoinGroup emphasize groupName={post.extra.deliverable} />
@@ -111,6 +111,13 @@ export default function Grant({ post, markdown, match, search }) {
111111 const metaPost = Object.assign({}, post);
112112 metaPost.title = `${post.title} - Grant`;
113113
114+ const isRFP = post.taxonomies.grant_type.filter((category) => {
115+ return category === "RFP";
116+ }).length > 0;
117+
118+ const hasChampion = post.extra.champion && post.extra.champion[0] !== "";
119+ const hasAssignee = post.extra.assignee && post.extra.assignee[0] !== "";
120+
114121 return (
115122 <Container>
116123 <Head>
@@ -126,6 +133,7 @@ export default function Grant({ post, markdown, match, search }) {
126133 {statusBadge}
127134 {post.taxonomies.grant_type.map((category) => {
128135 const className = classnames({
136+ "bg-purple-400 text-white": category === "RFP",
129137 "bg-blue-400 text-white": category === "Proposal",
130138 "bg-green-400 text-white": category === "Apprenticeship",
131139 "bg-yellow-300": category === "Bounty",
@@ -161,13 +169,13 @@ export default function Grant({ post, markdown, match, search }) {
161169 content={mentorLink}
162170 />
163171 ) : null}
164− {post.extra.champion ? (
172+ {hasChampion ? (
165173 <MetadataBlock
166174 title="Champion"
167175 content={mentorLink}
168176 />
169177 ) : null}
170− {post.extra.assignee ? (
178+ {hasAssignee ? (
171179 <MetadataBlock
172180 title="Grantee(s)"
173181 content={assigneeLink}
@@ -175,17 +183,26 @@ export default function Grant({ post, markdown, match, search }) {
175183 ) : null}
176184 </div>
177185 {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>}
186+ <p className="text-wall-400 font-semibold">Group</p>
187+ <JoinGroup emphasize groupName={post.extra.link} />
188+ </div>}
181189 {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>}
190+ <p className="text-wall-400 font-semibold">Deliverable</p>
191+ {deliverable}
192+ </div>}
185193 </Section>
186194 <Section narrow className="markdown">
187195 <Markdown.render content={JSON.parse(markdown)} />
188196 </Section>
197+ {isRFP && (
198+ <a
199+ className="bg-green-400 text-white button-lg"
200+ href="/grants/rfps"
201+ target="_blank"
202+ >
203+ Learn More
204+ </a>
205+ )}
189206 {canApply && (
190207 <a
191208 className="bg-green-400 text-white button-lg"
pages/grants/apprenticeships.js+1−1
@@ -8,7 +8,7 @@ export default function Post({ post, markdown, search }) {
88 post={post}
99 markdown={markdown}
1010 search={search}
11− actionText="View Apprenticeships"
11+ actionText="View Requests"
1212 actionLink="/grants?program=apprenticeship&completed=true#view-grants"
1313 />
1414 );
pages/grants/rfps.jsnew file+29
@@ -0,0 +1,29 @@
1+import GrantProgramOverview from "../../components/GrantProgramOverview";
2+import { Markdown, getPostBySlug } from "@urbit/foundation-design-system";
3+
4+export default function Post({ post, markdown, search }) {
5+ return (
6+ <GrantProgramOverview
7+ program="rfps"
8+ post={post}
9+ markdown={markdown}
10+ search={search}
11+ actionText="View RFPs"
12+ actionLink="/grants?program=rfps#view-grants"
13+ />
14+ );
15+}
16+
17+export async function getStaticProps() {
18+ const post = getPostBySlug(
19+ "/rfps",
20+ ["title", "date", "slug", "content"],
21+ "/"
22+ );
23+
24+ const markdown = JSON.stringify(Markdown.parse({ post }));
25+
26+ return {
27+ props: { post, markdown },
28+ };
29+}