mirror of
https://github.com/mastodon/mastodon.git
synced 2025-10-05 08:33:00 +00:00
create donation state
This commit is contained in:
parent
aa99edaf6d
commit
e22184c20e
72
app/javascript/mastodon/actions/donate.ts
Normal file
72
app/javascript/mastodon/actions/donate.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
import { createAction } from '@reduxjs/toolkit';
|
||||
|
||||
import { apiGetDonateData } from '../api/donate';
|
||||
import {
|
||||
createAppThunk,
|
||||
createDataLoadingThunk,
|
||||
} from '../store/typed_functions';
|
||||
|
||||
import { focusCompose, resetCompose } from './compose';
|
||||
import { closeModal, openModal } from './modal';
|
||||
|
||||
export const setDonateSeed = createAction<number>('donate/setSeed');
|
||||
|
||||
export const initializeDonate = createAppThunk(
|
||||
(_arg, { dispatch, getState }) => {
|
||||
if (!getState().donate.seed) {
|
||||
let seed = Math.floor(Math.random() * 99) + 1;
|
||||
try {
|
||||
const storedSeed = localStorage.getItem('donate_seed');
|
||||
if (storedSeed) {
|
||||
seed = Number.parseInt(storedSeed, 10);
|
||||
} else {
|
||||
localStorage.setItem('donate_seed', seed.toString());
|
||||
}
|
||||
} catch {
|
||||
// No local storage available, just set a seed for this session.
|
||||
}
|
||||
dispatch(setDonateSeed(seed));
|
||||
}
|
||||
void dispatch(fetchDonateData());
|
||||
},
|
||||
);
|
||||
|
||||
export const fetchDonateData = createDataLoadingThunk(
|
||||
'donate/fetch',
|
||||
(_args: unknown, { getState }) => {
|
||||
const state = getState();
|
||||
return apiGetDonateData({
|
||||
locale: state.meta.get('locale', 'en') as string,
|
||||
seed: state.donate.seed ?? 1, // If we somehow don't have the seed, just set it to 1.
|
||||
});
|
||||
},
|
||||
(data) => data, // This is needed for TypeScript to infer the correct overload.
|
||||
);
|
||||
|
||||
export const showDonateModal = createAppThunk(
|
||||
(_arg, { dispatch, getState }) => {
|
||||
const state = getState();
|
||||
const lastPoll = state.donate.nextPoll;
|
||||
if (!lastPoll || Date.now() >= lastPoll) {
|
||||
void dispatch(fetchDonateData());
|
||||
}
|
||||
dispatch(
|
||||
openModal({
|
||||
modalType: 'DONATE',
|
||||
modalProps: {},
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export const composeDonateShare = createAppThunk(
|
||||
(_arg, { dispatch, getState }) => {
|
||||
const state = getState();
|
||||
const shareText = state.donate.apiResponse?.donation_success_post;
|
||||
if (shareText) {
|
||||
dispatch(resetCompose());
|
||||
dispatch(focusCompose(shareText));
|
||||
}
|
||||
dispatch(closeModal({ modalType: 'DONATE', ignoreFocus: false }));
|
||||
},
|
||||
);
|
30
app/javascript/mastodon/api/donate.ts
Normal file
30
app/javascript/mastodon/api/donate.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import type {
|
||||
DonateServerRequest,
|
||||
DonateServerResponse,
|
||||
} from '../api_types/donate';
|
||||
|
||||
// TODO: Proxy this through the backend.
|
||||
const API_URL = 'https://api.joinmastodon.org/v1/donations/campaigns/active';
|
||||
|
||||
export const apiGetDonateData = async ({
|
||||
locale,
|
||||
seed,
|
||||
}: DonateServerRequest) => {
|
||||
// Create the URL with query parameters.
|
||||
const params = new URLSearchParams({
|
||||
locale,
|
||||
seed: seed.toString(),
|
||||
platform: 'web',
|
||||
source: 'menu',
|
||||
});
|
||||
const url = new URL(`${API_URL}?${params.toString()}`);
|
||||
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error fetching donation campaign: ${response.statusText}`);
|
||||
}
|
||||
if (response.status === 204) {
|
||||
return null;
|
||||
}
|
||||
return response.json() as Promise<DonateServerResponse>;
|
||||
};
|
23
app/javascript/mastodon/api_types/donate.ts
Normal file
23
app/javascript/mastodon/api_types/donate.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
export const donationFrequencyTypes = [
|
||||
'one_time',
|
||||
'monthly',
|
||||
'yearly',
|
||||
] as const;
|
||||
export type DonationFrequency = (typeof donationFrequencyTypes)[number];
|
||||
|
||||
export interface DonateServerRequest {
|
||||
locale: string;
|
||||
seed: number;
|
||||
}
|
||||
|
||||
export interface DonateServerResponse {
|
||||
id: string;
|
||||
amounts: Record<DonationFrequency, Record<string, number[]>>;
|
||||
donation_url: string;
|
||||
banner_message: string;
|
||||
banner_button_text: string;
|
||||
donation_message: string;
|
||||
donation_button_text: string;
|
||||
donation_success_post: string;
|
||||
default_currency: string;
|
||||
}
|
|
@ -11,6 +11,7 @@ import { connect } from 'react-redux';
|
|||
import { debounce } from 'lodash';
|
||||
|
||||
import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app';
|
||||
import { initializeDonate } from '@/mastodon/actions/donate';
|
||||
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers';
|
||||
import { fetchNotifications } from 'mastodon/actions/notification_groups';
|
||||
import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
|
||||
|
@ -394,6 +395,7 @@ class UI extends PureComponent {
|
|||
this.props.dispatch(expandHomeTimeline());
|
||||
this.props.dispatch(fetchNotifications());
|
||||
this.props.dispatch(fetchServerTranslationLanguages());
|
||||
this.props.dispatch(initializeDonate());
|
||||
|
||||
setTimeout(() => this.props.dispatch(fetchServer()), 3000);
|
||||
}
|
||||
|
|
36
app/javascript/mastodon/reducers/donate.ts
Normal file
36
app/javascript/mastodon/reducers/donate.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { createReducer } from '@reduxjs/toolkit';
|
||||
|
||||
import { fetchDonateData, setDonateSeed } from '../actions/donate';
|
||||
import type { DonateServerResponse } from '../api_types/donate';
|
||||
|
||||
interface DonateState {
|
||||
apiResponse?: DonateServerResponse;
|
||||
nextPoll?: number;
|
||||
isFetching: boolean;
|
||||
seed?: number;
|
||||
}
|
||||
|
||||
const initialState: DonateState = {
|
||||
isFetching: false,
|
||||
};
|
||||
|
||||
export const donateReducer = createReducer(initialState, (builder) => {
|
||||
builder
|
||||
.addCase(setDonateSeed, (state, action) => {
|
||||
state.seed = action.payload;
|
||||
})
|
||||
.addCase(fetchDonateData.pending, (state) => {
|
||||
state.isFetching = true;
|
||||
})
|
||||
.addCase(fetchDonateData.rejected, (state) => {
|
||||
state.isFetching = false;
|
||||
})
|
||||
.addCase(fetchDonateData.fulfilled, (state, action) => {
|
||||
if (action.payload) {
|
||||
state.apiResponse = action.payload;
|
||||
}
|
||||
// If we have data, poll in four hours, otherwise try again in one hour.
|
||||
state.nextPoll = Date.now() + 1000 * 60 * 60 * (action.payload ? 4 : 1);
|
||||
state.isFetching = false;
|
||||
});
|
||||
});
|
|
@ -12,6 +12,7 @@ import { composeReducer } from './compose';
|
|||
import { contextsReducer } from './contexts';
|
||||
import conversations from './conversations';
|
||||
import custom_emojis from './custom_emojis';
|
||||
import { donateReducer } from './donate';
|
||||
import { dropdownMenuReducer } from './dropdown_menu';
|
||||
import filters from './filters';
|
||||
import height_cache from './height_cache';
|
||||
|
@ -43,6 +44,7 @@ import user_lists from './user_lists';
|
|||
|
||||
const reducers = {
|
||||
announcements,
|
||||
donate: donateReducer,
|
||||
dropdownMenu: dropdownMenuReducer,
|
||||
timelines,
|
||||
meta,
|
||||
|
|
Loading…
Reference in New Issue
Block a user