187 lines
5.6 KiB
TypeScript
187 lines
5.6 KiB
TypeScript
import {
|
|
mapValues,
|
|
groupBy,
|
|
sumBy,
|
|
sum,
|
|
sortBy,
|
|
debounce,
|
|
uniqBy,
|
|
} from 'lodash'
|
|
import { useState, useMemo } from 'react'
|
|
import { charities, Charity as CharityType } from 'common/charity'
|
|
import { CharityCard } from 'web/components/charity/charity-card'
|
|
import { Col } from 'web/components/layout/col'
|
|
import { Spacer } from 'web/components/layout/spacer'
|
|
import { Page } from 'web/components/page'
|
|
import { SiteLink } from 'web/components/site-link'
|
|
import { Title } from 'web/components/title'
|
|
import { getAllCharityTxns } from 'web/lib/firebase/txns'
|
|
import { manaToUSD } from 'common/util/format'
|
|
import { quadraticMatches } from 'common/quadratic-funding'
|
|
import { Txn } from 'common/txn'
|
|
import { useTracking } from 'web/hooks/use-tracking'
|
|
import { searchInAny } from 'common/util/parse'
|
|
|
|
export async function getStaticProps() {
|
|
const txns = await getAllCharityTxns()
|
|
const totals = mapValues(groupBy(txns, 'toId'), (txns) =>
|
|
sumBy(txns, (txn) => txn.amount)
|
|
)
|
|
const totalRaised = sum(Object.values(totals))
|
|
const sortedCharities = sortBy(charities, [
|
|
(charity) => (charity.tags?.includes('Featured') ? 0 : 1),
|
|
(charity) => -totals[charity.id],
|
|
])
|
|
const matches = quadraticMatches(txns, totalRaised)
|
|
const numDonors = uniqBy(txns, (txn) => txn.fromId).length
|
|
|
|
return {
|
|
props: {
|
|
totalRaised,
|
|
charities: sortedCharities,
|
|
matches,
|
|
txns,
|
|
numDonors,
|
|
},
|
|
revalidate: 60,
|
|
}
|
|
}
|
|
|
|
type Stat = {
|
|
name: string
|
|
stat: string
|
|
}
|
|
|
|
function DonatedStats(props: { stats: Stat[] }) {
|
|
const { stats } = props
|
|
return (
|
|
<dl className="mt-3 grid grid-cols-1 gap-5 rounded-lg bg-gradient-to-r from-pink-300 via-purple-300 to-indigo-400 p-4 sm:grid-cols-3">
|
|
{stats.map((item) => (
|
|
<div
|
|
key={item.name}
|
|
className="overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6"
|
|
>
|
|
<dt className="truncate text-sm font-medium text-gray-500">
|
|
{item.name}
|
|
</dt>
|
|
<dd className="mt-1 text-3xl font-semibold text-gray-900">
|
|
{item.stat}
|
|
</dd>
|
|
</div>
|
|
))}
|
|
</dl>
|
|
)
|
|
}
|
|
|
|
export default function Charity(props: {
|
|
totalRaised: number
|
|
charities: CharityType[]
|
|
matches: { [charityId: string]: number }
|
|
txns: Txn[]
|
|
numDonors: number
|
|
}) {
|
|
const { totalRaised, charities, matches, numDonors } = props
|
|
|
|
const [query, setQuery] = useState('')
|
|
const debouncedQuery = debounce(setQuery, 50)
|
|
|
|
const filterCharities = useMemo(
|
|
() =>
|
|
charities.filter(
|
|
(charity) =>
|
|
searchInAny(
|
|
query,
|
|
charity.name,
|
|
charity.preview,
|
|
charity.description
|
|
) || (charity.tags as string[])?.includes(query.toLowerCase())
|
|
),
|
|
[charities, query]
|
|
)
|
|
|
|
useTracking('view charity')
|
|
|
|
return (
|
|
<Page>
|
|
<Col className="w-full rounded px-4 py-6 sm:px-8 xl:w-[125%]">
|
|
<Col className="">
|
|
<Title className="!mt-0" text="Manifold for Charity" />
|
|
<span className="text-gray-600">
|
|
Through July 15, up to $25k of donations will be matched via{' '}
|
|
<SiteLink href="https://wtfisqf.com/" className="font-bold">
|
|
quadratic funding
|
|
</SiteLink>
|
|
, courtesy of{' '}
|
|
<SiteLink href="https://ftxfuturefund.org/" className="font-bold">
|
|
the FTX Future Fund
|
|
</SiteLink>
|
|
!
|
|
</span>
|
|
<DonatedStats
|
|
stats={[
|
|
{
|
|
name: 'Raised by Manifold users',
|
|
stat: manaToUSD(totalRaised),
|
|
},
|
|
{
|
|
name: 'Number of donors',
|
|
stat: `${numDonors}`,
|
|
},
|
|
{
|
|
name: 'Matched via quadratic funding',
|
|
stat: manaToUSD(sum(Object.values(matches))),
|
|
},
|
|
]}
|
|
/>
|
|
<Spacer h={10} />
|
|
|
|
<input
|
|
type="text"
|
|
onChange={(e) => debouncedQuery(e.target.value)}
|
|
placeholder="Find a charity"
|
|
className="input input-bordered mb-6 w-full"
|
|
/>
|
|
</Col>
|
|
<div className="grid max-w-xl grid-flow-row grid-cols-1 gap-4 lg:max-w-full lg:grid-cols-2 xl:grid-cols-3">
|
|
{filterCharities.map((charity) => (
|
|
<CharityCard
|
|
charity={charity}
|
|
key={charity.name}
|
|
match={matches[charity.id]}
|
|
/>
|
|
))}
|
|
</div>
|
|
{filterCharities.length === 0 && (
|
|
<div className="text-center text-gray-500">
|
|
😢 We couldn't find that charity...
|
|
</div>
|
|
)}
|
|
|
|
<div className="mt-10 w-full rounded-xl bg-gradient-to-r from-pink-300 via-purple-300 to-indigo-400 p-5">
|
|
<iframe
|
|
height="405"
|
|
src="https://manifold.markets/ManifoldMarkets/how-much-will-be-donated-through-ma"
|
|
title="Total donations for Manifold for Charity this May (in USD)"
|
|
frameBorder="0"
|
|
className="w-full rounded-xl bg-white p-10"
|
|
></iframe>
|
|
</div>
|
|
|
|
<div className="mt-10 text-gray-500">
|
|
<span className="font-semibold">Notes</span>
|
|
<br />
|
|
- Don't see your favorite charity? Recommend it by emailing
|
|
charity@manifold.markets!
|
|
<br />
|
|
- Manifold is not affiliated with non-Featured charities; we're just
|
|
fans of their work.
|
|
<br />
|
|
- As Manifold itself is a for-profit entity, your contributions will
|
|
not be tax deductible.
|
|
<br />- Donations + matches are wired once each quarter.
|
|
</div>
|
|
</Col>
|
|
</Page>
|
|
)
|
|
}
|