mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-04-02 00:07:56 +01:00
Internal tool to test megaphone
Co-authored-by: ayumi-signal <143036029+ayumi-signal@users.noreply.github.com>
This commit is contained in:
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
@@ -631,6 +631,7 @@ export default {
|
||||
action('generateDonationReceiptBlob')();
|
||||
return new Blob();
|
||||
},
|
||||
addVisibleMegaphone: async () => Promise.resolve(),
|
||||
internalDeleteAllMegaphones: async () => {
|
||||
return Promise.resolve(0);
|
||||
},
|
||||
|
||||
@@ -101,6 +101,7 @@ import { AxoButton } from '../axo/AxoButton.dom.js';
|
||||
import type { ExternalProps as SmartNotificationProfilesProps } from '../state/smart/PreferencesNotificationProfiles.preload.js';
|
||||
import type { LocalBackupExportMetadata } from '../types/LocalExport.std.js';
|
||||
import { isDonationsPage } from './PreferencesDonations.dom.js';
|
||||
import type { VisibleRemoteMegaphoneType } from '../types/Megaphone.std.js';
|
||||
|
||||
const { isNumber, noop, partition } = lodash;
|
||||
|
||||
@@ -289,6 +290,7 @@ type PropsFunctionType = {
|
||||
receipt: DonationReceipt,
|
||||
i18n: LocalizerType
|
||||
) => Promise<Blob>;
|
||||
addVisibleMegaphone: (megaphone: VisibleRemoteMegaphoneType) => void;
|
||||
|
||||
// Change handlers
|
||||
onAudioNotificationsChange: CheckboxChangeHandlerType;
|
||||
@@ -541,6 +543,7 @@ export function Preferences({
|
||||
internalAddDonationReceipt,
|
||||
saveAttachmentToDisk,
|
||||
generateDonationReceiptBlob,
|
||||
addVisibleMegaphone,
|
||||
internalDeleteAllMegaphones,
|
||||
__dangerouslyRunAbitraryReadOnlySqlQuery,
|
||||
cqsTestMode,
|
||||
@@ -2327,6 +2330,7 @@ export function Preferences({
|
||||
internalAddDonationReceipt={internalAddDonationReceipt}
|
||||
saveAttachmentToDisk={saveAttachmentToDisk}
|
||||
generateDonationReceiptBlob={generateDonationReceiptBlob}
|
||||
addVisibleMegaphone={addVisibleMegaphone}
|
||||
internalDeleteAllMegaphones={internalDeleteAllMegaphones}
|
||||
__dangerouslyRunAbitraryReadOnlySqlQuery={
|
||||
__dangerouslyRunAbitraryReadOnlySqlQuery
|
||||
|
||||
@@ -21,6 +21,8 @@ import { getHumanDonationAmount } from '../util/currency.dom.js';
|
||||
import { AutoSizeTextArea } from './AutoSizeTextArea.dom.js';
|
||||
import { AxoButton } from '../axo/AxoButton.dom.js';
|
||||
import { AxoSwitch } from '../axo/AxoSwitch.dom.js';
|
||||
import type { VisibleRemoteMegaphoneType } from '../types/Megaphone.std.js';
|
||||
import { internalGetTestMegaphone } from '../util/getTestMegaphone.std.js';
|
||||
|
||||
const log = createLogger('PreferencesInternal');
|
||||
|
||||
@@ -33,6 +35,7 @@ export function PreferencesInternal({
|
||||
internalAddDonationReceipt,
|
||||
saveAttachmentToDisk,
|
||||
generateDonationReceiptBlob,
|
||||
addVisibleMegaphone,
|
||||
internalDeleteAllMegaphones,
|
||||
__dangerouslyRunAbitraryReadOnlySqlQuery,
|
||||
cqsTestMode,
|
||||
@@ -55,6 +58,7 @@ export function PreferencesInternal({
|
||||
receipt: DonationReceipt,
|
||||
i18n: LocalizerType
|
||||
) => Promise<Blob>;
|
||||
addVisibleMegaphone: (megaphone: VisibleRemoteMegaphoneType) => void;
|
||||
internalDeleteAllMegaphones: () => Promise<number>;
|
||||
__dangerouslyRunAbitraryReadOnlySqlQuery: (
|
||||
readonlySqlQuery: string
|
||||
@@ -73,6 +77,9 @@ export function PreferencesInternal({
|
||||
BackupValidationResultType | undefined
|
||||
>();
|
||||
|
||||
const [showMegaphoneResult, setShowMegaphoneResult] = useState<
|
||||
string | undefined
|
||||
>();
|
||||
const [deleteAllMegaphonesResult, setDeleteAllMegaphonesResult] = useState<
|
||||
number | undefined
|
||||
>();
|
||||
@@ -482,6 +489,42 @@ export function PreferencesInternal({
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow title="Megaphones">
|
||||
<FlowingSettingsControl>
|
||||
<div className="Preferences__two-thirds-flow">
|
||||
Show a test megaphone in memory. Disappears on restart.
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
'Preferences__flow-button',
|
||||
'Preferences__one-third-flow',
|
||||
'Preferences__one-third-flow--align-right'
|
||||
)}
|
||||
>
|
||||
<AxoButton.Root
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
onClick={async () => {
|
||||
const megaphone = internalGetTestMegaphone();
|
||||
addVisibleMegaphone(megaphone);
|
||||
setShowMegaphoneResult(
|
||||
`Megaphone shown. Go to Chats tab to view.\n${JSON.stringify(megaphone, null, 2)}`
|
||||
);
|
||||
}}
|
||||
>
|
||||
Show megaphone
|
||||
</AxoButton.Root>
|
||||
</div>
|
||||
{showMegaphoneResult != null && (
|
||||
<AutoSizeTextArea
|
||||
i18n={i18n}
|
||||
value={showMegaphoneResult}
|
||||
onChange={() => null}
|
||||
readOnly
|
||||
placeholder=""
|
||||
moduleClassName="Preferences__ReadonlySqlPlayground__Textarea"
|
||||
/>
|
||||
)}
|
||||
</FlowingSettingsControl>
|
||||
<FlowingSettingsControl>
|
||||
<div className="Preferences__two-thirds-flow">
|
||||
Delete local records of remote megaphones
|
||||
|
||||
@@ -26,7 +26,7 @@ export default {
|
||||
secondaryCtaText: 'Not now',
|
||||
title: 'Donate to Signal',
|
||||
body: 'Signal is powered by people like you. Show your support today!',
|
||||
imagePath: '/fixtures/donate-heart.png',
|
||||
imagePath: 'images/donate-heart.png',
|
||||
isFullSize: true,
|
||||
onClickNarrowMegaphone: action('onClickNarrowMegaphone'),
|
||||
onInteractWithMegaphone: action('onInteractWithMegaphone'),
|
||||
|
||||
@@ -17,6 +17,7 @@ import type {
|
||||
import type { ChangeLocationAction } from './nav.std.js';
|
||||
import { actions as navActions } from './nav.std.js';
|
||||
import { NavTab, SettingsPage } from '../../types/Nav.std.js';
|
||||
import { isTestMegaphoneId } from '../../util/getTestMegaphone.std.js';
|
||||
|
||||
const log = createLogger('megaphones');
|
||||
|
||||
@@ -88,10 +89,14 @@ function interactWithMegaphone(
|
||||
RemoveVisibleMegaphoneAction | ChangeLocationAction
|
||||
> {
|
||||
return async dispatch => {
|
||||
const isTest = isTestMegaphoneId(megaphoneId);
|
||||
|
||||
if (ctaId === 'donate' || ctaId === 'finish') {
|
||||
try {
|
||||
log.info(`Finishing megaphone ${megaphoneId}, ctaId=${ctaId}`);
|
||||
await DataWriter.finishMegaphone(megaphoneId);
|
||||
if (!isTest) {
|
||||
await DataWriter.finishMegaphone(megaphoneId);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Failed to finish megaphone ${megaphoneId}`,
|
||||
@@ -112,7 +117,9 @@ function interactWithMegaphone(
|
||||
} else if (ctaId === 'snooze') {
|
||||
try {
|
||||
log.info(`Snoozing megaphone ${megaphoneId}`);
|
||||
await DataWriter.snoozeMegaphone(megaphoneId);
|
||||
if (!isTest) {
|
||||
await DataWriter.snoozeMegaphone(megaphoneId);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Failed to snooze megaphone ${megaphoneId}`,
|
||||
|
||||
@@ -11,6 +11,10 @@ import {
|
||||
type VisibleRemoteMegaphoneType,
|
||||
} from '../../types/Megaphone.std.js';
|
||||
import type { StateSelector } from '../types.std.js';
|
||||
import {
|
||||
isTestMegaphone,
|
||||
TEST_MEGAPHONE_IMAGE,
|
||||
} from '../../util/getTestMegaphone.std.js';
|
||||
|
||||
export function getMegaphonesState(
|
||||
state: Readonly<StateType>
|
||||
@@ -36,6 +40,8 @@ export const getVisibleMegaphonesForDisplay: StateSelector<
|
||||
secondaryCtaText: megaphone.secondaryCtaText,
|
||||
title: megaphone.title,
|
||||
body: megaphone.body,
|
||||
imagePath: getAbsoluteMegaphoneImageFilePath(megaphone.imagePath),
|
||||
imagePath: isTestMegaphone(megaphone)
|
||||
? TEST_MEGAPHONE_IMAGE
|
||||
: getAbsoluteMegaphoneImageFilePath(megaphone.imagePath),
|
||||
}))
|
||||
);
|
||||
|
||||
@@ -120,6 +120,7 @@ import { DonationsErrorBoundary } from '../../components/DonationsErrorBoundary.
|
||||
import type { SmartPreferencesChatFoldersPageProps } from './PreferencesChatFoldersPage.preload.js';
|
||||
import type { SmartPreferencesEditChatFolderPageProps } from './PreferencesEditChatFolderPage.preload.js';
|
||||
import type { ExternalProps as SmartNotificationProfilesProps } from './PreferencesNotificationProfiles.preload.js';
|
||||
import { useMegaphonesActions } from '../ducks/megaphones.preload.js';
|
||||
|
||||
const DEFAULT_NOTIFICATION_SETTING = 'message';
|
||||
|
||||
@@ -225,6 +226,7 @@ export function SmartPreferences(): React.JSX.Element | null {
|
||||
const { showToast } = useToastActions();
|
||||
const { internalAddDonationReceipt } = useDonationsActions();
|
||||
const { startPlaintextExport, startLocalBackupExport } = useBackupActions();
|
||||
const { addVisibleMegaphone } = useMegaphonesActions();
|
||||
|
||||
// Selectors
|
||||
|
||||
@@ -986,6 +988,7 @@ export function SmartPreferences(): React.JSX.Element | null {
|
||||
internalAddDonationReceipt={internalAddDonationReceipt}
|
||||
saveAttachmentToDisk={saveAttachmentToDisk}
|
||||
generateDonationReceiptBlob={generateDonationReceiptBlob}
|
||||
addVisibleMegaphone={addVisibleMegaphone}
|
||||
internalDeleteAllMegaphones={internalDeleteAllMegaphones}
|
||||
__dangerouslyRunAbitraryReadOnlySqlQuery={
|
||||
__dangerouslyRunAbitraryReadOnlySqlQuery
|
||||
|
||||
@@ -35,7 +35,7 @@ const FAKE_MEGAPHONE: RemoteMegaphoneType = {
|
||||
localeFetched: 'en',
|
||||
title: 'megaphone',
|
||||
body: 'cats',
|
||||
imagePath: '../../../fixtures/donate-heart.png',
|
||||
imagePath: '../../../images/donate-heart.png',
|
||||
primaryCtaText: 'donate',
|
||||
secondaryCtaText: 'snooze',
|
||||
snoozeCount: 0,
|
||||
|
||||
51
ts/util/getTestMegaphone.std.ts
Normal file
51
ts/util/getTestMegaphone.std.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type {
|
||||
RemoteMegaphoneId,
|
||||
VisibleRemoteMegaphoneType,
|
||||
} from '../types/Megaphone.std.js';
|
||||
import { MegaphoneCtaId } from '../types/Megaphone.std.js';
|
||||
import { DAY } from './durations/index.std.js';
|
||||
|
||||
const INTERNAL_TEST_ID = 'INTERNAL_TEST' as RemoteMegaphoneId;
|
||||
export const TEST_MEGAPHONE_IMAGE = 'images/donate-heart.png';
|
||||
|
||||
export function internalGetTestMegaphone(
|
||||
props?: Partial<VisibleRemoteMegaphoneType>
|
||||
): VisibleRemoteMegaphoneType {
|
||||
return {
|
||||
priority: 100,
|
||||
desktopMinVersion: '1.0.0',
|
||||
dontShowBeforeEpochMs: Date.now() - 1 * DAY,
|
||||
dontShowAfterEpochMs: Date.now() + 14 * DAY,
|
||||
showForNumberOfDays: 30,
|
||||
conditionalId: 'internal',
|
||||
primaryCtaId: MegaphoneCtaId.Donate,
|
||||
primaryCtaData: null,
|
||||
secondaryCtaId: MegaphoneCtaId.Snooze,
|
||||
secondaryCtaData: { snoozeDurationDays: [5, 7, 100] },
|
||||
localeFetched: 'en',
|
||||
title: 'Donate Today',
|
||||
body: 'As a nonprofit, Signal needs your support.',
|
||||
imagePath: TEST_MEGAPHONE_IMAGE,
|
||||
primaryCtaText: 'Donate',
|
||||
secondaryCtaText: 'Snooze',
|
||||
snoozeCount: 0,
|
||||
snoozedAt: null,
|
||||
shownAt: null,
|
||||
isFinished: false,
|
||||
...props,
|
||||
id: INTERNAL_TEST_ID,
|
||||
};
|
||||
}
|
||||
|
||||
export function isTestMegaphone(
|
||||
megaphone: VisibleRemoteMegaphoneType
|
||||
): boolean {
|
||||
return megaphone.id === INTERNAL_TEST_ID;
|
||||
}
|
||||
|
||||
export function isTestMegaphoneId(id: RemoteMegaphoneId): boolean {
|
||||
return id === INTERNAL_TEST_ID;
|
||||
}
|
||||
Reference in New Issue
Block a user