format donate options via API

This commit is contained in:
ChaosExAnima 2025-09-11 19:18:10 +02:00
parent d8522e4c5c
commit 2f1aae289e
No known key found for this signature in database
GPG Key ID: 8F2B333100FB6117
3 changed files with 41 additions and 29 deletions

View File

@ -34,7 +34,7 @@ export function useDonateApi() {
if (!seed) { if (!seed) {
return; return;
} }
fetchCampaign({ locale: LOCALE, seed, source: 'web' }) fetchCampaign({ locale: LOCALE, seed })
.then((res) => { .then((res) => {
setResponse(res); setResponse(res);
}) })
@ -71,7 +71,8 @@ async function fetchCampaign(
url.searchParams.append(key, value.toString()); url.searchParams.append(key, value.toString());
} }
} }
url.searchParams.append('platform', 'web'); url.searchParams.append('platform', 'android');
url.searchParams.append('source', 'menu');
const response = await fetch(url); const response = await fetch(url);
if (!response.ok) { if (!response.ok) {
@ -83,6 +84,5 @@ async function fetchCampaign(
interface DonateServerRequest { interface DonateServerRequest {
locale: string; locale: string;
seed: number; seed: number;
source: string;
return_url?: string; return_url?: string;
} }

View File

@ -48,6 +48,10 @@
} }
} }
.title {
flex-grow: 1;
}
.button.toggle:not(.active) { .button.toggle:not(.active) {
background-color: inherit; background-color: inherit;
border: 2px solid var(--button-color); border: 2px solid var(--button-color);

View File

@ -1,7 +1,7 @@
import type { FC, SyntheticEvent } from 'react'; import type { FC, SyntheticEvent } from 'react';
import { forwardRef, useCallback, useMemo, useState } from 'react'; import { forwardRef, useCallback, useMemo, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
@ -24,6 +24,9 @@ interface DonateModalProps {
const messages = defineMessages({ const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' }, close: { id: 'lightbox.close', defaultMessage: 'Close' },
one_time: { id: 'donate.frequency.one_time', defaultMessage: 'Just once' },
monthly: { id: 'donate.frequency.monthly', defaultMessage: 'Monthly' },
yearly: { id: 'donate.frequency.yearly', defaultMessage: 'Yearly' },
}); });
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- React throws a warning if not set. // eslint-disable-next-line @typescript-eslint/no-unused-vars -- React throws a warning if not set.
@ -36,9 +39,8 @@ const DonateModal: FC<DonateModalProps> = forwardRef(({ onClose }, ref) => {
<div className='modal-root__modal dialog-modal donate_modal'> <div className='modal-root__modal dialog-modal donate_modal'>
<div className='dialog-modal__content'> <div className='dialog-modal__content'>
<header className='row'> <header className='row'>
<span className='dialog-modal__header__title'> <span className='dialog-modal__header__title title'>
By supporting Mastodon, you help sustain a global network that {donationData?.donation_message}
values people over profit. Will you join us today?
</span> </span>
<IconButton <IconButton
className='dialog-modal__header__close' className='dialog-modal__header__close'
@ -66,6 +68,8 @@ const DonateModal: FC<DonateModalProps> = forwardRef(({ onClose }, ref) => {
DonateModal.displayName = 'DonateModal'; DonateModal.displayName = 'DonateModal';
const DonateForm: FC<{ data: DonateServerResponse }> = ({ data }) => { const DonateForm: FC<{ data: DonateServerResponse }> = ({ data }) => {
const intl = useIntl();
const [frequency, setFrequency] = useState<DonationFrequency>('one_time'); const [frequency, setFrequency] = useState<DonationFrequency>('one_time');
const handleFrequencyToggle = useCallback((value: DonationFrequency) => { const handleFrequencyToggle = useCallback((value: DonationFrequency) => {
return () => { return () => {
@ -84,20 +88,22 @@ const DonateForm: FC<{ data: DonateServerResponse }> = ({ data }) => {
); );
const [amount, setAmount] = useState( const [amount, setAmount] = useState(
() => data.amounts[frequency][data.default_currency]?.[0] ?? 'EUR', () => data.amounts[frequency][data.default_currency]?.[0] ?? 1000,
); );
const handleAmountChange = useCallback((event: SyntheticEvent) => { const handleAmountChange = useCallback((event: SyntheticEvent) => {
if ( let newAmount = 1;
event.target instanceof HTMLButtonElement || if (event.target instanceof HTMLButtonElement) {
event.target instanceof HTMLInputElement newAmount = Number.parseInt(event.target.value);
) { } else if (event.target instanceof HTMLInputElement) {
setAmount(Number.parseInt(event.target.value)); newAmount = event.target.valueAsNumber * 100;
} }
setAmount(newAmount);
}, []); }, []);
const amountOptions: SelectItem[] = useMemo(() => { const amountOptions: SelectItem[] = useMemo(() => {
const formatter = new Intl.NumberFormat('en', { const formatter = new Intl.NumberFormat('en', {
style: 'currency', style: 'currency',
currency, currency,
maximumFractionDigits: 0,
}); });
return Object.values(data.amounts[frequency][currency] ?? {}).map( return Object.values(data.amounts[frequency][currency] ?? {}).map(
(value) => ({ (value) => ({
@ -110,18 +116,14 @@ const DonateForm: FC<{ data: DonateServerResponse }> = ({ data }) => {
return ( return (
<> <>
<div className='row'> <div className='row'>
<ToggleButton {(Object.keys(data.amounts) as DonationFrequency[]).map((freq) => (
active={frequency === 'one_time'} <ToggleButton
onClick={handleFrequencyToggle('one_time')} key={freq}
> active={frequency === freq}
One Time onClick={handleFrequencyToggle(freq)}
</ToggleButton> text={intl.formatMessage(messages[freq])}
<ToggleButton />
active={frequency === 'monthly'} ))}
onClick={handleFrequencyToggle('monthly')}
>
Monthly
</ToggleButton>
</div> </div>
<div className='row row--select'> <div className='row row--select'>
@ -134,8 +136,8 @@ const DonateForm: FC<{ data: DonateServerResponse }> = ({ data }) => {
<input <input
type='number' type='number'
min='1' min='1'
step='1' step='0.01'
value={amount} value={(amount / 100).toFixed(2)}
onChange={handleAmountChange} onChange={handleAmountChange}
/> />
</div> </div>
@ -153,12 +155,18 @@ const DonateForm: FC<{ data: DonateServerResponse }> = ({ data }) => {
</div> </div>
<Button className='submit' block type='submit'> <Button className='submit' block type='submit'>
Continue to payment <FormattedMessage
id='donate.continue'
defaultMessage='Continue to payment'
/>
<ExternalLinkIcon /> <ExternalLinkIcon />
</Button> </Button>
<p className='footer'> <p className='footer'>
You will be redirected to joinmastodon.org for secure payment <FormattedMessage
id='donate.redirect_notice'
defaultMessage='You will be redirected to joinmastodon.org for secure payment'
/>
</p> </p>
</> </>
); );