mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-07-08 14:24:14 +01:00
Disable donations while offline
This commit is contained in:
@@ -8958,6 +8958,10 @@
|
||||
"messageformat": "Thank you for supporting Signal. Your contribution helps fuel the mission of protecting free expression and enabling secure global communication for millions around the world, through open source privacy technology. If you’re a resident of the United States, please retain this receipt for your tax records. Signal Technology Foundation is a tax-exempt nonprofit organization in the United States under section 501c3 of the Internal Revenue Code. Our Federal Tax ID is 82-4506840.",
|
||||
"description": "Footer text shown on donation receipts explaining tax deductibility and Signal's mission"
|
||||
},
|
||||
"icu:Donations__OfflineTooltip": {
|
||||
"messageformat": "You are offline. Please connect to the internet to complete a donation.",
|
||||
"description": "Tooltip for donation related buttons which are temporarily disabled due to the app being offline."
|
||||
},
|
||||
"icu:Donations__Toast__Canceled": {
|
||||
"messageformat": "Donation canceled",
|
||||
"description": "Toast shown when a donation was manually canceled by the user"
|
||||
|
||||
@@ -417,5 +417,6 @@
|
||||
padding-block: 5px;
|
||||
padding-inline: 12px;
|
||||
font-weight: 400;
|
||||
border: 0.5px solid variables.$color-black-alpha-16;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
@@ -220,6 +220,7 @@ function renderDonationsPane(props: {
|
||||
clearWorkflow={action('clearWorkflow')}
|
||||
initialCurrency="usd"
|
||||
resumeWorkflow={action('resumeWorkflow')}
|
||||
isOnline
|
||||
isStaging
|
||||
page={props.page}
|
||||
setPage={props.setPage}
|
||||
|
||||
@@ -61,6 +61,7 @@ import {
|
||||
} from './preferences/donations/DonateInputCardCvc';
|
||||
import { I18n } from './I18n';
|
||||
import { strictAssert } from '../util/assert';
|
||||
import { DonationsOfflineTooltip } from './conversation/DonationsOfflineTooltip';
|
||||
import { DonateInputAmount } from './preferences/donations/DonateInputAmount';
|
||||
|
||||
const SUPPORT_URL = 'https://support.signal.org/hc/requests/new?desktop';
|
||||
@@ -68,6 +69,7 @@ const SUPPORT_URL = 'https://support.signal.org/hc/requests/new?desktop';
|
||||
export type PropsDataType = {
|
||||
i18n: LocalizerType;
|
||||
initialCurrency: string;
|
||||
isOnline: boolean;
|
||||
donationAmountsConfig: OneTimeDonationHumanAmounts | undefined;
|
||||
lastError: DonationErrorType | undefined;
|
||||
validCurrencies: ReadonlyArray<string>;
|
||||
@@ -92,6 +94,7 @@ export function PreferencesDonateFlow({
|
||||
contentsRef,
|
||||
i18n,
|
||||
initialCurrency,
|
||||
isOnline,
|
||||
donationAmountsConfig,
|
||||
lastError,
|
||||
validCurrencies,
|
||||
@@ -184,6 +187,7 @@ export function PreferencesDonateFlow({
|
||||
i18n={i18n}
|
||||
initialAmount={amount}
|
||||
initialCurrency={currency}
|
||||
isOnline={isOnline}
|
||||
donationAmountsConfig={donationAmountsConfig}
|
||||
validCurrencies={validCurrencies}
|
||||
onChangeCurrency={handleAmountPickerCurrencyChanged}
|
||||
@@ -206,6 +210,7 @@ export function PreferencesDonateFlow({
|
||||
disabled={isCardFormDisabled}
|
||||
i18n={i18n}
|
||||
initialValues={cardFormValues}
|
||||
isOnline={isOnline}
|
||||
onChange={handleCardFormChanged}
|
||||
onSubmit={handleSubmitDonation}
|
||||
showPrivacyModal={showPrivacyModal}
|
||||
@@ -252,6 +257,7 @@ type AmountPickerProps = {
|
||||
i18n: LocalizerType;
|
||||
initialAmount: HumanDonationAmount | undefined;
|
||||
initialCurrency: string | undefined;
|
||||
isOnline: boolean;
|
||||
donationAmountsConfig: OneTimeDonationHumanAmounts | undefined;
|
||||
validCurrencies: ReadonlyArray<string>;
|
||||
onChangeCurrency: (value: string) => void;
|
||||
@@ -263,6 +269,7 @@ function AmountPicker({
|
||||
i18n,
|
||||
initialAmount,
|
||||
initialCurrency = 'usd',
|
||||
isOnline,
|
||||
validCurrencies,
|
||||
onChangeCurrency,
|
||||
onSubmit,
|
||||
@@ -369,7 +376,7 @@ function AmountPicker({
|
||||
}, []);
|
||||
|
||||
const amount = parsedCustomAmount ?? presetAmount;
|
||||
const isContinueEnabled = currency != null && amount != null;
|
||||
const isContinueEnabled = isOnline && currency != null && amount != null;
|
||||
|
||||
const handleContinueClicked = useCallback(() => {
|
||||
if (!isContinueEnabled) {
|
||||
@@ -388,6 +395,17 @@ function AmountPicker({
|
||||
customInputClassName = 'DonationAmountPicker__CustomInput';
|
||||
}
|
||||
|
||||
const continueButton = (
|
||||
<Button
|
||||
className="PreferencesDonations__PrimaryButton"
|
||||
disabled={!isContinueEnabled}
|
||||
onClick={handleContinueClicked}
|
||||
variant={isOnline ? ButtonVariant.Primary : ButtonVariant.Secondary}
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="DonationAmountPicker">
|
||||
<Select
|
||||
@@ -431,14 +449,13 @@ function AmountPicker({
|
||||
/>
|
||||
</div>
|
||||
<div className="DonationAmountPicker__PrimaryButtonContainer">
|
||||
<Button
|
||||
className="PreferencesDonations__PrimaryButton"
|
||||
disabled={!isContinueEnabled}
|
||||
onClick={handleContinueClicked}
|
||||
variant={ButtonVariant.Primary}
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
{isOnline ? (
|
||||
continueButton
|
||||
) : (
|
||||
<DonationsOfflineTooltip i18n={i18n}>
|
||||
{continueButton}
|
||||
</DonationsOfflineTooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -456,6 +473,7 @@ type CardFormProps = {
|
||||
disabled: boolean;
|
||||
i18n: LocalizerType;
|
||||
initialValues: CardFormValues | undefined;
|
||||
isOnline: boolean;
|
||||
onChange: (values: CardFormValues) => void;
|
||||
onSubmit: (cardDetail: CardDetail) => void;
|
||||
showPrivacyModal: () => void;
|
||||
@@ -467,6 +485,7 @@ function CardForm({
|
||||
disabled,
|
||||
i18n,
|
||||
initialValues,
|
||||
isOnline,
|
||||
onChange,
|
||||
onSubmit,
|
||||
showPrivacyModal,
|
||||
@@ -566,6 +585,7 @@ function CardForm({
|
||||
|
||||
const isDonateDisabled =
|
||||
disabled ||
|
||||
!isOnline ||
|
||||
cardNumber === '' ||
|
||||
cardExpiration === '' ||
|
||||
cardCvc === '' ||
|
||||
@@ -573,6 +593,19 @@ function CardForm({
|
||||
cardExpirationError != null ||
|
||||
cardCvcError != null;
|
||||
|
||||
const donateButton = (
|
||||
<Button
|
||||
className="PreferencesDonations__PrimaryButton"
|
||||
disabled={isDonateDisabled}
|
||||
onClick={handleDonateClicked}
|
||||
variant={isOnline ? ButtonVariant.Primary : ButtonVariant.Secondary}
|
||||
>
|
||||
{i18n('icu:PreferencesDonations__donate-button-with-amount', {
|
||||
formattedCurrencyAmount,
|
||||
})}
|
||||
</Button>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="DonationCardForm">
|
||||
<div className="DonationCardForm__Header--Info PreferencesDonations__section-header">
|
||||
@@ -658,16 +691,13 @@ function CardForm({
|
||||
</div>
|
||||
</div>
|
||||
<div className="DonationCardForm__PrimaryButtonContainer">
|
||||
<Button
|
||||
className="PreferencesDonations__PrimaryButton"
|
||||
disabled={isDonateDisabled}
|
||||
onClick={handleDonateClicked}
|
||||
variant={ButtonVariant.Primary}
|
||||
>
|
||||
{i18n('icu:PreferencesDonations__donate-button-with-amount', {
|
||||
formattedCurrencyAmount,
|
||||
})}
|
||||
</Button>
|
||||
{isOnline ? (
|
||||
donateButton
|
||||
) : (
|
||||
<DonationsOfflineTooltip i18n={i18n}>
|
||||
{donateButton}
|
||||
</DonationsOfflineTooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -42,6 +42,7 @@ import { DonationErrorModal } from './DonationErrorModal';
|
||||
import { DonationVerificationModal } from './DonationVerificationModal';
|
||||
import { DonationProgressModal } from './DonationProgressModal';
|
||||
import { DonationStillProcessingModal } from './DonationStillProcessingModal';
|
||||
import { DonationsOfflineTooltip } from './conversation/DonationsOfflineTooltip';
|
||||
|
||||
const log = createLogger('PreferencesDonations');
|
||||
|
||||
@@ -52,6 +53,7 @@ type PropsExternalType = {
|
||||
export type PropsDataType = {
|
||||
i18n: LocalizerType;
|
||||
initialCurrency: string;
|
||||
isOnline: boolean;
|
||||
isStaging: boolean;
|
||||
page: SettingsPage;
|
||||
didResumeWorkflowAtStartup: boolean;
|
||||
@@ -94,7 +96,12 @@ type DonationPage =
|
||||
|
||||
type PreferencesHomeProps = Pick<
|
||||
PropsType,
|
||||
'contentsRef' | 'i18n' | 'setPage' | 'isStaging' | 'donationReceipts'
|
||||
| 'contentsRef'
|
||||
| 'i18n'
|
||||
| 'setPage'
|
||||
| 'isOnline'
|
||||
| 'isStaging'
|
||||
| 'donationReceipts'
|
||||
> & {
|
||||
navigateToPage: (newPage: SettingsPage) => void;
|
||||
renderDonationHero: () => JSX.Element;
|
||||
@@ -175,25 +182,35 @@ function DonationsHome({
|
||||
renderDonationHero,
|
||||
navigateToPage,
|
||||
setPage,
|
||||
isOnline,
|
||||
isStaging,
|
||||
donationReceipts,
|
||||
}: PreferencesHomeProps): JSX.Element {
|
||||
const hasReceipts = donationReceipts.length > 0;
|
||||
|
||||
const donateButton = (
|
||||
<Button
|
||||
className="PreferencesDonations__PrimaryButton PreferencesDonations__donate-button"
|
||||
disabled={!isOnline}
|
||||
variant={isOnline ? ButtonVariant.Primary : ButtonVariant.Secondary}
|
||||
size={ButtonSize.Medium}
|
||||
onClick={() => {
|
||||
setPage(SettingsPage.DonationsDonateFlow);
|
||||
}}
|
||||
>
|
||||
{i18n('icu:PreferencesDonations__donate-button')}
|
||||
</Button>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="PreferencesDonations">
|
||||
{renderDonationHero()}
|
||||
{isStaging && (
|
||||
<Button
|
||||
className="PreferencesDonations__PrimaryButton PreferencesDonations__donate-button"
|
||||
variant={ButtonVariant.Primary}
|
||||
size={ButtonSize.Medium}
|
||||
onClick={() => {
|
||||
setPage(SettingsPage.DonationsDonateFlow);
|
||||
}}
|
||||
>
|
||||
{i18n('icu:PreferencesDonations__donate-button')}
|
||||
</Button>
|
||||
{isStaging && isOnline ? (
|
||||
donateButton
|
||||
) : (
|
||||
<DonationsOfflineTooltip i18n={i18n}>
|
||||
{donateButton}
|
||||
</DonationsOfflineTooltip>
|
||||
)}
|
||||
|
||||
<hr className="PreferencesDonations__separator" />
|
||||
@@ -455,6 +472,7 @@ export function PreferencesDonations({
|
||||
contentsRef,
|
||||
i18n,
|
||||
initialCurrency,
|
||||
isOnline,
|
||||
isStaging,
|
||||
page,
|
||||
workflow,
|
||||
@@ -619,6 +637,7 @@ export function PreferencesDonations({
|
||||
<PreferencesDonateFlow
|
||||
contentsRef={contentsRef}
|
||||
i18n={i18n}
|
||||
isOnline={isOnline}
|
||||
initialCurrency={initialCurrency}
|
||||
donationAmountsConfig={donationAmountsConfig}
|
||||
lastError={lastError}
|
||||
@@ -641,6 +660,7 @@ export function PreferencesDonations({
|
||||
<DonationsHome
|
||||
contentsRef={contentsRef}
|
||||
i18n={i18n}
|
||||
isOnline={isOnline}
|
||||
navigateToPage={navigateToPage}
|
||||
donationReceipts={donationReceipts}
|
||||
isStaging={isStaging}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2025 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { offsetDistanceModifier } from '../../util/popperUtil';
|
||||
import { Tooltip, TooltipPlacement } from '../Tooltip';
|
||||
|
||||
import type { LocalizerType } from '../../types/I18N';
|
||||
|
||||
type Props = {
|
||||
i18n: LocalizerType;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export function getTooltipContent(i18n: LocalizerType): string {
|
||||
return i18n('icu:Donations__OfflineTooltip');
|
||||
}
|
||||
|
||||
export function DonationsOfflineTooltip({
|
||||
i18n,
|
||||
children,
|
||||
}: Props): JSX.Element {
|
||||
return (
|
||||
<Tooltip
|
||||
className="InAnotherCallTooltip"
|
||||
content={getTooltipContent(i18n)}
|
||||
direction={TooltipPlacement.Top}
|
||||
popperModifiers={[offsetDistanceModifier(15)]}
|
||||
>
|
||||
{children}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import { drop } from '../../util/drop';
|
||||
import type { OneTimeDonationHumanAmounts } from '../../types/Donations';
|
||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||
import { phoneNumberToCurrencyCode } from '../../services/donations';
|
||||
import { getNetworkIsOnline } from '../selectors/network';
|
||||
|
||||
export const SmartPreferencesDonations = memo(
|
||||
function SmartPreferencesDonations({
|
||||
@@ -38,6 +39,8 @@ export const SmartPreferencesDonations = memo(
|
||||
useState<OneTimeDonationHumanAmounts>();
|
||||
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
|
||||
const isOnline = useSelector(getNetworkIsOnline);
|
||||
const isStaging = isStagingServer();
|
||||
const i18n = useSelector(getIntl);
|
||||
const theme = useSelector(getTheme);
|
||||
@@ -91,6 +94,7 @@ export const SmartPreferencesDonations = memo(
|
||||
showToast={showToast}
|
||||
contentsRef={contentsRef}
|
||||
initialCurrency={initialCurrency}
|
||||
isOnline={isOnline}
|
||||
isStaging={isStaging}
|
||||
page={page}
|
||||
didResumeWorkflowAtStartup={donationsState.didResumeWorkflowAtStartup}
|
||||
|
||||
Reference in New Issue
Block a user