Skip to content

Commit

Permalink
Use Supabase to load charities page (manifoldmarkets#2085)
Browse files Browse the repository at this point in the history
  • Loading branch information
mqp authored Oct 21, 2023
1 parent a299902 commit bab0f80
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 58 deletions.
8 changes: 8 additions & 0 deletions backend/supabase/txns.sql
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,11 @@ with daily_totals as (
select total from daily_totals
order by total desc;
$$ language sql;

create or replace function get_donations_by_charity() returns table (charity_id text, total numeric) as $$
select data->>'toId' as charity_id, sum((data->'amount')::numeric) as total
from txns
where data->>'category' = 'CHARITY'
group by data->>'toId'
order by total desc
$$ language sql;
9 changes: 8 additions & 1 deletion common/src/supabase/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3001,7 +3001,14 @@ export interface Database {
Args: {
table_id: string
}
Returns: Database['public']['CompositeTypes']['table_spec']
Returns: Database["public"]["CompositeTypes"]["table_spec"]
}
get_donations_by_charity: {
Args: Record<PropertyKey, never>
Returns: {
charity_id: string
total: number
}[]
}
get_engaged_users: {
Args: Record<PropertyKey, never>
Expand Down
2 changes: 2 additions & 0 deletions common/src/supabase/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Contract } from '../contract'
import { Bet } from '../bet'
import { ContractMetric } from '../contract-metric'
import { Group } from '../group'
import { Txn } from '../txn'

export type Schema = Database['public']
export type Tables = Schema['Tables']
Expand Down Expand Up @@ -96,6 +97,7 @@ type JsonTypes = {
contract_bets: Bet
public_contract_bets: Bet
groups: Group
txns: Txn
}

export type DataFor<T extends Selectable> = T extends keyof JsonTypes
Expand Down
16 changes: 6 additions & 10 deletions web/components/charity/charity-card.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
import { StarIcon } from '@heroicons/react/solid'
import { sumBy } from 'lodash'
import Link from 'next/link'
import Image from 'next/legacy/image'
import { Charity } from 'common/charity'
import { useCharityTxns } from 'web/hooks/use-charity-txns'
import { formatMoneyUSD } from 'common/util/format'
import { Row } from '../layout/row'
import { Col } from '../layout/col'
import { Card } from '../widgets/card'

export function CharityCard(props: { charity: Charity }) {
const { charity } = props
const { slug, photo, preview, id, tags } = charity

const txns = useCharityTxns(id)
const raised = sumBy(txns, (txn) => txn.amount)
export function CharityCard(props: { charity: Charity, raised: number }) {
const { charity, raised } = props
const { slug, photo, preview, tags } = charity
const raisedUSD = Math.floor(raised / 100);

return (
<Link href={`/charity/${slug}`} className="flex-1">
Expand All @@ -31,12 +27,12 @@ export function CharityCard(props: { charity: Charity }) {
</div>
<Col className="p-8">
<div className="line-clamp-4 text-sm">{preview}</div>
{raised > 0 && (
{raisedUSD > 0 && (
<>
<Row className="text-ink-900 mt-4 flex-1 items-end justify-center gap-6">
<Row className="items-baseline gap-1">
<span className="text-3xl font-semibold">
{formatMoneyUSD(raised / 100)}
{formatMoneyUSD(raisedUSD)}
</span>
raised
</Row>
Expand Down
8 changes: 1 addition & 7 deletions web/lib/firebase/txns.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ManalinkTxn, DonationTxn, TipTxn, Txn, LeagueBidTxn } from 'common/txn'
import { orderBy, query, where } from 'firebase/firestore'
import { coll, getValues, listenForValues } from './utils'
import { coll, listenForValues } from './utils'
import { useState, useEffect } from 'react'
import { orderBy as _orderBy } from 'lodash'

Expand All @@ -21,12 +21,6 @@ export function listenForCharityTxns(
return listenForValues<DonationTxn>(getCharityQuery(charityId), setTxns)
}

const charitiesQuery = query(txns, where('toType', '==', 'CHARITY'))

export function getAllCharityTxns() {
return getValues<DonationTxn>(charitiesQuery)
}

const getTipsOnContractQuery = (contractId: string) =>
query(
txns,
Expand Down
17 changes: 17 additions & 0 deletions web/lib/supabase/txns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { db } from './db'
import { run, selectFrom } from 'common/supabase/utils'

export async function getDonationsByCharity() {
const { data } = await db.rpc('get_donations_by_charity');
return Object.fromEntries(data!.map((r: any) => [r.charity_id, r.total]));
}

export async function getMostRecentDonation() {
const { data } = await run(
selectFrom(db, 'txns', 'fromId', 'toId')
.eq('data->>category', 'CHARITY')
.order('data->createdTime', { ascending: false } as any)
.limit(1)
)
return data[0]
}
70 changes: 30 additions & 40 deletions web/pages/charity/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
import {
mapValues,
groupBy,
sumBy,
sum,
sortBy,
debounce,
uniqBy,
} from 'lodash'
import { debounce, sortBy, sum } 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/layout/page'
import { Title } from 'web/components/widgets/title'
import { getAllCharityTxns } from 'web/lib/firebase/txns'
import { getDonationsByCharity, getMostRecentDonation } from 'web/lib/supabase/txns'
import { formatMoney, manaToUSD } from 'common/util/format'
import { searchInAny } from 'common/util/parse'
import { getUser } from 'web/lib/firebase/users'
Expand All @@ -26,25 +18,15 @@ import { ENV_CONFIG } from 'common/envs/constants'
import { AlertBox } from 'web/components/widgets/alert-box'

export async function getStaticProps() {
let txns = await getAllCharityTxns()
// Sort by newest txns first
txns = sortBy(txns, 'createdTime').reverse()
const totals = mapValues(groupBy(txns, 'toId'), (txns) =>
sumBy(txns, (txn) => txn.amount)
)
const totalRaised = sum(Object.values(totals))
const sortedCharities = sortBy(charities, [(charity) => -totals[charity.id]])
const numDonors = uniqBy(txns, (txn) => txn.fromId).length
const mostRecentDonor = txns[0] ? await getUser(txns[0].fromId) : null
const mostRecentCharity = txns[0]?.toId ?? ''

const [totalsByCharity, mostRecentDonation] = await Promise.all([
getDonationsByCharity(),
getMostRecentDonation()
])
return {
props: {
totalRaised,
charities: sortedCharities,
numDonors,
mostRecentDonor,
mostRecentCharity,
totalsByCharity,
mostRecentCharityId: mostRecentDonation.toId,
mostRecentDonor: await getUser(mostRecentDonation.fromId)
},
revalidate: 60,
}
Expand Down Expand Up @@ -83,32 +65,36 @@ function DonatedStats(props: { stats: Stat[] }) {
}

export default function Charity(props: {
totalRaised: number
charities: CharityType[]
numDonors: number
totalsByCharity: { [k: string]: number },
mostRecentDonor?: User | null
mostRecentCharity?: string
mostRecentCharityId?: string
}) {
const { totalRaised, charities, mostRecentCharity, mostRecentDonor } = props
const { totalsByCharity, mostRecentCharityId, mostRecentDonor } = props

const [query, setQuery] = useState('')
const totalRaised = sum(Object.values(totalsByCharity));
const debouncedQuery = debounce(setQuery, 50)
const recentCharityName =
charities.find((charity) => charity.id === mostRecentCharity)?.name ??
charities.find((charity) => charity.id === mostRecentCharityId)?.name ??
'Nobody'

const filterCharities = useMemo(
() =>
charities.filter(
() => {
const sortedCharities = sortBy(charities, [
c => -(totalsByCharity[c.id] ?? 0),
c => c.name
])
return sortedCharities.filter(
(charity) =>
searchInAny(
query,
charity.name,
charity.preview,
charity.description
) || (charity.tags as string[])?.includes(query.toLowerCase())
),
[charities, query]
)
},
[charities, totalsByCharity, query]
)

return (
Expand Down Expand Up @@ -161,7 +147,7 @@ export default function Charity(props: {
{
name: 'Most recent donation',
stat: recentCharityName,
url: `/charity/${mostRecentCharity}`,
url: `/charity/${mostRecentCharityId}`,
},
]}
/>
Expand All @@ -175,8 +161,12 @@ export default function Charity(props: {
/>
</Col>
<div className="grid max-w-xl grid-flow-row grid-cols-1 gap-4 self-center lg:max-w-full lg:grid-cols-2 xl:grid-cols-3">
{filterCharities.map((charity) => (
<CharityCard charity={charity} key={charity.name} />
{filterCharities.map((charity, i) => (
<CharityCard
charity={charity}
raised={totalsByCharity[charity.id]}
key={charity.id}
/>
))}
</div>
{filterCharities.length === 0 && (
Expand Down

0 comments on commit bab0f80

Please sign in to comment.