Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Common app for grantees #138

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions app/causes/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,29 @@ import { createServerClient } from '@/db/supabase-server'
import { FullCause, listFullCauses } from '@/db/cause'
import Image from 'next/image'
import Link from 'next/link'
import { FUNDER_SLUGS } from '@/utils/constants'

export default async function CausesPage() {
const supabase = createServerClient()
const causesList = await listFullCauses(supabase)
const funders = causesList.filter((c) => FUNDER_SLUGS.includes(c.slug))
const prizes = causesList.filter((c) => c.prize)
const regularCauses = causesList.filter((c) => !c.prize)
const regularCauses = causesList.filter(
(c) => !c.prize && !FUNDER_SLUGS.includes(c.slug)
)
return (
<div className="p-5">
<h1 className="text-lg font-bold text-gray-900 sm:text-2xl">
<h1 className="text-lg font-bold text-gray-900 sm:text-2xl">Funders</h1>
<span className="text-sm text-gray-600">
Other funders who partner with Manifund&apos;s common app
</span>
<div className="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 lg:gap-8">
{funders.map((cause) => (
<CauseCard key={cause.slug} cause={cause} />
))}
</div>

<h1 className="mt-10 text-lg font-bold text-gray-900 sm:text-2xl">
Prize rounds
</h1>
<span className="text-sm text-gray-600">
Expand All @@ -21,6 +35,7 @@ export default async function CausesPage() {
<CauseCard key={cause.slug} cause={cause} />
))}
</div>

<h1 className="mt-10 text-lg font-bold text-gray-900 sm:text-2xl">
Causes
</h1>
Expand Down
146 changes: 130 additions & 16 deletions app/create/create-project-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,21 @@ import { HorizontalRadioGroup } from '@/components/radio-group'
import { Checkbox } from '@/components/input'
import { usePartialUpdater } from '@/hooks/user-partial-updater'
import { ProjectParams } from '@/utils/upsert-project'
import questionBank from '../questions/questionBank.json'
import questionChoicesData from '../questions/questionChoices.json'
import { CheckCircleIcon } from '@heroicons/react/24/solid'
import { FUNDER_SLUGS } from '@/utils/constants'

const DESCRIPTION_OUTLINE = `
interface QuestionsData {
id: string
question: string
description: string
}

const questions = questionBank as QuestionsData[]
const questionChoices = questionChoicesData as { [key: string]: string[] }

var DESCRIPTION_OUTLINE = `
<h3>Project summary</h3>
</br>
<h3>What are this project's goals and how will you achieve them?</h3>
Expand All @@ -38,6 +51,32 @@ const DESCRIPTION_OUTLINE = `
<h3>What other funding are you or your project getting?</h3>
</br>
`

const addQuestionsToDescription = (
selectedCauses: string[],
descriptionOutline: string
) => {
const addedQuestions = selectedCauses.reduce((set, cause) => {
const causeQuestionIds = questionChoices[cause] || []
causeQuestionIds.forEach((questionId) => set.add(questionId))
return set
}, new Set<string>())

addedQuestions.forEach((questionId) => {
const question = questions.find((q) => q.id === questionId)
if (question) {
const formattedDescription = question.description.replace(/\n/g, '<br>')
descriptionOutline += `
<h3>${question.question}</h3>
<p>${formattedDescription}</p>
</br>
`
}
})

return descriptionOutline
}

const DESCRIPTION_KEY = 'ProjectDescription'

export function CreateProjectForm(props: { causesList: Cause[] }) {
Expand All @@ -56,6 +95,8 @@ export function CreateProjectForm(props: { causesList: Cause[] }) {
}
)
const [isSubmitting, setIsSubmitting] = useState<boolean>(false)
const [isLTFFSelected, setIsLTFFSelected] = useState(false)
const [isEAIFSelected, setIsEAIFSelected] = useState(false)

