diff --git a/shared/locales/de/website-transparency.json b/shared/locales/de/website-transparency.json index f950d77fc..6c1277a22 100644 --- a/shared/locales/de/website-transparency.json +++ b/shared/locales/de/website-transparency.json @@ -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.", @@ -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?", @@ -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 }}", @@ -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", diff --git a/shared/locales/en/website-transparency.json b/shared/locales/en/website-transparency.json index d151d083c..30a3470c8 100644 --- a/shared/locales/en/website-transparency.json +++ b/shared/locales/en/website-transparency.json @@ -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.", @@ -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?", @@ -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.", @@ -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", diff --git a/shared/src/utils/stats/ContributionStatsCalculator.ts b/shared/src/utils/stats/ContributionStatsCalculator.ts index fbce72d5b..1084d03c8 100644 --- a/shared/src/utils/stats/ContributionStatsCalculator.ts +++ b/shared/src/utils/stats/ContributionStatsCalculator.ts @@ -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 { + static async build( + firestoreAdmin: FirestoreAdmin, + currency: string, + contributionFilter = (c: Contribution) => c.status === StatusKey.SUCCEEDED, + ): Promise { const exchangeRate = await getLatestExchangeRate(firestoreAdmin, currency); const getContributionsForUser = async (userId: string): Promise => { @@ -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())); diff --git a/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/info-card.tsx b/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/info-card.tsx index bc245c70a..776699331 100644 --- a/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/info-card.tsx +++ b/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/info-card.tsx @@ -1,4 +1,4 @@ -import { Typography } from '@socialincome/ui'; +import { Card, CardContent, Typography } from '@socialincome/ui'; import _ from 'lodash'; import { ReactElement } from 'react'; @@ -6,10 +6,8 @@ type TransparencyCardProps = { sectionTitle: string; title: string; text: string; - firstIcon?: ReactElement; firstContent?: ReactElement; - secondIcon?: ReactElement; secondContent?: ReactElement; }; @@ -18,30 +16,30 @@ export function InfoCard( { sectionTitle, title, text, firstIcon, firstContent, secondIcon, secondContent }: TransparencyCardProps, ) { return ( -
-
- {sectionTitle} - - {title} - - - {text} - -
-
- {!_.isNil(firstIcon) && !_.isNil(firstContent) && ( -
-
{firstIcon}
-
{firstContent}
-
- )} - {!_.isNil(secondIcon) && !_.isNil(secondContent) && ( -
-
{secondIcon}
-
{secondContent}
-
- )} -
-
+ + +
+ {sectionTitle} + + {title} + + {text} +
+
+ {!_.isNil(firstIcon) && !_.isNil(firstContent) && ( +
+
{firstIcon}
+
{firstContent}
+
+ )} + {!_.isNil(secondIcon) && !_.isNil(secondContent) && ( +
+
{secondIcon}
+
{secondContent}
+
+ )} +
+
+
); } diff --git a/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/page.tsx b/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/page.tsx index 9e7fbce9b..a6bf60fe1 100644 --- a/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/page.tsx +++ b/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/page.tsx @@ -42,14 +42,12 @@ export default async function Page({ params }: TransparencyPageProps) { const { contributionStats, paymentStats } = await getStats(currency); return ( -
+
-
- - - - -
+ + + +
); } diff --git a/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-1.tsx b/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-1.tsx index a434af963..4a3fd9a60 100644 --- a/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-1.tsx +++ b/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-1.tsx @@ -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'; @@ -25,23 +25,27 @@ export async function Section1({ params, paymentStats, contributionStats }: Sect ]; return ( -
-
- +
+
+ {translator.t('section-1.title-1')} - + {translator.t('section-1.title-2')}
- {translator.t('section-1.since-march-2020')} - {cards.map((card, index) => ( -
- - {card} - -
- ))} +
+ {translator.t('section-1.since-march-2020')} + {cards.map((card, index) => ( + + + + {card} + + + + ))} +
); } diff --git a/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-2.tsx b/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-2.tsx index 5a31decd8..3031d3198 100644 --- a/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-2.tsx +++ b/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-2.tsx @@ -8,13 +8,16 @@ 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 ( -
- +
+ {translator.t('section-2.title')} } firstContent={
@@ -31,15 +34,15 @@ export async function Section2({ params, contributionStats, paymentStats }: Sect {translator.t('section-2.contributions-from', { context: { value: contributionStats.totalIndividualContributionsAmount, currency: params.currency }, })} - - {translator.t('section-2.individuals', { - context: { count: contributionStats.totalIndividualContributorsCount }, - })} - + + {translator.t('section-2.individuals', { + context: { count: contributionStats.totalIndividualContributorsCount }, + })} +
- {translator.t('past-payouts', { + {translator.t('section-2.past-payouts', { context: { value: paymentStats.totalPaymentsAmount, currency: params.currency, @@ -47,7 +50,7 @@ export async function Section2({ params, contributionStats, paymentStats }: Sect })} - {translator.t('future-payouts', { + {translator.t('section-2.future-payouts', { context: { value: contributionStats.totalIndividualContributionsAmount - paymentStats.totalPaymentsAmount, currency: params.currency, @@ -67,22 +70,25 @@ export async function Section2({ params, contributionStats, paymentStats }: Sect currency: params.currency, }, })} - - {translator.t('section-2.institutions', { - context: { count: contributionStats.totalInstitutionalContributorsCount }, - })} - + + {translator.t('section-2.institutions', { + context: { count: contributionStats.totalInstitutionalContributorsCount }, + })} +
- {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, + }, })} - {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, }, })} diff --git a/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-3-cards.tsx b/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-3-cards.tsx index ce8c24236..b215c0587 100644 --- a/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-3-cards.tsx +++ b/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-3-cards.tsx @@ -1,6 +1,6 @@ 'use client'; -import { Button, Typography } from '@socialincome/ui'; +import { Button, Card, CardContent, Typography } from '@socialincome/ui'; import { Children, PropsWithChildren, useState } from 'react'; /** @@ -21,16 +21,18 @@ type CountryCardProps = { /* eslint-disable @next/next/no-img-element */ export function CountryCard({ country, translations }: CountryCardProps) { return ( -
  • -
    - -
    - - {translations.country} - - {translations.total} -
    -
    +
  • + + + +
    + + {translations.country} + + {translations.total} +
    +
    +
  • ); } @@ -39,12 +41,12 @@ export function CountryCardList({ children, buttonText }: PropsWithChildren<{ bu const [expanded, setExpanded] = useState(false); return ( -
    +
      {!expanded ? Children.toArray(children).slice(0, 6) : children}
    {!expanded && ( - )} diff --git a/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-3.tsx b/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-3.tsx index 749822849..2ef554e76 100644 --- a/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-3.tsx +++ b/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-3.tsx @@ -19,7 +19,7 @@ export async function Section3({ params, contributionStats }: SectionProps) { {translator.t('section-3.title')} - + {translator.t('section-3.subtitle', { context: { value: totalContributionsByCountry.length } })} diff --git a/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-4.tsx b/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-4.tsx index 1b99f0831..cb1e212a1 100644 --- a/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-4.tsx +++ b/website/src/app/[lang]/[region]/(website)/transparency/finances/[currency]/section-4.tsx @@ -9,15 +9,15 @@ import _ from 'lodash'; import { InfoCard } from './info-card'; import { SectionProps } from './page'; -// TODO: fix tooltips on mobile export async function Section4({ params, paymentStats, contributionStats }: SectionProps) { const translator = await Translator.getInstance({ language: params.lang, namespaces: ['website-transparency'] }); const paymentFees = _.sumBy(contributionStats.totalPaymentFeesByIsInstitution, 'amount'); // TODO: make these dynamic - const transactionFees = 8600; - const operatingCosts = 9300; - const otherCosts = 9600; + const transactionFees = 7800; // "Exchange rate and currency losses" + "Account fees" + "Transaction fees" (without stripe fees) + const operatingCosts = 9300; // "Total operative expenses" + const otherCosts = 9600; // "Other project costs" + const totalExpenses = paymentFees + transactionFees + operatingCosts + otherCosts + paymentStats.totalPaymentsAmount; const totalReserves = contributionStats.totalContributionsAmount - totalExpenses; const exchangeRateSLE = await getLatestExchangeRate(firestoreAdmin, 'SLE'); @@ -35,7 +35,7 @@ export async function Section4({ params, paymentStats, contributionStats }: Sect } firstContent={
    @@ -60,7 +60,7 @@ export async function Section4({ params, paymentStats, contributionStats }: Sect })} - {translator.t('future-payouts', { + {translator.t('section-4.future-payouts', { context: { value: contributionStats.totalIndividualContributionsAmount - paymentStats.totalPaymentsAmount, currency: params.currency, @@ -89,12 +89,13 @@ export async function Section4({ params, paymentStats, contributionStats }: Sect currency: params.currency, }, })} - + - {translator.t('section-4.payment-fees-tooltip')} + {/*{translator.t('section-4.payment-fees-tooltip')}*/} + Stripe fees @@ -105,9 +106,17 @@ export async function Section4({ params, paymentStats, contributionStats }: Sect currency: params.currency, }, })} - {/**/} - {/* */} - {/**/} + + + + + + {/*{translator.t('section-4.transaction-fees-tooltip')}*/} + + "Exchange rate and currency losses" + "Account fees" + "Transaction fees" (without Stripe fees) + + + {translator.t('section-4.operating-costs', { @@ -116,9 +125,15 @@ export async function Section4({ params, paymentStats, contributionStats }: Sect currency: params.currency, }, })} - {/**/} - {/* */} - {/**/} + + + + + + {/*{translator.t('section-4.operating-costs-tooltip')}*/} + "Total operative expenses" + + {translator.t('section-4.other-costs', { @@ -127,9 +142,15 @@ export async function Section4({ params, paymentStats, contributionStats }: Sect currency: params.currency, }, })} - {/**/} - {/* */} - {/**/} + + + + + + {/*{translator.t('section-4.other-costs-tooltip')}*/} + "Other project costs" + +
    } @@ -140,7 +161,7 @@ export async function Section4({ params, paymentStats, contributionStats }: Sect title={translator.t('amount', { context: { value: totalReserves, currency: params.currency }, })} - text={translator.t('amount-since-march-2020')} + text={translator.t('section-4.amount-since-march-2020')} firstIcon={} firstContent={
    diff --git a/website/src/components/footer/footer.tsx b/website/src/components/footer/footer.tsx index 25e1ea8ed..b0546caab 100644 --- a/website/src/components/footer/footer.tsx +++ b/website/src/components/footer/footer.tsx @@ -130,7 +130,7 @@ export default async function Footer({ lang, region }: DefaultParams) {
    -
    +
    ) : ( - +