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

website(refactor): revisit transparency page #634

Merged
merged 5 commits into from
Nov 16, 2023
Merged
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
15 changes: 8 additions & 7 deletions shared/locales/de/website-transparency.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
{
"amount": "{{ value, currency }}",
"amount-since-march-2020": "Insgesamt seit März 2020",
"future-payouts": "Künftige Auszahlungen: {{ value, currency }}",
"past-payouts": "Vergangene Auszahlungen: {{ value, currency }}",
"section-1": {
"title-1": "Transparenz schafft Vertrauen.",
"title-2": "Vertrauen schafft Solidarität.",
Expand All @@ -14,11 +11,14 @@
"section-2": {
"title": "Wie wird Social Income finanziert?",
"donations": "Spenden",
"amount-since-march-2020": "Insgesamt seit März 2020",
"contributions-from": "{{ value, currency }} von",
"individuals": "{{ count }} Individuen",
"institutions": "{{ count }} Institutionen",
"used-for-fees": "Für Zustellgebühren von Individualspenden verwendet: {{ value, currency }}",
"used-for-operating-costs": "Für Betriebsgebühren verwendet: {{ value, currency }}"
"past-payouts": "Vergangene Auszahlungen: {{ value, currency }}",
"future-payouts": "Künftige Auszahlungen: {{ value, currency }}",
"past-costs": "Bereits verwendet um Kosten zu decken: {{ value, currency }}",
"future-costs": "Um kommende Kosten zu decken: {{ value, currency }}"
},
"section-3": {
"title": "Von wo kommen die Spender:innen?",
Expand All @@ -29,10 +29,12 @@
"section-4": {
"title": "Wie werden Spenden ausbezahlt?",
"subtitle": "Individuelle Spenden werden zu 100% ausbezahlt. Institutionell Spenden werden gebraucht um die operativen Kosten zu decken.",
"amount-since-march-2020": "Insgesamt seit März 2020",
"expenses": "Ausgaben",
"payments-total": "{{ value, currency }} ausbezahlt an {{ recipientsCount }} Empfänger:innen",
"payments-last-month": "Letzter Monat: {{ value, currency }} an {{ recipientsCount }} Empfänger:innen",
"total-costs": "{{ value, currency }} fees and costs",
"future-payouts": "Künftige Auszahlungen: {{ value, currency }}",
"total-costs": "{{ value, currency }} Gebühren und Kosten",
"payment-fees": "Zustellgebühren: {{ value, currency }}",
"payment-fees-tooltip": "Dies beinhaltet die Transaktionsgebühren von Zahlungsportalen wie Stripe.",
"transaction-fees": "Transaktionsgebühren: {{ value, currency }}",
Expand All @@ -41,7 +43,6 @@
"operating-costs-tooltip": "Dies beinhaltet Kosten für Saläre, Marketing, Fundraising.",
"other-costs": "Andere Kosten: {{ value, currency }}",
"other-costs-tooltip": "??",

"reserves": "Reserven",
"covers-payments": "Reserven decken Auszahlungen für",
"covers-payments-1": "{{ recipientsCount }} Empfänger:innen für {{ monthsCount }} Monate",
Expand Down
14 changes: 7 additions & 7 deletions shared/locales/en/website-transparency.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
{
"amount": "{{ value, currency }}",
"amount-since-march-2020": "Total amount since March 2020",
"future-payouts": "To be paid out: {{ value, currency }}",
"past-payouts": "Already paid out: {{ value, currency }}",
"section-1": {
"title-1": "Transparency builds trust.",
"title-2": "Trust builds solidarity.",
Expand All @@ -14,11 +11,14 @@
"section-2": {
"title": "How is Social Income funded?",
"donations": "Donations",
"amount-since-march-2020": "Total amount since March 2020",
"contributions-from": "{{ value, currency }} contributions from",
"individuals": "{{ count }} Individuals",
"institutions": "{{ count }} Institutions",
"used-for-fees": "Used to cover payment fees: {{ value, currency }}",
"used-for-operating-costs": "Used to cover operational costs: {{ value, currency }}"
"past-payouts": "Already paid out: {{ value, currency }}",
"future-payouts": "To be paid out: {{ value, currency }}",
"past-costs": "Already used to cover costs: {{ value, currency }}",
"future-costs": "To be used to cover costs: {{ value, currency }}"
},
"section-3": {
"title": "Where are our contributors from?",
Expand All @@ -29,10 +29,11 @@
"section-4": {
"title": "How are the funds used?",
"subtitle": "Recurring individual donations are always paid out to 100%. Institutional donations are used to cover our operational costs.",
"amount-since-march-2020": "Total amount since March 2020",
"expenses": "Expenses",
"payments-total": "{{ value, currency }} paid out to {{ recipientsCount }} recipients",
"payments-last-month": "Last month: {{ value, currency }} to {{ recipientsCount }} recipients",

"future-payouts": "To be paid out: {{ value, currency }}",
"total-costs": "{{ value, currency }} fees and costs",
"payment-fees": "Payment fees: {{ value, currency }}",
"payment-fees-tooltip": "This includes transaction fees charged by payment providers like Stripe.",
Expand All @@ -43,7 +44,6 @@
"other-costs": "Other costs: {{ value, currency }}",
"other-costs-tooltip": "??",
"reserves": "Reserves",

"covers-payments": "This covers payments for",
"covers-payments-1": "{{ recipientsCount }} recipients for {{ monthsCount }} months",
"covers-payments-2": "{{ recipientsCount }} recipients for 1 month",
Expand Down
52 changes: 25 additions & 27 deletions shared/src/utils/stats/ContributionStatsCalculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,13 @@ export class ContributionStatsCalculator {
* ContributionStatsCalculator with the flattened intermediate data structure.
* @param firestoreAdmin
* @param currency
* @param contributionFilter
*/
static async build(firestoreAdmin: FirestoreAdmin, currency: string): Promise<ContributionStatsCalculator> {
static async build(
firestoreAdmin: FirestoreAdmin,
currency: string,
contributionFilter = (c: Contribution) => c.status === StatusKey.SUCCEEDED,
): Promise<ContributionStatsCalculator> {
const exchangeRate = await getLatestExchangeRate(firestoreAdmin, currency);

const getContributionsForUser = async (userId: string): Promise<Contribution[]> => {
Expand All @@ -71,32 +76,25 @@ export class ContributionStatsCalculator {
.map(async (userDoc) => {
const user = userDoc.data();
const contributions = await getContributionsForUser(userDoc.id);
return contributions
.filter(
(contribution) =>
contribution.status == StatusKey.SUCCEEDED ||
contribution.status == StatusKey.UNKNOWN ||
contribution.status == undefined,
)
.map((contribution) => {
const created = contribution.created.toDate();
if (created.getFullYear() < 2020) {
console.log(userDoc.id, created);
}
return {
userId: userDoc.id,
isInstitution: Boolean(user.institution),
country: user.location?.toUpperCase() ?? 'CH',
amount: contribution.amount_chf * exchangeRate,
paymentFees: contribution.fees_chf * exchangeRate,
source: contribution.source,
currency: contribution.currency.toUpperCase() ?? '',
month: DateTime.fromObject({
year: created.getFullYear(),
month: created.getMonth() + 1, // month is indexed from 0 in JS
}).toFormat('yyyy-MM'),
} as ContributionStatsEntry;
});
return contributions.filter(contributionFilter).map((contribution) => {
const created = contribution.created.toDate();
if (created.getFullYear() < 2020) {
console.log(userDoc.id, created);
}
return {
userId: userDoc.id,
isInstitution: Boolean(user.institution),
country: user.location?.toUpperCase() ?? 'CH',
amount: contribution.amount_chf * exchangeRate,
paymentFees: contribution.fees_chf * exchangeRate,
source: contribution.source,
currency: contribution.currency.toUpperCase() ?? '',
month: DateTime.fromObject({
year: created.getFullYear(),
month: created.getMonth() + 1, // month is indexed from 0 in JS
}).toFormat('yyyy-MM'),
} as ContributionStatsEntry;
});
}),
);
return new ContributionStatsCalculator(_(contributions.flat()));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { Typography } from '@socialincome/ui';
import { Card, CardContent, Typography } from '@socialincome/ui';
import _ from 'lodash';
import { ReactElement } from 'react';

type TransparencyCardProps = {
sectionTitle: string;
title: string;
text: string;

firstIcon?: ReactElement;
firstContent?: ReactElement;

secondIcon?: ReactElement;
secondContent?: ReactElement;
};
Expand All @@ -18,30 +16,30 @@ export function InfoCard(
{ sectionTitle, title, text, firstIcon, firstContent, secondIcon, secondContent }: TransparencyCardProps,
) {
return (
<div className="divide-neutral grid grid-cols-1 items-center divide-y rounded-lg bg-neutral-200 py-8 md:grid-cols-2 md:divide-x md:divide-y-0">
<div className="space-y-2 p-8">
<Typography size="2xl">{sectionTitle}</Typography>
<Typography size="3xl" weight="bold">
{title}
</Typography>
<Typography size="2xl" weight="medium">
{text}
</Typography>
</div>
<div className="flex flex-col space-y-8 p-8">
{!_.isNil(firstIcon) && !_.isNil(firstContent) && (
<div className="grid grid-cols-9">
<div>{firstIcon}</div>
<div className="col-span-8">{firstContent}</div>
</div>
)}
{!_.isNil(secondIcon) && !_.isNil(secondContent) && (
<div className="grid grid-cols-9">
<div>{secondIcon}</div>
<div className="col-span-8">{secondContent}</div>
</div>
)}
</div>
</div>
<Card>
<CardContent className="grid grid-cols-1 items-center divide-y py-8 md:grid-cols-2 md:divide-x md:divide-y-0">
<div className="space-y-2 p-8">
<Typography size="2xl">{sectionTitle}</Typography>
<Typography size="3xl" weight="bold">
{title}
</Typography>
<Typography size="2xl">{text}</Typography>
</div>
<div className="flex flex-col space-y-8 p-8">
{!_.isNil(firstIcon) && !_.isNil(firstContent) && (
<div className="grid grid-cols-9">
<div>{firstIcon}</div>
<div className="col-span-8">{firstContent}</div>
</div>
)}
{!_.isNil(secondIcon) && !_.isNil(secondContent) && (
<div className="grid grid-cols-9">
<div>{secondIcon}</div>
<div className="col-span-8">{secondContent}</div>
</div>
)}
</div>
</CardContent>
</Card>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,12 @@ export default async function Page({ params }: TransparencyPageProps) {
const { contributionStats, paymentStats } = await getStats(currency);

return (
<div>
<div className="flex flex-col space-y-16 py-8">
<CurrencyRedirect currency={currency} />
<div className="flex flex-col space-y-16">
<Section1 params={params} contributionStats={contributionStats} paymentStats={paymentStats} />
<Section2 params={params} contributionStats={contributionStats} paymentStats={paymentStats} />
<Section3 params={params} contributionStats={contributionStats} paymentStats={paymentStats} />
<Section4 params={params} contributionStats={contributionStats} paymentStats={paymentStats} />
</div>
<Section1 params={params} contributionStats={contributionStats} paymentStats={paymentStats} />
<Section2 params={params} contributionStats={contributionStats} paymentStats={paymentStats} />
<Section3 params={params} contributionStats={contributionStats} paymentStats={paymentStats} />
<Section4 params={params} contributionStats={contributionStats} paymentStats={paymentStats} />
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Translator } from '@socialincome/shared/src/utils/i18n';
import { Typography } from '@socialincome/ui';
import { Card, CardContent, Typography } from '@socialincome/ui';
import _ from 'lodash';
import { SectionProps } from './page';

Expand All @@ -25,23 +25,27 @@ export async function Section1({ params, paymentStats, contributionStats }: Sect
];

return (
<div className="flex flex-col justify-center space-y-2">
<div className="my-8">
<Typography weight="bold" size="4xl" lineHeight="tight">
<div>
<div className="mb-8">
<Typography weight="bold" size="4xl">
{translator.t('section-1.title-1')}
</Typography>
<Typography weight="bold" size="4xl" lineHeight="tight">
<Typography weight="bold" size="4xl" color="secondary">
{translator.t('section-1.title-2')}
</Typography>
</div>
<Typography color="muted-foreground">{translator.t('section-1.since-march-2020')}</Typography>
{cards.map((card, index) => (
<div key={index} className="border-neutral rounded-lg border px-4 py-6 duration-200 hover:scale-[102%]">
<Typography size="xl" weight="normal">
{card}
</Typography>
</div>
))}
<div className="flex flex-col space-y-2">
<Typography color="muted-foreground">{translator.t('section-1.since-march-2020')}</Typography>
{cards.map((card, index) => (
<Card key={index} className="duration-200 hover:scale-[102%]">
<CardContent className="py-8">
<Typography size="xl" weight="normal">
{card}
</Typography>
</CardContent>
</Card>
))}
</div>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,24 @@ import { SectionProps } from './page';
export async function Section2({ params, contributionStats, paymentStats }: SectionProps) {
const translator = await Translator.getInstance({ language: params.lang, namespaces: ['website-transparency'] });
const paymentFees = _.sumBy(contributionStats.totalPaymentFeesByIsInstitution, 'amount');

// TODO: Calculate these costs dynamically
const transactionFees = 8600;
const operatingCosts = 9300;
const otherCosts = 9600;
const totalCosts = paymentFees + transactionFees + operatingCosts + otherCosts;

return (
<div className="flex flex-col space-y-8">
<Typography weight="bold" size="3xl">
<div>
<Typography weight="bold" size="3xl" className="mb-8">
{translator.t('section-2.title')}
</Typography>
<InfoCard
sectionTitle={translator.t('section-2.donations')}
title={translator.t('amount', {
context: { value: contributionStats.totalContributionsAmount, currency: params.currency },
})}
text={translator.t('amount-since-march-2020')}
text={translator.t('section-2.amount-since-march-2020')}
firstIcon={<HeartIcon className="h-8 w-8" />}
firstContent={
<div className="flex flex-col space-y-1">
Expand All @@ -31,23 +34,23 @@ export async function Section2({ params, contributionStats, paymentStats }: Sect
{translator.t('section-2.contributions-from', {
context: { value: contributionStats.totalIndividualContributionsAmount, currency: params.currency },
})}
<Badge className="mx-1">
{translator.t('section-2.individuals', {
context: { count: contributionStats.totalIndividualContributorsCount },
})}
</Badge>
</Typography>
<Badge className="mx-1">
{translator.t('section-2.individuals', {
context: { count: contributionStats.totalIndividualContributorsCount },
})}
</Badge>
</div>
<Typography>
{translator.t('past-payouts', {
{translator.t('section-2.past-payouts', {
context: {
value: paymentStats.totalPaymentsAmount,
currency: params.currency,
},
})}
</Typography>
<Typography>
{translator.t('future-payouts', {
{translator.t('section-2.future-payouts', {
context: {
value: contributionStats.totalIndividualContributionsAmount - paymentStats.totalPaymentsAmount,
currency: params.currency,
Expand All @@ -67,22 +70,25 @@ export async function Section2({ params, contributionStats, paymentStats }: Sect
currency: params.currency,
},
})}
<Badge className="mx-1">
{translator.t('section-2.institutions', {
context: { count: contributionStats.totalInstitutionalContributorsCount },
})}
</Badge>
</Typography>
<Badge className="mx-1">
{translator.t('section-2.institutions', {
context: { count: contributionStats.totalInstitutionalContributorsCount },
})}
</Badge>
</div>
<Typography>
{translator.t('section-2.used-for-fees', {
context: { value: paymentFees, currency: params.currency },
{translator.t('section-2.past-costs', {
context: {
value: totalCosts,
currency: params.currency,
},
})}
</Typography>
<Typography>
{translator.t('section-2.used-for-operating-costs', {
{translator.t('section-2.future-costs', {
context: {
value: paymentFees + transactionFees + operatingCosts + otherCosts,
value: contributionStats.totalInstitutionalContributionsAmount - totalCosts,
currency: params.currency,
},
})}
Expand Down
Loading
Loading