+ Choose additional funders to review your project. Funders may ask
+ supplemental questions.
+
+
+
+ updateProjectParams({ selectedCauses: newCauses })
+ }
+ />
+ {projectParams.selectedCauses.map((c) => c.slug).includes('ltff') && (
+
+
+
+ Applying to{' '}
+
+ Long-Term Future Fund
+
+
+
+ Funds people or projects that aim to improve the long-term future,
+ such as by reducing risks from artificial intelligence and
+ engineered pandemics
+
+
+ )}
+ {projectParams.selectedCauses.map((c) => c.slug).includes('eaif') && (
+
+
+
+ Applying to{' '}
+
+ EA Infrastructure Fund
+
+
+
+ Funds organizations or people that aim to grow or improve the
+ effective altruism community
+
+
+ )}
+
+
@@ -217,14 +326,15 @@ export function CreateProjectForm(props: { causesList: Cause[] }) {
/>
- Note that the editor offers formatting shortcuts{' '}
-
like Notion
- {' '}
- for hyperlinks, bullet points, headers, and more.
+ {' '}
+ for links, bullet points, headings, images and more.
@@ -327,9 +437,13 @@ export function CreateProjectForm(props: { causesList: Cause[] }) {
- 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.
/>
diff --git a/app/projects/[slug]/approve-app.tsx b/app/projects/[slug]/approve-app.tsx
new file mode 100644
index 00000000..d92e7861
--- /dev/null
+++ b/app/projects/[slug]/approve-app.tsx
@@ -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(null)
+ const [amount, setAppAmount] = useState(0)
+ return (
+
+ Evaluate for LTFF
+
+ {
+ setAppStage(value === 'approve' ? 'active' : 'not funded')
+ }}
+ />
+
+ {appStage === 'active' && (
+ <>
+ Amount to fund ($)
+ setAppAmount(parseInt(e.target.value))}
+ placeholder="Amount"
+ />
+ >
+ )}
+ {
+ 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}
+
+
+ )
+}
diff --git a/app/projects/[slug]/comments.tsx b/app/projects/[slug]/comments.tsx
index f6aebdf2..1567a9d9 100644
--- a/app/projects/[slug]/comments.tsx
+++ b/app/projects/[slug]/comments.tsx
@@ -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'
@@ -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
diff --git a/app/projects/[slug]/project-tabs.tsx b/app/projects/[slug]/project-tabs.tsx
index bb6b8ee0..315e2b2f 100644
--- a/app/projects/[slug]/project-tabs.tsx
+++ b/app/projects/[slug]/project-tabs.tsx
@@ -1,6 +1,6 @@
'use client'
import { Comments } from './comments'
-import { FullProject, TOTAL_SHARES } from '@/db/project'
+import { FullProject, ProjectStage, TOTAL_SHARES } from '@/db/project'
import { Profile } from '@/db/profile'
import { useSearchParams } from 'next/navigation'
import { Bids } from './bids'
@@ -15,6 +15,9 @@ import { uniq } from 'lodash'
import { compareDesc } from 'date-fns'
import { formatMoneyPrecise, formatPercent } from '@/utils/formatting'
import { MarketTab } from '../market-tab'
+import clsx from 'clsx'
+import { Col } from '@/components/layout/col'
+import { JudgeApp } from './approve-app'
export function ProjectTabs(props: {
project: FullProject
@@ -110,6 +113,41 @@ export function ProjectTabs(props: {
),
})
}
+ tabs.push({
+ name: 'App status',
+ id: 'app-status',
+ count: 0,
+ display: (
+
+ Application status
+ App status across various funders
+
+
+ Manifund
+
+
+ EAIF{' '}
+ c.cause_slug === 'eaif')
+ ?.application_stage ?? null
+ }
+ />
+
+
+ LTFF{' '}
+ c.cause_slug === 'ltff')
+ ?.application_stage ?? null
+ }
+ />
+
+
+
+
+ ),
+ })
if (
(project.stage === 'active' || project.stage === 'complete') &&
@@ -168,6 +206,29 @@ export function getShareholders(txns: TxnAndProfiles[]) {
return shareholdersArray.filter((shareholder) => !!shareholder.profile)
}
+export function StageBadge(props: { stage: ProjectStage | null }) {
+ const { stage } = props
+ const colors = {
+ proposal: 'bg-blue-50 text-blue-700 ring-blue-700/10',
+ active: 'bg-green-50 text-green-700 ring-green-600/20',
+ complete: 'bg-purple-50 text-purple-700 ring-purple-700/10',
+ 'not funded': 'bg-red-50 text-red-700 ring-red-600/10',
+ hidden: 'bg-gray-50 text-gray-700 ring-gray-600/10',
+ draft: 'bg-gray-50 text-gray-700 ring-gray-600/10',
+ }
+ const color = colors[stage ?? 'hidden']
+ return (
+
+ {stage ?? 'not applying'}
+
+ )
+}
+
export function getCommenterContributions(
comments: CommentAndProfile[],
bids: BidAndProfile[],
diff --git a/app/questions/questionBank.json b/app/questions/questionBank.json
new file mode 100644
index 00000000..daeeebc6
--- /dev/null
+++ b/app/questions/questionBank.json
@@ -0,0 +1,22 @@
+[
+ {
+ "id": "trackRecord",
+ "question": "Track record",
+ "description": "What is your track record for running projects of this kind?\n\nPlease give us an outline of previous successes and failures that would help us understand your (or your organization's) ability to execute this specific project. For example, campaigns you've implemented, products you've built, research you've published, or relevant professional and volunteering experience.\n\nAlternatively, what other kind of evidence do you have that the organization or project will succeed? If you are applying for a project that is related to a previous EA Funds grant, please elaborate on how your previous grant/project went."
+ },
+ {
+ "id": "projectGoals",
+ "question": "Project goals",
+ "description": "What specific actions or steps might your project involve? What impact will this have on the world? What is your project's goal, how will you know if you've achieved it, and what is the path to impact? How does this relate to the goals of the fund(s) you are applying to?"
+ },
+ {
+ "id": "destroyXRisk",
+ "question": "How are you going to destroy all x-risk",
+ "description": "joke answers only"
+ },
+ {
+ "id": "10xEAMovement",
+ "question": "How are you going to 10x the ea movement",
+ "description": "cryptocurrency exchange?"
+ }
+]
\ No newline at end of file
diff --git a/app/questions/questionChoices.json b/app/questions/questionChoices.json
new file mode 100644
index 00000000..50eb3ee5
--- /dev/null
+++ b/app/questions/questionChoices.json
@@ -0,0 +1,12 @@
+{
+ "ltff": [
+ "trackRecord",
+ "projectGoals",
+ "destroyXRisk"
+ ],
+ "eaif": [
+ "trackRecord",
+ "projectGoals",
+ "10xEAMovement"
+ ]
+}
\ No newline at end of file
diff --git a/components/checkbox.tsx b/components/checkbox.tsx
new file mode 100644
index 00000000..de091d62
--- /dev/null
+++ b/components/checkbox.tsx
@@ -0,0 +1,126 @@
+// not in use
+
+// import { RadioGroup } from '@headlessui/react'
+// import clsx from 'clsx'
+// import { Row } from './layout/row'
+
+// export function HorizontalRadioGroup(props: {
+// value: string
+// onChange: (value: string) => void
+// options: { [key: string]: string }
+// wide?: boolean
+// }) {
+// const { value, onChange, options, wide } = props
+// return (
+//
+//
+// {Object.entries(options).map(([type, label]) => (
+//
+// clsx(
+// 'cursor-pointer focus:outline-none',
+// checked
+// ? 'bg-orange-500 text-white hover:bg-orange-600'
+// : 'bg-white text-gray-900',
+// 'flex items-center justify-center rounded-md px-3 py-3 text-sm font-semibold'
+// )
+// }
+// >
+// {label}
+//
+// ))}
+//
+//
+// )
+// }
+
+
+// // tailwind.config.js
+// module.exports = {
+// // ...
+// plugins: [
+// // ...
+// require('@tailwindcss/forms'),
+// ],
+// }
+// ```
+// */
+
+
+export default function Checkbox() {
+ return (
+
+ Notifications
+
+
+
+
+
+
+
+ Comments
+
+
+
+
+
+
+
+
+
+
+ Candidates
+
+
+ Get notified when a candidate applies for a job.
+
+
+
+
+
+
+
+
+
+ Offers
+
+
+ Get notified when a candidate accepts or rejects an offer.
+
+
+
+
+
+ )
+}
diff --git a/db/database.types.ts b/db/database.types.ts
index 8b6ac26a..1edc95ef 100644
--- a/db/database.types.ts
+++ b/db/database.types.ts
@@ -342,14 +342,21 @@ export type Database = {
}
project_causes: {
Row: {
+ application_stage: Database["public"]["Enums"]["project_stage"] | null
cause_slug: string
project_id: string
}
Insert: {
+ application_stage?:
+ | Database["public"]["Enums"]["project_stage"]
+ | null
cause_slug: string
project_id: string
}
Update: {
+ application_stage?:
+ | Database["public"]["Enums"]["project_stage"]
+ | null
cause_slug?: string
project_id?: string
}
diff --git a/db/project.ts b/db/project.ts
index c848facc..57e7dafe 100644
--- a/db/project.ts
+++ b/db/project.ts
@@ -25,9 +25,11 @@ export type FullProject = Project & { profiles: Profile } & {
project_transfers: ProjectTransfer[]
} & { project_votes: ProjectVote[] } & { causes: MiniCause[] } & {
project_follows: ProjectFollow[]
-}
+} & { project_causes: ProjectCause[] }
export type MiniProject = Project & { profiles: Profile } & { txns: Txn[] }
export const TOTAL_SHARES = 10_000_000
+export type ProjectStage = Database['public']['Enums']['project_stage']
+export type ProjectCause = Database['public']['Tables']['project_causes']['Row']
export async function getProjectBySlug(supabase: SupabaseClient, slug: string) {
const { data, error } = await supabase
@@ -101,7 +103,7 @@ export async function getFullProjectBySlug(
const { data } = await supabase
.from('projects')
.select(
- '*, profiles!projects_creator_fkey(*), bids(*), txns(*), comments(*), rounds(*), project_transfers(*), project_votes(*), project_follows(follower_id), causes(title, slug)'
+ '*, profiles!projects_creator_fkey(*), bids(*), txns(*), comments(*), rounds(*), project_transfers(*), project_votes(*), project_follows(follower_id), causes(title, slug), project_causes(*)'
)
.eq('slug', slug)
.throwOnError()
diff --git a/pages/api/judge-app.ts b/pages/api/judge-app.ts
new file mode 100644
index 00000000..ec17d4cd
--- /dev/null
+++ b/pages/api/judge-app.ts
@@ -0,0 +1,44 @@
+import { NextRequest, NextResponse } from 'next/server'
+import { createEdgeClient } from './_db'
+import { getProjectById, updateProjectStage } from '@/db/project'
+import { JSONContent } from '@tiptap/core'
+import { sendComment } from '@/db/comment'
+
+export const config = {
+ runtime: 'edge',
+ regions: ['sfo1'],
+ // From https://github.com/lodash/lodash/issues/5525
+ unstable_allowDynamic: [
+ '**/node_modules/lodash/_root.js', // Use a glob to allow anything in the function-bind 3rd party module
+ ],
+}
+
+type judgeAppProps = {
+ projectId: string
+ causeSlug: string
+ decision: 'approve' | 'reject'
+ funding: number
+}
+
+export default async function handler(req: NextRequest) {
+ const { projectId, causeSlug, decision, funding } =
+ (await req.json()) as judgeAppProps
+ const supabase = createEdgeClient(req)
+
+ // Turn off auth checks for hackathon
+ // const resp = await supabase.auth.getUser()
+ // const user = resp.data.user
+ // const project = await getProjectById(supabase, projectId)
+ // if (!user || user.id !== project.creator) return NextResponse.error()
+
+ // Update the project cause to include the decision and funding amount
+ const { data, error } = await supabase
+ .from('project_causes')
+ .update({
+ application_stage: decision === 'approve' ? 'active' : 'not funded',
+ })
+ .eq('project_id', projectId)
+ .eq('cause_slug', causeSlug)
+
+ return NextResponse.json('success')
+}
diff --git a/seed.sql b/seed.sql
index 4a3e4c92..8c2a259b 100644
--- a/seed.sql
+++ b/seed.sql
@@ -14,6 +14,7 @@ create table public.profiles (
long_description jsonb,
regranter_status boolean not null,
stripe_connect_id text,
+ managed_causes jsonb,
primary key (id)
);
@@ -96,6 +97,7 @@ create table if not exists public.projects (
approved boolean,
signed_agreement boolean not null default false,
markets jsonb,
+ private bool,
primary key (id)
);
@@ -110,7 +112,7 @@ UPDATE
CREATE POLICY "Enable read access for all users" ON "public"."projects" AS PERMISSIVE FOR
SELECT
- TO public USING (true);
+ TO public USING (private = false);
CREATE POLICY "Enable update for austin based on email" ON "public"."projects" AS PERMISSIVE FOR
UPDATE
@@ -154,6 +156,24 @@ INSERT
AND auth.uid() :: text = (storage.foldername(name)) [1]
);
+CREATE POLICY "Allow user to see own private records"
+ON "public"."projects"
+TO authenticated
+USING ((( SELECT auth.uid() AS uid) = creator)
+);
+
+CREATE policy "Allow cause managers to see cause grants"
+ON "public"."projects"
+TO public
+USING (
+ ((private = false) OR (id IN ( SELECT projects.id
+ FROM project_causes
+ WHERE (project_causes.cause_slug IN ( SELECT jsonb_array_elements_text(profiles.managed_causes) AS jsonb_array_elements_text
+ FROM profiles
+ WHERE (profiles.id = auth.uid()))))))
+);
+
+
--
--
--
@@ -368,6 +388,7 @@ CREATE TABLE public.project_causes (
id int8 NOT NULL,
project_id uuid NOT NULL REFERENCES public.projects(id) ON DELETE CASCADE,
tag_slug text NOT NULL REFERENCES public.causes(slug) ON DELETE CASCADE,
+ application_stage project_stage DEFAULT null,
PRIMARY KEY (id)
);
diff --git a/utils/constants.ts b/utils/constants.ts
index 83bf1643..4d90d108 100644
--- a/utils/constants.ts
+++ b/utils/constants.ts
@@ -98,3 +98,5 @@ export function isCharitableDeposit(txnId: string) {
}
export const CURRENT_AGREEMENT_VERSION = 3
+
+export const FUNDER_SLUGS = ['ltff', 'eaif']