const editor = useTextEditor(DESCRIPTION_OUTLINE, DESCRIPTION_KEY)
const [madeChanges, setMadeChanges] = useState<boolean>(false)
Expand All @@ -76,19 +117,30 @@ export function CreateProjectForm(props: { causesList: Cause[] }) {
100,
})
if (!madeChanges) {
editor?.commands.setContent(
let descriptionOutline =
projectParams.selectedPrize?.project_description_outline ??
DESCRIPTION_OUTLINE
DESCRIPTION_OUTLINE

descriptionOutline = addQuestionsToDescription(
projectParams.selectedCauses.map((cause) => cause.slug),
descriptionOutline
)

editor?.commands.setContent(descriptionOutline)
setMadeChanges(false)
}
}, [projectParams.selectedPrize])
}, [projectParams.selectedPrize, projectParams.selectedCauses])

const selectablePrizeCauses = causesList.filter(
(cause) => cause.open && cause.prize
)
const selectableCauses = causesList.filter(
(cause) => cause.open && !cause.prize
(cause) => cause.open && !cause.prize && !FUNDER_SLUGS.includes(cause.slug)
)
const funderCauses = causesList.filter((cause) =>
FUNDER_SLUGS.includes(cause.slug)
)

const minMinFunding = projectParams.selectedPrize?.cert_params
? projectParams.selectedPrize.cert_params.minMinFunding
: 500
Expand Down Expand Up @@ -130,10 +182,10 @@ export function CreateProjectForm(props: { causesList: Cause[] }) {
return (
<Col className="gap-4 p-5">
<div className="flex flex-col md:flex-row md:justify-between">
<h1 className="text-3xl font-bold">Add a project</h1>
<h1 className="text-3xl font-bold">Propose a project</h1>
</div>
<Col className="gap-1">
<label>I am applying for...</label>
{/* <label>I am applying for...</label>
<p className="text-sm text-gray-600">
Select &quot;a regular grant&quot; by default. The other options are
specific prizes that you can learn more about{' '}
Expand All @@ -160,7 +212,7 @@ export function CreateProjectForm(props: { causesList: Cause[] }) {
selectablePrizeCauses.map((cause) => [cause.slug, cause.title])
),
}}
/>
/> */}
</Col>
<Col className="gap-1">
<label htmlFor="title">
Expand All @@ -183,6 +235,7 @@ export function CreateProjectForm(props: { causesList: Cause[] }) {
</span>
</Col>
</Col>

<Col className="gap-1">
<label htmlFor="subtitle">Subtitle</label>
<Col>
Expand All @@ -201,6 +254,62 @@ export function CreateProjectForm(props: { causesList: Cause[] }) {
</span>
</Col>
</Col>

<Col className="gap-2">
<label>Common app funders</label>
<p className="text-sm text-gray-500">
Choose additional funders to review your project. Funders may ask
supplemental questions.
</p>

<SelectCauses
causesList={funderCauses}
selectedCauses={projectParams.selectedCauses}
setSelectedCauses={(newCauses: MiniCause[]) =>
updateProjectParams({ selectedCauses: newCauses })
}
/>
{projectParams.selectedCauses.map((c) => c.slug).includes('ltff') && (
<div className="mb-4 mt-2">
<h2 className="text-lg">
<CheckCircleIcon className="mr-2 inline-block h-6 w-6 text-orange-500" />
Applying to{' '}
<SiteLink
followsLinkClass
className="text-orange-500"
href="https://funds.effectivealtruism.org/funds/far-future"
>
Long-Term Future Fund
</SiteLink>
</h2>
<p className="text-sm text-gray-800">
Funds people or projects that aim to improve the long-term future,
such as by reducing risks from artificial intelligence and
engineered pandemics
</p>
</div>
)}
{projectParams.selectedCauses.map((c) => c.slug).includes('eaif') && (
<div className="mb-4 mt-2">
<h2 className="text-lg">
<CheckCircleIcon className="mr-2 inline-block h-6 w-6 text-orange-500" />
Applying to{' '}
<SiteLink
followsLinkClass
className="text-orange-500"
href="https://funds.effectivealtruism.org/funds/ea-community"
>
EA Infrastructure Fund
</SiteLink>
</h2>
<p className="text-sm text-gray-800">
Funds organizations or people that aim to grow or improve the
effective altruism community
</p>
</div>
)}
</Col>

<Col className="gap-1">
<Row className="items-center justify-between">
<label>
Expand All @@ -217,14 +326,15 @@ export function CreateProjectForm(props: { causesList: Cause[] }) {
/>
</Row>
<p className="text-sm text-gray-500">
Note that the editor offers formatting shortcuts{' '}
<Link
className="hover:underline"
Rich text supported, with formatting{' '}
<SiteLink
followsLinkClass
className="text-orange-500"
href="https://www.notion.so/help/keyboard-shortcuts#markdown-style"
>
like Notion
</Link>{' '}
for hyperlinks, bullet points, headers, and more.
</SiteLink>{' '}
for links, bullet points, headings, images and more.
</p>
<TextEditor editor={editor} />
</Col>
Expand Down Expand Up @@ -327,9 +437,13 @@ export function CreateProjectForm(props: { causesList: Cause[] }) {
<SelectCauses
causesList={selectableCauses}
selectedCauses={projectParams.selectedCauses}
setSelectedCauses={(newCauses: MiniCause[]) =>
updateProjectParams({ selectedCauses: newCauses })
}
setSelectedCauses={(newCauses: (MiniCause | undefined)[]) =>
updateProjectParams({
selectedCauses: newCauses.filter(
(cause): cause is MiniCause => !!cause
),
})
} //added a type assertion to filter out any undefined values from the newCauses array before updating the selectedCauses property.
/>
</Col>

Expand Down
69 changes: 69 additions & 0 deletions app/projects/[slug]/approve-app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Button } from '@/components/button'
import { Input } from '@/components/input'
import { Col } from '@/components/layout/col'
import { HorizontalRadioGroup } from '@/components/radio-group'
import { Project } from '@/db/project'
import { useRouter } from 'next/navigation'
import { useState } from 'react'

type AppStage = 'proposal' | 'not funded' | 'active' | null

// A component that allows a grantmaker to approve or reject a proposal,
// and to determine how much to fund it with, if approved.
export function JudgeApp(props: { project: Project }) {
const { project } = props
const router = useRouter()
const [appStage, setAppStage] = useState<AppStage>(null)
const [amount, setAppAmount] = useState<number>(0)
return (
<Col className="mt-4 max-w-md gap-4 rounded-xl border border-gray-500 p-4">
<h2 className="text-xl font-semibold">Evaluate for LTFF</h2>

<HorizontalRadioGroup
options={{
approve: 'Approve',
reject: 'Reject',
}}
value={appStage === 'active' ? 'approve' : 'reject'}
onChange={(value) => {
setAppStage(value === 'approve' ? 'active' : 'not funded')
}}
/>

{appStage === 'active' && (
<>
Amount to fund ($)
<Input
type="number"
value={amount}
onChange={(e) => setAppAmount(parseInt(e.target.value))}
placeholder="Amount"
/>
</>
)}
<Button
onClick={async () => {
try {
await fetch('/api/judge-app', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
projectId: project.id,
causeSlug: 'ltff',
decision: appStage === 'active' ? 'approve' : 'reject',
funding: amount,
}),
})
} catch (e) {
console.error(e)
}
router.refresh()
}}
>
{appStage === 'active' ? 'Approve' : 'Reject'} {project.title}
</Button>
</Col>
)
}
3 changes: 2 additions & 1 deletion app/projects/[slug]/comments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Project } from '@/db/project'
import { ArrowUturnRightIcon } from '@heroicons/react/24/outline'
import { PaperAirplaneIcon } from '@heroicons/react/24/solid'
import { Row } from '@/components/layout/row'
import { IconButton } from '@/components/button'
import { Button, IconButton } from '@/components/button'
import { useEffect, useState } from 'react'
import { orderBy, sortBy } from 'lodash'
import { Tooltip } from '@/components/tooltip'
Expand All @@ -17,6 +17,7 @@ import { JSONContent } from '@tiptap/react'
import clsx from 'clsx'
import { clearLocalStorageItem } from '@/hooks/use-local-storage'
import { Comment } from '@/components/comment'
import { Select } from '@/components/select'

export function Comments(props: {
project: Project
Expand Down
Loading