manifold/web/pages/charity/index.tsx

195 lines
6.1 KiB
TypeScript
Raw Normal View History

2022-07-27 17:22:52 +00:00
import { mapValues, groupBy, sumBy, sum, debounce, uniqBy } from 'lodash'
import { useState, useMemo } from 'react'
2022-07-27 17:22:52 +00:00
import { 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 { Title } from 'web/components/title'
2022-06-14 04:27:20 +00:00
import { manaToUSD } from 'common/util/format'
import { quadraticMatches } from 'common/quadratic-funding'
2022-07-27 17:22:52 +00:00
import { DonationTxn, Txn } from 'common/txn'
import { useTracking } from 'web/hooks/use-tracking'
2022-07-15 21:16:00 +00:00
import { searchInAny } from 'common/util/parse'
import { getUser } from 'web/lib/firebase/users'
import { SiteLink } from 'web/components/site-link'
2022-07-19 07:34:53 +00:00
import { User } from 'common/user'
2022-07-22 16:56:03 +00:00
import { SEO } from 'web/components/SEO'
export async function getStaticProps() {
2022-07-27 17:22:52 +00:00
const txns: DonationTxn[] = []
const totals = mapValues(groupBy(txns, 'toId'), (txns) =>
sumBy(txns, (txn) => txn.amount)
)
const totalRaised = sum(Object.values(totals))
const matches = quadraticMatches(txns, totalRaised)
const numDonors = uniqBy(txns, (txn) => txn.fromId).length
const mostRecentDonor = await getUser(txns[txns.length - 1].fromId)
return {
props: {
totalRaised,
2022-07-27 17:22:52 +00:00
charities: [],
matches,
txns,
numDonors,
mostRecentDonor,
},
revalidate: 60,
}
}
type Stat = {
name: string
stat: string
url?: 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((stat) => (
<div
key={stat.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">
{stat.name}
</dt>
<dd className="mt-1 text-3xl font-semibold text-gray-900">
{stat.url ? (
<SiteLink href={stat.url}>{stat.stat}</SiteLink>
) : (
<span>{stat.stat}</span>
)}
</dd>
</div>
))}
</dl>
)
}
export default function Charity(props: {
totalRaised: number
charities: CharityType[]
matches: { [charityId: string]: number }
txns: Txn[]
numDonors: number
mostRecentDonor: User
}) {
const { totalRaised, charities, matches, numDonors, mostRecentDonor } = props
const [query, setQuery] = useState('')
const debouncedQuery = debounce(setQuery, 50)
const filterCharities = useMemo(
2022-04-30 12:42:25 +00:00
() =>
charities.filter(
(charity) =>
2022-07-15 21:16:00 +00:00
searchInAny(
query,
charity.name,
charity.preview,
charity.description
) || (charity.tags as string[])?.includes(query.toLowerCase())
2022-04-30 12:42:25 +00:00
),
[charities, query]
)
useTracking('view charity')
return (
<Page>
2022-07-22 16:56:03 +00:00
<SEO
title="Manifold for Charity"
description="Donate your prediction market earnings to charity on Manifold."
url="/charity"
/>
2022-05-03 15:26:02 +00:00
<Col className="w-full rounded px-4 py-6 sm:px-8 xl:w-[125%]">
<Col className="">
2022-06-11 16:15:59 +00:00
<Title className="!mt-0" text="Manifold for Charity" />
{/* <span className="text-gray-600">
2022-06-14 04:27:20 +00:00
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> */}
2022-07-22 16:59:25 +00:00
<span className="text-gray-600">
Convert your M$ earnings into real charitable donations.
</span>
<DonatedStats
stats={[
{
name: 'Raised by Manifold users',
stat: manaToUSD(totalRaised),
},
{
name: 'Number of donors',
stat: `${numDonors}`,
},
{
name: 'Most recent donor',
stat: mostRecentDonor.name ?? 'Nobody',
url: `/${mostRecentDonor.username}`,
},
]}
/>
<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">
2022-04-30 12:42:25 +00:00
😢 We couldn't find that charity...
</div>
)}
2022-06-14 04:27:20 +00:00
<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>
2022-04-30 12:42:25 +00:00
<div className="mt-10 text-gray-500">
2022-06-14 04:27:20 +00:00
<span className="font-semibold">Notes</span>
<br />
- Don't see your favorite charity? Recommend it by emailing
charity@manifold.markets!
<br />
2022-06-14 04:27:20 +00:00
- Manifold is not affiliated with non-Featured charities; we're just
fans of their work.
2022-04-30 12:42:25 +00:00
<br />
2022-06-14 04:27:20 +00:00
- 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>
)
}