mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-08-19 20:57:57 +01:00
Improved relink prompt
Co-authored-by: Scott Nonnenberg <scott@signal.org>
This commit is contained in:
co-authored by
Scott Nonnenberg
parent
e0ef26bb4f
commit
e15c3adddc
@@ -11022,6 +11022,50 @@
|
||||
"messageformat": "Slide",
|
||||
"description": "aria-label for a slide within a carousel UI layout"
|
||||
},
|
||||
"icu:MaybeTransferModal__title": {
|
||||
"messageformat": "Transfer your phone’s message history to this desktop?",
|
||||
"description": "Title for dialog shown when user is unlinked and clicks to relink"
|
||||
},
|
||||
"icu:MaybeTransferModal__description": {
|
||||
"messageformat": "When you re-link this desktop, you can transfer your phone’s message history to this device.",
|
||||
"description": "Description for dialog shown when user is unlinked and clicks to relink"
|
||||
},
|
||||
"icu:MaybeTransferModal__learnMore": {
|
||||
"messageformat": "Learn more",
|
||||
"description": "Text for link to a support article about relinking a linked device"
|
||||
},
|
||||
"icu:MaybeTransferModal__replaceHistory": {
|
||||
"messageformat": "This will <bold>replace</bold> the message history on this desktop.",
|
||||
"description": "Bullet point in relink dialog for what will happen if you link & sync"
|
||||
},
|
||||
"icu:MaybeTransferModal__scanQrCode": {
|
||||
"messageformat": "You’ll need to relink by scanning a QR code.",
|
||||
"description": "Bullet point in relink dialog for what will happen if you link & sync"
|
||||
},
|
||||
"icu:MaybeTransferModal__media": {
|
||||
"messageformat": "If you have paid Signal Secure Backups, all of your media will be transferred. Otherwise, your last 45 days of media will be included.",
|
||||
"description": "Bullet point in relink dialog for what will happen if you link & sync"
|
||||
},
|
||||
"icu:MaybeTransferModal__transfer": {
|
||||
"messageformat": "Transfer",
|
||||
"description": "Button text when user wants to transfer data from their phone"
|
||||
},
|
||||
"icu:MaybeTransferModal__dontTransfer": {
|
||||
"messageformat": "Don't transfer",
|
||||
"description": "Button text when user does not want to transfer data from their phone"
|
||||
},
|
||||
"icu:DeleteDataAndRelinkConfirmationDialog__title": {
|
||||
"messageformat": "Delete all data?",
|
||||
"description": "Title in confirmation dialog shown when user is unlinked and chose to delete data and relink"
|
||||
},
|
||||
"icu:DeleteDataAndRelinkConfirmationDialog__pending": {
|
||||
"messageformat": "Deleting all data",
|
||||
"description": "Accessiblity label for button when data is in progress of being deleted"
|
||||
},
|
||||
"icu:DeleteDataAndRelinkConfirmationDialog__body": {
|
||||
"messageformat": "All current data in Signal Desktop will be deleted. You can then scan a QR code and transfer your message history from your phone.",
|
||||
"description": "Description in confirmation dialog shown when user is unlinked and chose to delete data and relink"
|
||||
},
|
||||
"icu:WhatsNew__bugfixes": {
|
||||
"messageformat": "This version contains a number of small tweaks and bug fixes to keep Signal running smoothly.",
|
||||
"description": "Release notes for releases that only include bug fixes",
|
||||
|
||||
@@ -16,6 +16,7 @@ const defaultProps = {
|
||||
containerWidthBreakpoint: WidthBreakpoint.Wide,
|
||||
i18n,
|
||||
relinkDevice: action('relink-device'),
|
||||
renderClearingDataView: action('render-clearing-data-view'),
|
||||
reregister: action('reregister'),
|
||||
weArePrimaryDevice: false,
|
||||
};
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { JSX } from 'react';
|
||||
import { useState, type JSX } from 'react';
|
||||
|
||||
import type { LocalizerType } from '../types/Util.std.ts';
|
||||
import type { WidthBreakpoint } from './_util.std.ts';
|
||||
|
||||
import { LeftPaneDialog } from './LeftPaneDialog.dom.tsx';
|
||||
import {
|
||||
DeleteDataAndRelinkConfirmationDialog,
|
||||
MaybeTransferModal,
|
||||
} from './MaybeTransferModal.dom.tsx';
|
||||
|
||||
export type PropsType = {
|
||||
containerWidthBreakpoint: WidthBreakpoint;
|
||||
i18n: LocalizerType;
|
||||
relinkDevice: () => void;
|
||||
renderClearingDataView: () => void;
|
||||
reregister: () => void;
|
||||
weArePrimaryDevice: boolean;
|
||||
};
|
||||
@@ -20,9 +25,14 @@ export function DialogRelink({
|
||||
containerWidthBreakpoint,
|
||||
i18n,
|
||||
relinkDevice,
|
||||
renderClearingDataView,
|
||||
reregister,
|
||||
weArePrimaryDevice,
|
||||
}: PropsType): JSX.Element | null {
|
||||
const [relinkDialogStep, setRelinkDialogStep] = useState<
|
||||
'maybe-transfer' | 'confirm-deletion' | null
|
||||
>(null);
|
||||
|
||||
if (weArePrimaryDevice) {
|
||||
return (
|
||||
<LeftPaneDialog
|
||||
@@ -38,14 +48,34 @@ export function DialogRelink({
|
||||
}
|
||||
|
||||
return (
|
||||
<LeftPaneDialog
|
||||
containerWidthBreakpoint={containerWidthBreakpoint}
|
||||
type="warning"
|
||||
icon="relink"
|
||||
clickLabel={i18n('icu:unlinkedWarning')}
|
||||
onClick={relinkDevice}
|
||||
title={i18n('icu:unlinked')}
|
||||
hasAction
|
||||
/>
|
||||
<>
|
||||
<LeftPaneDialog
|
||||
containerWidthBreakpoint={containerWidthBreakpoint}
|
||||
type="warning"
|
||||
icon="relink"
|
||||
clickLabel={i18n('icu:unlinkedWarning')}
|
||||
onClick={() => setRelinkDialogStep('maybe-transfer')}
|
||||
title={i18n('icu:unlinked')}
|
||||
hasAction
|
||||
/>
|
||||
<MaybeTransferModal
|
||||
open={relinkDialogStep === 'maybe-transfer'}
|
||||
i18n={i18n}
|
||||
onTransfer={() => setRelinkDialogStep('confirm-deletion')}
|
||||
onDontTransfer={relinkDevice}
|
||||
onCancel={() => setRelinkDialogStep(null)}
|
||||
/>
|
||||
{relinkDialogStep === 'confirm-deletion' ? (
|
||||
<DeleteDataAndRelinkConfirmationDialog
|
||||
open={relinkDialogStep === 'confirm-deletion'}
|
||||
i18n={i18n}
|
||||
onCancel={() => setRelinkDialogStep('maybe-transfer')}
|
||||
onConfirm={() => {
|
||||
renderClearingDataView();
|
||||
setRelinkDialogStep(null);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -260,6 +260,7 @@ const useProps = (overrideProps: OverridePropsType = {}): PropsType => {
|
||||
<DialogRelink
|
||||
i18n={i18n}
|
||||
relinkDevice={action('relinkDevice')}
|
||||
renderClearingDataView={action('renderClearingDataView')}
|
||||
reregister={action('reregister')}
|
||||
weArePrimaryDevice={false}
|
||||
{...props}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { JSX } from 'react';
|
||||
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { type Meta } from '@storybook/react';
|
||||
import {
|
||||
MaybeTransferModal,
|
||||
DeleteDataAndRelinkConfirmationDialog,
|
||||
} from './MaybeTransferModal.dom.tsx';
|
||||
|
||||
const { i18n } = window.SignalContext;
|
||||
|
||||
export default {
|
||||
title: 'Components/MaybeTransferModal',
|
||||
} satisfies Meta;
|
||||
|
||||
export function UnlinkedMaybeTransferModal(): JSX.Element {
|
||||
return (
|
||||
<MaybeTransferModal
|
||||
open
|
||||
onDontTransfer={action('onDontTransfer')}
|
||||
onTransfer={action('onTransfer')}
|
||||
onCancel={action('onCancel')}
|
||||
i18n={i18n}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function DeleteDataConfirmationDialog(): JSX.Element {
|
||||
return (
|
||||
<DeleteDataAndRelinkConfirmationDialog
|
||||
open
|
||||
onCancel={action('onCancel')}
|
||||
onConfirm={async () => action('onConfirm')()}
|
||||
i18n={i18n}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { type JSX, type ReactNode } from 'react';
|
||||
|
||||
import type { LocalizerType } from '../types/Util.std.ts';
|
||||
import { AxoDialog } from '../axo/AxoDialog.dom.tsx';
|
||||
import { tw } from '../axo/tw.dom.tsx';
|
||||
import { type AxoSymbolIconName } from '../axo/_internal/AxoSymbolDefs.generated.std.ts';
|
||||
import { AxoSymbol } from '../axo/AxoSymbol.dom.tsx';
|
||||
import { I18n } from './I18n.dom.tsx';
|
||||
import { AxoAlertDialog } from '../axo/AxoAlertDialog.dom.tsx';
|
||||
import { AxoButton } from '../axo/AxoButton.dom.tsx';
|
||||
|
||||
const LEARN_MORE_LINK =
|
||||
'https://support.signal.org/hc/articles/360007320551-Linked-Devices';
|
||||
|
||||
export function MaybeTransferModal({
|
||||
i18n,
|
||||
onCancel,
|
||||
onDontTransfer,
|
||||
onTransfer,
|
||||
open,
|
||||
}: {
|
||||
i18n: LocalizerType;
|
||||
onCancel: () => void;
|
||||
onDontTransfer: () => void;
|
||||
onTransfer: () => void;
|
||||
open: boolean;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<AxoDialog.Root
|
||||
open={open}
|
||||
onOpenChange={isOpen => {
|
||||
if (!isOpen) {
|
||||
onCancel();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AxoDialog.Content
|
||||
size="sm"
|
||||
escape="cancel-is-noop"
|
||||
disableMissingAriaDescriptionWarning
|
||||
>
|
||||
<AxoDialog.Body maxHeight={600}>
|
||||
<div className={tw('mt-8 flex flex-col items-center')}>
|
||||
<img
|
||||
src="images/desktop-and-phone.svg"
|
||||
height="80"
|
||||
width="100"
|
||||
alt=""
|
||||
/>
|
||||
<AxoDialog.Title screenReaderOnly>
|
||||
{i18n('icu:MaybeTransferModal__title')}
|
||||
</AxoDialog.Title>
|
||||
<div className={tw('mt-4 text-center type-title-medium')}>
|
||||
{i18n('icu:MaybeTransferModal__title')}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={tw('mt-3 mb-5 flex flex-col gap-5 text-label-secondary')}
|
||||
>
|
||||
<div className={tw('text-center')}>
|
||||
<div>{i18n('icu:MaybeTransferModal__description')}</div>
|
||||
<a
|
||||
className={tw('text-label-primary')}
|
||||
href={LEARN_MORE_LINK}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{i18n('icu:MaybeTransferModal__learnMore')}
|
||||
</a>
|
||||
</div>
|
||||
<ul className={tw('flex flex-col gap-5 px-4')}>
|
||||
<ListItemWithIcon
|
||||
iconName="message-thread"
|
||||
content={
|
||||
<I18n
|
||||
i18n={i18n}
|
||||
id="icu:MaybeTransferModal__replaceHistory"
|
||||
components={{ bold: Bold }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ListItemWithIcon
|
||||
iconName="qrcode"
|
||||
content={i18n('icu:MaybeTransferModal__scanQrCode')}
|
||||
/>
|
||||
<ListItemWithIcon
|
||||
iconName="backup"
|
||||
content={i18n('icu:MaybeTransferModal__media')}
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</AxoDialog.Body>
|
||||
<AxoDialog.Footer>
|
||||
<AxoDialog.Actions>
|
||||
<AxoDialog.Action variant="secondary" onClick={onDontTransfer}>
|
||||
{i18n('icu:MaybeTransferModal__dontTransfer')}
|
||||
</AxoDialog.Action>
|
||||
<AxoDialog.Action variant="primary" onClick={onTransfer}>
|
||||
{i18n('icu:MaybeTransferModal__transfer')}
|
||||
</AxoDialog.Action>
|
||||
</AxoDialog.Actions>
|
||||
</AxoDialog.Footer>
|
||||
</AxoDialog.Content>
|
||||
</AxoDialog.Root>
|
||||
);
|
||||
}
|
||||
|
||||
function ListItemWithIcon({
|
||||
iconName,
|
||||
content,
|
||||
}: {
|
||||
iconName: AxoSymbolIconName;
|
||||
content: ReactNode;
|
||||
}): ReactNode {
|
||||
return (
|
||||
<li className={tw('flex gap-2')}>
|
||||
<div
|
||||
className={tw(
|
||||
'flex size-8 shrink-0 items-center justify-center rounded-full bg-fill-secondary'
|
||||
)}
|
||||
>
|
||||
<AxoSymbol.Icon size={20} symbol={iconName} label={null} />
|
||||
</div>
|
||||
<div>{content}</div>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
function Bold(parts: Array<string | JSX.Element>) {
|
||||
return <strong>{parts}</strong>;
|
||||
}
|
||||
|
||||
export function DeleteDataAndRelinkConfirmationDialog({
|
||||
i18n,
|
||||
onCancel,
|
||||
onConfirm,
|
||||
open,
|
||||
}: {
|
||||
i18n: LocalizerType;
|
||||
onCancel: () => void;
|
||||
onConfirm: () => void;
|
||||
open: boolean;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<AxoAlertDialog.Root
|
||||
open={open}
|
||||
onOpenChange={isOpen => {
|
||||
if (!isOpen) {
|
||||
onCancel();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AxoAlertDialog.Content escape="cancel-is-noop">
|
||||
<AxoAlertDialog.Body>
|
||||
<AxoAlertDialog.Title>
|
||||
{i18n('icu:DeleteDataAndRelinkConfirmationDialog__title')}
|
||||
</AxoAlertDialog.Title>
|
||||
<AxoAlertDialog.Description>
|
||||
{i18n('icu:DeleteDataAndRelinkConfirmationDialog__body')}
|
||||
</AxoAlertDialog.Description>
|
||||
</AxoAlertDialog.Body>
|
||||
<AxoAlertDialog.Footer>
|
||||
<AxoAlertDialog.Action variant="secondary" onClick={onCancel}>
|
||||
{i18n('icu:cancel')}
|
||||
</AxoAlertDialog.Action>
|
||||
<AxoButton.Root
|
||||
variant="destructive"
|
||||
size="md"
|
||||
width="grow"
|
||||
onClick={onConfirm}
|
||||
>
|
||||
{i18n('icu:delete')}
|
||||
</AxoButton.Root>
|
||||
</AxoAlertDialog.Footer>
|
||||
</AxoAlertDialog.Content>
|
||||
</AxoAlertDialog.Root>
|
||||
);
|
||||
}
|
||||
@@ -104,7 +104,7 @@ import { SmartCaptchaDialog } from './CaptchaDialog.preload.tsx';
|
||||
import { SmartCrashReportDialog } from './CrashReportDialog.preload.tsx';
|
||||
import { SmartMessageSearchResult } from './MessageSearchResult.preload.tsx';
|
||||
import { SmartNetworkStatus } from './NetworkStatus.preload.tsx';
|
||||
import { SmartRelinkDialog } from './RelinkDialog.dom.tsx';
|
||||
import { SmartRelinkDialog } from './RelinkDialog.preload.tsx';
|
||||
import {
|
||||
renderToastManagerWithoutMegaphone,
|
||||
SmartToastManager,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { areWePrimaryDevice, getIntl } from '../selectors/user.std.ts';
|
||||
import { useNetworkActions } from '../ducks/network.dom.ts';
|
||||
|
||||
import type { WidthBreakpoint } from '../../components/_util.std.ts';
|
||||
import { renderClearingDataView } from '../../shims/renderClearingDataView.preload.tsx';
|
||||
|
||||
type SmartRelinkDialogProps = Readonly<{
|
||||
containerWidthBreakpoint: WidthBreakpoint;
|
||||
@@ -25,6 +26,7 @@ export const SmartRelinkDialog = memo(function SmartRelinkDialog({
|
||||
i18n={i18n}
|
||||
containerWidthBreakpoint={containerWidthBreakpoint}
|
||||
relinkDevice={relinkDevice}
|
||||
renderClearingDataView={renderClearingDataView}
|
||||
reregister={reregister}
|
||||
weArePrimaryDevice={weArePrimaryDevice}
|
||||
/>
|
||||
@@ -425,6 +425,7 @@ export class Bootstrap {
|
||||
return;
|
||||
}
|
||||
await relinkButton.click();
|
||||
await window.getByRole('button', { name: "Don't transfer" }).click();
|
||||
} catch {
|
||||
// Ignore, provision will fail if QR code was never generated
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user