-
+
);
diff --git a/app/soapbox/features/ui/components/funding_panel.js b/app/soapbox/features/ui/components/funding_panel.js
deleted file mode 100644
index 45010360c..000000000
--- a/app/soapbox/features/ui/components/funding_panel.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import { Map as ImmutableMap } from 'immutable';
-import React from 'react';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { injectIntl } from 'react-intl';
-import { connect } from 'react-redux';
-
-import { fetchPatronInstance } from 'soapbox/actions/patron';
-import Icon from 'soapbox/components/icon';
-
-import ProgressBar from '../../../components/progress_bar';
-
-const moneyFormat = amount => (
- new Intl
- .NumberFormat('en-US', {
- style: 'currency',
- currency: 'usd',
- notation: 'compact',
- })
- .format(amount/100)
-);
-
-class FundingPanel extends ImmutablePureComponent {
-
- componentDidMount() {
- this.props.dispatch(fetchPatronInstance());
- }
-
- render() {
- const { patron } = this.props;
- if (patron.isEmpty()) return null;
-
- const amount = patron.getIn(['funding', 'amount']);
- const goal = patron.getIn(['goals', '0', 'amount']);
- const goal_text = patron.getIn(['goals', '0', 'text']);
- const goal_reached = amount >= goal;
- let ratio_text;
-
- if (goal_reached) {
- ratio_text = <>
{moneyFormat(goal)} per month
— reached!>;
- } else {
- ratio_text = <>
{moneyFormat(amount)} out of {moneyFormat(goal)} per month>;
- }
-
- return (
-
-
-
-
- Funding Goal
-
-
-
-
- {ratio_text}
-
-
-
- {goal_text}
-
-
Donate
-
-
- );
- }
-
-}
-
-const mapStateToProps = state => {
- return {
- patron: state.getIn(['patron', 'instance'], ImmutableMap()),
- };
-};
-
-export default injectIntl(
- connect(mapStateToProps, null, null, {
- forwardRef: true,
- },
- )(FundingPanel));
diff --git a/app/soapbox/features/ui/components/funding_panel.tsx b/app/soapbox/features/ui/components/funding_panel.tsx
new file mode 100644
index 000000000..7c104c2b7
--- /dev/null
+++ b/app/soapbox/features/ui/components/funding_panel.tsx
@@ -0,0 +1,79 @@
+import React, { useEffect } from 'react';
+import { FormattedMessage } from 'react-intl';
+import { useHistory } from 'react-router-dom';
+
+import { fetchPatronInstance } from 'soapbox/actions/patron';
+import { Widget, Button, Text } from 'soapbox/components/ui';
+import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
+
+import ProgressBar from '../../../components/progress_bar';
+
+/** Open link in a new tab. */
+// https://stackoverflow.com/a/28374344/8811886
+const openInNewTab = (href: string): void => {
+ Object.assign(document.createElement('a'), {
+ target: '_blank',
+ href: href,
+ }).click();
+};
+
+/** Formats integer to USD string. */
+const moneyFormat = (amount: number): string => (
+ new Intl
+ .NumberFormat('en-US', {
+ style: 'currency',
+ currency: 'usd',
+ notation: 'compact',
+ })
+ .format(amount/100)
+);
+
+const FundingPanel: React.FC = () => {
+ const history = useHistory();
+ const dispatch = useAppDispatch();
+ const patron = useAppSelector(state => state.patron.instance);
+
+ useEffect(() => {
+ dispatch(fetchPatronInstance());
+ }, []);
+
+ if (patron.funding.isEmpty() || patron.goals.isEmpty()) return null;
+
+ const amount = patron.getIn(['funding', 'amount']) as number;
+ const goal = patron.getIn(['goals', '0', 'amount']) as number;
+ const goalText = patron.getIn(['goals', '0', 'text']) as string;
+ const goalReached = amount >= goal;
+ let ratioText;
+
+ if (goalReached) {
+ ratioText = <>
{moneyFormat(goal)} per month
— reached!>;
+ } else {
+ ratioText = <>
{moneyFormat(amount)} out of {moneyFormat(goal)} per month>;
+ }
+
+ const handleDonateClick = () => {
+ openInNewTab(patron.url);
+ };
+
+ return (
+
}
+ onActionClick={handleDonateClick}
+ >
+
+ {ratioText}
+
+
+
+ {goalText}
+
+
+
+
+
+ );
+};
+
+export default FundingPanel;
diff --git a/app/soapbox/normalizers/account.ts b/app/soapbox/normalizers/account.ts
index 2ca866c18..eac930ec9 100644
--- a/app/soapbox/normalizers/account.ts
+++ b/app/soapbox/normalizers/account.ts
@@ -16,6 +16,7 @@ import { normalizeEmoji } from 'soapbox/normalizers/emoji';
import { unescapeHTML } from 'soapbox/utils/html';
import { mergeDefined, makeEmojiMap } from 'soapbox/utils/normalizers';
+import type { PatronAccount } from 'soapbox/reducers/patron';
import type { Emoji, Field, EmbeddedEntity } from 'soapbox/types/entities';
// https://docs.joinmastodon.org/entities/account/
@@ -57,7 +58,7 @@ export const AccountRecord = ImmutableRecord({
moderator: false,
note_emojified: '',
note_plain: '',
- patron: ImmutableMap
(),
+ patron: null as PatronAccount | null,
relationship: ImmutableList>(),
should_refetch: false,
staff: false,
diff --git a/app/soapbox/pages/home_page.js b/app/soapbox/pages/home_page.js
index fcff5b1e3..68b41961a 100644
--- a/app/soapbox/pages/home_page.js
+++ b/app/soapbox/pages/home_page.js
@@ -11,6 +11,7 @@ import {
TrendsPanel,
SignUpPanel,
PromoPanel,
+ FundingPanel,
CryptoDonatePanel,
BirthdayPanel,
} from 'soapbox/features/ui/util/async-components';
@@ -33,7 +34,7 @@ const mapStateToProps = state => {
return {
me,
account: state.getIn(['accounts', me]),
- showFundingPanel: hasPatron,
+ hasPatron,
hasCrypto,
cryptoLimit,
features,
@@ -49,7 +50,7 @@ class HomePage extends ImmutablePureComponent {
}
render() {
- const { me, children, account, features, hasCrypto, cryptoLimit } = this.props;
+ const { me, children, account, features, hasPatron, hasCrypto, cryptoLimit } = this.props;
const acct = account ? account.get('acct') : '';
@@ -90,6 +91,11 @@ class HomePage extends ImmutablePureComponent {
{Component => }
)}
+ {hasPatron && (
+
+ {Component => }
+
+ )}
{hasCrypto && cryptoLimit > 0 && (
{Component => }
diff --git a/app/soapbox/reducers/patron.js b/app/soapbox/reducers/patron.js
deleted file mode 100644
index 66a8940d8..000000000
--- a/app/soapbox/reducers/patron.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import { Map as ImmutableMap, fromJS } from 'immutable';
-
-import {
- PATRON_INSTANCE_FETCH_SUCCESS,
- PATRON_ACCOUNT_FETCH_SUCCESS,
-} from '../actions/patron';
-
-const initialState = ImmutableMap();
-
-const normalizePatronAccount = (state, account) => {
- const normalized = fromJS(account).deleteAll(['url']);
- return state.setIn(['accounts', account.url], normalized);
-};
-
-export default function patron(state = initialState, action) {
- switch(action.type) {
- case PATRON_INSTANCE_FETCH_SUCCESS:
- return state.set('instance', fromJS(action.instance));
- case PATRON_ACCOUNT_FETCH_SUCCESS:
- return normalizePatronAccount(state, action.account);
- default:
- return state;
- }
-}
diff --git a/app/soapbox/reducers/patron.ts b/app/soapbox/reducers/patron.ts
new file mode 100644
index 000000000..edf4c1c43
--- /dev/null
+++ b/app/soapbox/reducers/patron.ts
@@ -0,0 +1,50 @@
+import {
+ Map as ImmutableMap,
+ List as ImmutableList,
+ Record as ImmutableRecord,
+ fromJS,
+} from 'immutable';
+
+import {
+ PATRON_INSTANCE_FETCH_SUCCESS,
+ PATRON_ACCOUNT_FETCH_SUCCESS,
+} from '../actions/patron';
+
+import type { AnyAction } from 'redux';
+
+const PatronAccountRecord = ImmutableRecord({
+ is_patron: false,
+ url: '',
+});
+
+const PatronInstanceRecord = ImmutableRecord({
+ funding: ImmutableMap(),
+ goals: ImmutableList(),
+ url: '',
+});
+
+const ReducerRecord = ImmutableRecord({
+ instance: PatronInstanceRecord() as PatronInstance,
+ accounts: ImmutableMap(),
+});
+
+type State = ReturnType;
+
+export type PatronAccount = ReturnType;
+export type PatronInstance = ReturnType;
+
+const normalizePatronAccount = (state: State, account: Record) => {
+ const normalized = PatronAccountRecord(account);
+ return state.setIn(['accounts', normalized.url], normalized);
+};
+
+export default function patron(state = ReducerRecord(), action: AnyAction) {
+ switch(action.type) {
+ case PATRON_INSTANCE_FETCH_SUCCESS:
+ return state.set('instance', PatronInstanceRecord(ImmutableMap(fromJS(action.instance))));
+ case PATRON_ACCOUNT_FETCH_SUCCESS:
+ return normalizePatronAccount(state, action.account);
+ default:
+ return state;
+ }
+}
diff --git a/app/soapbox/selectors/index.ts b/app/soapbox/selectors/index.ts
index 40f8105e3..abc8f1811 100644
--- a/app/soapbox/selectors/index.ts
+++ b/app/soapbox/selectors/index.ts
@@ -26,7 +26,7 @@ const getAccountMeta = (state: RootState, id: string) => state.accounts_
const getAccountAdminData = (state: RootState, id: string) => state.admin.users.get(id);
const getAccountPatron = (state: RootState, id: string) => {
const url = state.accounts.get(id)?.url;
- return state.patron.getIn(['accounts', url]);
+ return url ? state.patron.accounts.get(url) : null;
};
export const makeGetAccount = () => {
@@ -47,7 +47,7 @@ export const makeGetAccount = () => {
map.set('pleroma', meta.get('pleroma', ImmutableMap()).merge(base.get('pleroma', ImmutableMap()))); // Lol, thanks Pleroma
map.set('relationship', relationship);
map.set('moved', moved || null);
- map.set('patron', patron);
+ map.set('patron', patron || null);
map.setIn(['pleroma', 'admin'], admin);
});
});
diff --git a/app/styles/application.scss b/app/styles/application.scss
index 64478a2f5..8c5a05553 100644
--- a/app/styles/application.scss
+++ b/app/styles/application.scss
@@ -63,7 +63,6 @@
@import 'components/getting-started';
@import 'components/promo-panel';
@import 'components/still-image';
-@import 'components/badge';
@import 'components/theme-toggle';
@import 'components/trends';
@import 'components/wtf-panel';
diff --git a/app/styles/components/badge.scss b/app/styles/components/badge.scss
deleted file mode 100644
index 1710c6e47..000000000
--- a/app/styles/components/badge.scss
+++ /dev/null
@@ -1,23 +0,0 @@
-.badge {
- @apply inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-primary-600 text-white;
-
- &--patron {
- @apply bg-primary-600 text-white;
- }
-
- &--admin {
- @apply bg-black;
- }
-
- &--moderator {
- @apply bg-cyan-600 text-white;
- }
-
- &--bot {
- @apply bg-gray-100 text-gray-800;
- }
-
- &--opaque {
- @apply bg-white bg-opacity-75 text-gray-900;
- }
-}
diff --git a/app/styles/donations.scss b/app/styles/donations.scss
index c66ce0b00..6709189cc 100644
--- a/app/styles/donations.scss
+++ b/app/styles/donations.scss
@@ -198,16 +198,3 @@ body.admin {
padding: 15px;
}
}
-
-.progress-bar {
- height: 8px;
- width: 100%;
- border-radius: 4px;
- background: var(--background-color);
- overflow: hidden;
-
- &__progress {
- height: 100%;
- background: var(--brand-color);
- }
-}