Skip to content

Commit

Permalink
Merge branch 'main' into feature/draw-transparency
Browse files Browse the repository at this point in the history
  • Loading branch information
CluEleSsUK authored Sep 18, 2023
2 parents 09ec782 + 8634cc6 commit 9830b58
Show file tree
Hide file tree
Showing 29 changed files with 1,333 additions and 1,418 deletions.
2 changes: 1 addition & 1 deletion admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"@mui/x-date-pickers": "^6.12.1",
"@socialincome/shared": "^1.0.0",
"algoliasearch": "^4.19.1",
"firebase": "^10.3.1",
"firebase": "^9.23.0",
"firecms": "2.0.5",
"lodash": "^4.17.21",
"luxon": "^3.4.2",
Expand Down
1,394 changes: 661 additions & 733 deletions package-lock.json

Large diffs are not rendered by default.

59 changes: 48 additions & 11 deletions shared/locales/de/website-transparency.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,51 @@
{
"finances": {
"title": "Aktuelle Finanzen",
"totalContributionsByMonthAndType": "Total Spenden",
"totalInstitutionalContributionsByMonthTooltip": "Institutionelle Spenden : {{value, currency}}",
"totalIndividualContributionsByMonthTooltip": "Individuellen Spenden: {{value, currency}}",
"totalPayout": "Total Auszahlungen an Empfänger",
"totalPayoutTooltip": "Auszahlungen an Empfänger: {{value, currency}}",
"totalRecipients": "Empfänger insgesamt",
"totalRecipientsTooltip": "Anzahl Empfänger bis zum ausgewählten Monat: {{value}}",
"monthlyPayoutPerSocialIncome": "Monatliche Auszahlung pro Social Income",
"monthlyPayoutPerSocialIncomeTooltip": "Wert eines Social Incomes: {{value, currency}}"
"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.",
"since-march-2020": "Seit März 2020",
"totalContributions": "{{ contributorCount }} Spender:innen spendeten insgesamt {{value, currency}}",
"totalPayments": "{{ value }} Auszahlungen direkt aufs Mobiltelefon",
"totalRecipients": "{{ value }} Empfänger:innen wurden aufgenommen in unserem 3-Jahresprogramm"
},
"section-2": {
"title": "Wie wird Social Income finanziert?",
"donations": "Spenden",
"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 }}"
},
"section-3": {
"title": "Von wo kommen die Spender:innen?",
"subtitle": "Wir erhalten Spenden aus {{ value }} Ländern.",
"country-amount": "{{ value, currency }} von {{ contributorsCount }} Spender:innen",
"all-countries": "Alle Länder anzeigen"
},
"section-4": {
"title": "Wie werden Spenden ausbezahlt?",
"subtitle": "Individuelle Spenden werden zu 100% ausbezahlt. Institutionell Spenden werden gebraucht um die operativen Kosten zu decken.",
"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",
"payment-fees": "Zustellgebühren: {{ value, currency }}",
"payment-fees-tooltip": "Dies beinhaltet die Transaktionsgebühren von Zahlungsportalen wie Stripe.",
"transaction-fees": "Transaktionsgebühren: {{ value, currency }}",
"transaction-fees-tooltip": "Dies beinhaltet Kontogebühren, Geldwechselgebühren, sowie Währungsverluste.",
"operating-costs": "Operative Kosten: {{ value, currency }}",
"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",
"covers-payments-2": "{{ recipientsCount }} Empfänger:innen für einen Monat",
"reserves-text": "Wir halten liquide Mittel um Zahlungen über mehrere Monate ohne Neugeld finanzieren zu können."
}
}
60 changes: 49 additions & 11 deletions shared/locales/en/website-transparency.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,52 @@
{
"finances": {
"title": "Current Finances",
"totalContributionsByMonthAndType": "Total Contributions",
"totalInstitutionalContributionsByMonthTooltip": "Total Institutional Contributions: {{value, currency}}",
"totalIndividualContributionsByMonthTooltip": "Total Individual Contributions: {{value, currency}}",
"totalPayout": "Total Payout",
"totalPayoutTooltip": "Total Payouts: {{value, currency}}",
"totalRecipients": "Total Recipients",
"totalRecipientsTooltip": "Total Recipients till selected month: {{value}}",
"monthlyPayoutPerSocialIncome": "Monthly Payout Per Social Income",
"monthlyPayoutPerSocialIncomeTooltip": "Social Income Value: {{value, currency}}"
"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.",
"since-march-2020": "Since March 2020",
"totalContributions": "{{ contributorCount }} contributors donated a total of {{value, currency}}",
"totalPayments": "{{ value }} mobile money payments made",
"totalRecipients": "{{ value }} recipients enrolled for a 3-year program"
},
"section-2": {
"title": "How is Social Income funded?",
"donations": "Donations",
"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 }}"
},
"section-3": {
"title": "Where are our contributors from?",
"subtitle": "We received contributions from {{ value }} countries.",
"country-amount": "{{ value, currency }} from {{ contributorsCount }} contributors",
"all-countries": "Show All Countries"
},
"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.",
"expenses": "Expenses",
"payments-total": "{{ value, currency }} paid out to {{ recipientsCount }} recipients",
"payments-last-month": "Last month: {{ value, currency }} to {{ recipientsCount }} recipients",

"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.",
"transaction-fees": "Transaction fees: {{ value, currency }}",
"transaction-fees-tooltip": "This includes bank account fees, exchange rate fees, or foreign currency losses.",
"operating-costs": "Operating costs: {{ value, currency }}",
"operating-costs-tooltip": "This includes salaries, fundraising, and other costs to run the organization.",
"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",
"reserves-text": "We keep liquid funds to make sure we can cover payments over multiple months without being dependent on contributions."
}
}
26 changes: 13 additions & 13 deletions shared/src/utils/stats/ContributionStatsCalculator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,52 +29,52 @@ test('building ContributionStatsCalculator', async () => {
});

test('calculate overall contributions', async () => {
expect(calculator.totalContributions()).toEqual(2400);
expect(calculator.totalContributionsAmount()).toEqual(2400);
});

test('calculate contributions by currency', async () => {
expect(calculator.totalContributionsByCurrency()).toEqual(
expect.arrayContaining([
{ amount: 400, currency: 'USD' },
{ amount: 2000, currency: 'CHF' },
{ amount: 400, currency: 'USD', usersCount: 1 },
{ amount: 2000, currency: 'CHF', usersCount: 1 },
]),
);
});

test('calculate contributions by isInstitutuion', async () => {
expect(calculator.totalContributionsByIsInstitution()).toEqual(
expect.arrayContaining([
{ amount: 400, isInstitution: 'false' },
{ amount: 2000, isInstitution: 'true' },
{ amount: 400, isInstitution: 'false', usersCount: 1 },
{ amount: 2000, isInstitution: 'true', usersCount: 1 },
]),
);
});

test('calculate contributions by country', async () => {
expect(calculator.totalContributionsByCountry()).toEqual(
expect.arrayContaining([
{ amount: 400, country: 'US' },
{ amount: 2000, country: 'CH' },
{ amount: 400, country: 'US', usersCount: 1 },
{ amount: 2000, country: 'CH', usersCount: 1 },
]),
);
});

test('calculate contributions by source', async () => {
expect(calculator.totalContributionsBySource()).toEqual(
expect.arrayContaining([
{ amount: 2000, source: 'benevity' },
{ amount: 400, source: 'stripe' },
{ amount: 2000, source: 'benevity', usersCount: 1 },
{ amount: 400, source: 'stripe', usersCount: 1 },
]),
);
});

test('calculate contributions by first day in month', async () => {
expect(calculator.totalContributionsByMonth()).toEqual(
expect.arrayContaining([
{ amount: 1100, month: '2023-01' },
{ amount: 100, month: '2023-02' },
{ amount: 100, month: '2023-03' },
{ amount: 1100, month: '2023-04' },
{ amount: 1100, month: '2023-01', usersCount: 2 },
{ amount: 100, month: '2023-02', usersCount: 1 },
{ amount: 100, month: '2023-03', usersCount: 1 },
{ amount: 1100, month: '2023-04', usersCount: 2 },
]),
);
});
Expand Down
33 changes: 25 additions & 8 deletions shared/src/utils/stats/ContributionStatsCalculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ import { getLatestExchangeRate } from '../exchangeRates';
import { cumulativeSum, StatsEntry } from './utils';

export interface ContributionStats {
totalContributions: number;
totalContributionsAmount: number;
totalContributionsCount: number;
totalContributorsCount: number;
totalIndividualContributionsAmount: number;
totalIndividualContributorsCount: number;
totalInstitutionalContributionsAmount: number;
totalInstitutionalContributorsCount: number;
totalContributionsByCurrency: Record<string, string | number>[];
totalContributionsByIsInstitution: StatsEntry[];
totalContributionsByCountry: StatsEntry[];
Expand Down Expand Up @@ -38,7 +44,7 @@ type ContributionStatsEntry = {
export class ContributionStatsCalculator {
readonly contributions: _.Collection<ContributionStatsEntry>;

constructor(contributions: _.Collection<ContributionStatsEntry>) {
private constructor(contributions: _.Collection<ContributionStatsEntry>) {
this.contributions = contributions;
}

Expand Down Expand Up @@ -78,7 +84,7 @@ export class ContributionStatsCalculator {
}
return {
userId: userDoc.id,
isInstitution: user.institution ?? false,
isInstitution: Boolean(user.institution),
country: user.location?.toUpperCase() ?? 'CH',
amount: contribution.amount_chf * exchangeRate,
paymentFees: contribution.fees_chf * exchangeRate,
Expand All @@ -95,8 +101,8 @@ export class ContributionStatsCalculator {
return new ContributionStatsCalculator(_(contributions.flat()));
}

totalContributions = () => {
return this.contributions.sumBy((c) => c.amount);
totalContributionsAmount = () => {
return this.contributions.sumBy('amount');
};

totalContributionsByCurrency = () => {
Expand Down Expand Up @@ -128,7 +134,8 @@ export class ContributionStatsCalculator {
.groupBy(attribute)
.map((contributions, group) => ({
[attribute]: group,
amount: _.sumBy(contributions, (c) => c.amount),
amount: _.sumBy(contributions, 'amount'),
usersCount: _.size(_.countBy(contributions, 'userId')),
}))
.sortBy((x) => x[attribute])
.value();
Expand Down Expand Up @@ -166,10 +173,20 @@ export class ContributionStatsCalculator {
};

allStats = (): ContributionStats => {
const totalContributionsByIsInstitution = this.totalContributionsByIsInstitution();
const totalIndividualContributions = totalContributionsByIsInstitution.find((e) => e.isInstitution === 'false');
const totalInstitutionalContributions = totalContributionsByIsInstitution.find((e) => e.isInstitution === 'true');

return {
totalContributions: this.totalContributions(),
totalContributionsAmount: this.totalContributionsAmount(),
totalContributionsCount: this.contributions.size(),
totalContributorsCount: this.contributions.groupBy('userId').size(),
totalIndividualContributionsAmount: totalIndividualContributions?.amount || 0,
totalIndividualContributorsCount: totalIndividualContributions?.usersCount || 0,
totalInstitutionalContributionsAmount: totalInstitutionalContributions?.amount || 0,
totalInstitutionalContributorsCount: totalInstitutionalContributions?.usersCount || 0,
totalContributionsByCurrency: this.totalContributionsByCurrency(),
totalContributionsByIsInstitution: this.totalContributionsByIsInstitution(),
totalContributionsByIsInstitution: totalContributionsByIsInstitution,
totalContributionsByCountry: this.totalContributionsByCountry(),
totalContributionsBySource: this.totalContributionsBySource(),
totalContributionsByMonth: this.totalContributionsByMonth(),
Expand Down
19 changes: 12 additions & 7 deletions shared/src/utils/stats/PaymentStatsCalculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import { getLatestExchangeRate } from '../exchangeRates';
import { cumulativeSum, groupByAndSort, StatsEntry } from './utils';

export interface PaymentStats {
totalPayments: number;
totalPaymentsAmount: number;
totalPaymentsCount: number;
totalPaymentsByMonth: StatsEntry[];
socialIncomesByMonth: StatsEntry[];
cumulativePaymentsByMonth: StatsEntry[];
cumulativeRecipientsByMonth: StatsEntry[];
meanPaymentsByMonth: StatsEntry[];
totalRecipientsCount: number;
}

/**
Expand All @@ -20,7 +22,7 @@ export interface PaymentStats {
type PaymentStatsEntry = {
recipientId: string;
amount: number;
amountSle: number;
amountSLE: number;
month: string;
};

Expand Down Expand Up @@ -50,7 +52,7 @@ export class PaymentStatsCalculator {
return {
recipientId: paymentDoc.ref.parent?.parent?.id,
amount: payment.amount_chf! * exchangeRate,
amountSle: payment.currency === 'SLE' ? payment.amount : payment.amount / 1000,
amountSLE: payment.currency === 'SLE' ? payment.amount : payment.amount / 1000, // some payments are in SLE, some in SLL
month: toDateTime(payment.payment_at).toFormat('yyyy-MM'),
} as PaymentStatsEntry;
});
Expand Down Expand Up @@ -95,10 +97,11 @@ export class PaymentStatsCalculator {
) => {
return this.payments
.groupBy(groupAttribute)
.map((contributions, group) => ({
.map((payments, group) => ({
[groupAttribute]: group,
payment: aggregate(contributions, 'amount'),
paymentSle: aggregate(contributions, 'amountSle'),
amount: aggregate(payments, 'amount'),
amountSLE: aggregate(payments, 'amountSLE'),
recipientsCount: _.size(_.groupBy(payments, 'recipientId')),
}))
.sortBy((x) => x[groupAttribute])
.value();
Expand Down Expand Up @@ -140,12 +143,14 @@ export class PaymentStatsCalculator {

allStats = (): PaymentStats => {
return {
totalPayments: this.totalPayments(),
totalPaymentsAmount: this.totalPayments(),
totalPaymentsCount: this.payments.size(),
totalPaymentsByMonth: this.totalPaymentsByMonth(),
cumulativePaymentsByMonth: this.cumulativePaymentsByMonth(),
cumulativeRecipientsByMonth: this.cumulativeRecipientsByMonth(),
meanPaymentsByMonth: this.meanPaymentsByMonth(),
socialIncomesByMonth: this.socialIncomesByMonth(),
totalRecipientsCount: this.payments.groupBy('recipientId').size(),
} as PaymentStats;
};
}
2 changes: 1 addition & 1 deletion ui/daisyui-themes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const defaultTheme = {
neutral: '#c1c4c7',
neutral: '#a4a4b2',
'neutral-content': '#1E293B',

'base-100': '#fefffe',
Expand Down
8 changes: 8 additions & 0 deletions ui/src/components/badge/badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use client';

import { Badge as DaisyUIBadge } from 'react-daisyui';
import { BadgeProps } from 'react-daisyui/dist/Badge/Badge';

export function Badge(props: BadgeProps) {
return <DaisyUIBadge {...props} />;
}
1 change: 1 addition & 0 deletions ui/src/components/badge/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './badge';
2 changes: 0 additions & 2 deletions ui/src/components/index.ts

This file was deleted.

1 change: 1 addition & 0 deletions ui/src/components/tooltip/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './tooltip';
8 changes: 8 additions & 0 deletions ui/src/components/tooltip/tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use client';

import { Tooltip as DaisyUITooltip } from 'react-daisyui';
import { TooltipProps } from 'react-daisyui/dist/Tooltip/Tooltip';

export function Tooltip(props: TooltipProps) {
return <DaisyUITooltip {...props} />;
}
2 changes: 2 additions & 0 deletions ui/src/components/typography/typography.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const FONT_WEIGHT_MAP: { [key in FontWeight]: string } = {
type FontColor = Extract<
Color,
| 'base-content'
| 'primary'
| 'primary-content'
| 'secondary'
| 'secondary-focus'
Expand All @@ -51,6 +52,7 @@ type FontColor = Extract<

const FONT_COLOR_MAP: { [key in FontColor]: string } = {
'base-content': 'text-base-content',
primary: 'text-primary',
'primary-content': 'text-primary-content',
secondary: 'text-secondary',
'secondary-focus': 'text-secondary-focus',
Expand Down
Loading

0 comments on commit 9830b58

Please sign in to comment.