mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-09-07 05:41:24 +01:00
Update key transparency error dialog
Co-authored-by: Jamie <113370520+jamiebuilds-signal@users.noreply.github.com>
This commit is contained in:
co-authored by
Jamie
parent
7679a21bd6
commit
032eb916b3
@@ -862,14 +862,26 @@
|
||||
"messageformat": "Automatic Key Verification is currently unavailable for your device. Submit debug log?",
|
||||
"description": "Key Transparency Error Dialog > Title"
|
||||
},
|
||||
"icu:KeyTransparencyErrorDialog__Title-v2": {
|
||||
"messageformat": "Automatic Key Verification is currently unavailable for your device. Submit a report to help us improve?",
|
||||
"description": "Key Transparency Error Dialog > Title"
|
||||
},
|
||||
"icu:KeyTransparencyErrorDialog__Description": {
|
||||
"messageformat": "Debug logs helps us diagnose and fix the issue, and do not contain identifying information.",
|
||||
"description": "Key Transparency Error Dialog > Description"
|
||||
},
|
||||
"icu:KeyTransparencyErrorDialog__Description-v2": {
|
||||
"messageformat": "Including your debug logs help us diagnose and fix the issue, and do not contain identifying information. Sharing is optional.",
|
||||
"description": "Key Transparency Error Dialog > Description"
|
||||
},
|
||||
"icu:KeyTransparencyErrorDialog__ShareDebugLog__Label": {
|
||||
"messageformat": "Share debug log",
|
||||
"description": "Key Transparency Error Dialog > Share debug log > Label"
|
||||
},
|
||||
"icu:KeyTransparencyErrorDialog__ShareDebugLog__Label-v2": {
|
||||
"messageformat": "Include debug log",
|
||||
"description": "Key Transparency Error Dialog > Share debug log > Label"
|
||||
},
|
||||
"icu:KeyTransparencyErrorDialog__ShareDebugLog__ViewButton": {
|
||||
"messageformat": "View",
|
||||
"description": "Key Transparency Error Dialog > Share debug log > View Button"
|
||||
@@ -878,6 +890,10 @@
|
||||
"messageformat": "Submit",
|
||||
"description": "Primary button text in the dialog shown when an unexpected key transparency error occurs. Clicking it will open a support page"
|
||||
},
|
||||
"icu:KeyTransparencyErrorDialog__Cancel": {
|
||||
"messageformat": "No thanks",
|
||||
"description": "Cancel button text in the dialog shown when an unexpected key transparency error occurs. Clicking it will close the dialog"
|
||||
},
|
||||
"icu:KeyTransparencyOnboardingDialog__Title": {
|
||||
"messageformat": "Signal can now auto-verify encryption",
|
||||
"description": "Key Transparency Onboarding Dialog > Title"
|
||||
|
||||
@@ -103,8 +103,8 @@ function Template(props: {
|
||||
</AxoAlertDialog.Cancel>
|
||||
<AxoAlertDialog.Action
|
||||
variant="strong-primary"
|
||||
symbol={props.extraLongText ? 'check' : undefined}
|
||||
arrow={props.extraLongText}
|
||||
symbol={props.extraLongText ? 'check' : null}
|
||||
arrow={props.extraLongText ? 'next' : null}
|
||||
onClick={action('Action clicked')}
|
||||
>
|
||||
{props.extraLongText ? EXAMPLE_ACTION_LONG : EXAMPLE_ACTION}
|
||||
|
||||
@@ -352,6 +352,8 @@ export namespace AxoAlertDialog {
|
||||
| 'strong-destructive'
|
||||
| 'subtle-destructive';
|
||||
|
||||
export type Arrow = 'next' | 'external-link';
|
||||
|
||||
export type ActionProps = Readonly<{
|
||||
/**
|
||||
* Visual style of the button.
|
||||
@@ -360,11 +362,11 @@ export namespace AxoAlertDialog {
|
||||
/**
|
||||
* Optional leading icon.
|
||||
*/
|
||||
symbol?: AxoSymbol.Name;
|
||||
symbol?: AxoSymbol.Name | null;
|
||||
/**
|
||||
* When `true`, shows a forward arrow on the trailing side.
|
||||
*/
|
||||
arrow?: boolean;
|
||||
arrow?: Arrow | null;
|
||||
/**
|
||||
* Called when the button is clicked.
|
||||
*/
|
||||
@@ -397,7 +399,7 @@ export namespace AxoAlertDialog {
|
||||
<AxoButton.Root
|
||||
variant={props.variant}
|
||||
symbol={props.symbol}
|
||||
arrow={props.arrow ? 'next' : null}
|
||||
arrow={props.arrow}
|
||||
size="md"
|
||||
width="grow"
|
||||
onClick={props.onClick}
|
||||
|
||||
@@ -318,3 +318,22 @@ export function WidthsTest(): JSX.Element {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Arrows(): ReactNode {
|
||||
return (
|
||||
<div className={tw('flex gap-2')}>
|
||||
<AxoButton.Root variant="subtle-primary" size="md" arrow="collapse">
|
||||
Collapse
|
||||
</AxoButton.Root>
|
||||
<AxoButton.Root variant="subtle-primary" size="md" arrow="expand">
|
||||
Expand
|
||||
</AxoButton.Root>
|
||||
<AxoButton.Root variant="subtle-primary" size="md" arrow="next">
|
||||
Next
|
||||
</AxoButton.Root>
|
||||
<AxoButton.Root variant="subtle-primary" size="md" arrow="external-link">
|
||||
External Link
|
||||
</AxoButton.Root>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ export namespace AxoButton {
|
||||
* Note: Omitted 'prev' because arrow appears on trailing side,
|
||||
* back buttons should probably all use AxoIconButton.
|
||||
*/
|
||||
export type Arrow = 'collapse' | 'expand' | 'next';
|
||||
export type Arrow = 'collapse' | 'expand' | 'next' | 'external-link';
|
||||
|
||||
const baseStyles = tw(
|
||||
'relative inline-flex max-w-full items-center-safe justify-center-safe rounded-full',
|
||||
@@ -211,6 +211,7 @@ export namespace AxoButton {
|
||||
collapse: 'chevron-up',
|
||||
expand: 'chevron-down',
|
||||
next: 'chevron-[end]',
|
||||
'external-link': 'open',
|
||||
});
|
||||
|
||||
/** @testexport */
|
||||
@@ -248,7 +249,7 @@ export namespace AxoButton {
|
||||
/**
|
||||
* Optional leading icon.
|
||||
*/
|
||||
symbol?: AxoSymbol.Name;
|
||||
symbol?: AxoSymbol.Name | null;
|
||||
/**
|
||||
* Optional trailing arrow icon.
|
||||
*/
|
||||
|
||||
@@ -104,13 +104,14 @@ export namespace AxoCheckbox {
|
||||
className={tw(
|
||||
RootStyles.get(props.variant),
|
||||
'flex items-center justify-center',
|
||||
'border border-primary inset-shadow-on-color',
|
||||
'border border-secondary',
|
||||
'data-[state=unchecked]:bg-control',
|
||||
'data-[state=unchecked]:enabled:inset-shadow-on-color',
|
||||
'data-[state=unchecked]:enabled:active:bg-control-pressed',
|
||||
'data-[state=checked]:bg-accent',
|
||||
'data-[state=checked]:text-primary-oncolor',
|
||||
'data-[state=checked]:enabled:shadow-[0_1px_1px_var(--axo-shadow-color-elevation-1)]',
|
||||
'data-[state=checked]:enabled:active:bg-accent-pressed',
|
||||
'data-disabled:border-secondary',
|
||||
'data-[state=checked]:data-disabled:text-disabled-oncolor',
|
||||
'outline-none keyboard-mode:focus:axo-focus-ring',
|
||||
'overflow-hidden'
|
||||
|
||||
@@ -661,6 +661,8 @@ export namespace AxoDialog {
|
||||
| 'subtle-destructive'
|
||||
| 'subtle-secondary';
|
||||
|
||||
export type Arrow = 'next' | 'external-link';
|
||||
|
||||
export type ActionProps = Readonly<{
|
||||
/**
|
||||
* Visual style of the button.
|
||||
@@ -673,7 +675,7 @@ export namespace AxoDialog {
|
||||
/**
|
||||
* When `true`, shows a forward arrow on the trailing side.
|
||||
*/
|
||||
arrow?: boolean | null;
|
||||
arrow?: Arrow | null;
|
||||
/**
|
||||
* When `true`, shows a loading spinner and prevents interaction.
|
||||
*/
|
||||
@@ -700,7 +702,7 @@ export namespace AxoDialog {
|
||||
<AxoButton.Root
|
||||
variant={props.variant}
|
||||
symbol={props.symbol}
|
||||
arrow={props.arrow ? 'next' : null}
|
||||
arrow={props.arrow}
|
||||
pending={props.pending}
|
||||
disabled={props.disabled}
|
||||
size="md"
|
||||
|
||||
@@ -465,7 +465,7 @@
|
||||
/* prettier-ignore */
|
||||
@theme {
|
||||
--axo-color-border-primary: light-dark(--alpha(#000000 / 6%), --alpha(#ffffff / 6%));
|
||||
--axo-color-border-secondary: light-dark(--alpha(#000000 / 10%), --alpha(#ffffff / 10%));
|
||||
--axo-color-border-secondary: light-dark(--alpha(#000000 / 12%), --alpha(#ffffff / 12%));
|
||||
--axo-color-border-tertiary: light-dark(--alpha(#000000 / 24%), --alpha(#ffffff / 24%));
|
||||
--axo-color-border-selected: light-dark(#4655ff, #c9ceff);
|
||||
--axo-color-border-selected-oncolor: light-dark(#fafafa, #fafafa);
|
||||
|
||||
@@ -26,6 +26,10 @@ export function KeyTransparencyErrorDialog(
|
||||
const debugLogCheckboxId = useId();
|
||||
const [shareDebugLog, setShareDebugLog] = useState(false);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
onOpenChange(false);
|
||||
}, [onOpenChange]);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
onSubmit(shareDebugLog);
|
||||
}, [onSubmit, shareDebugLog]);
|
||||
@@ -35,13 +39,13 @@ export function KeyTransparencyErrorDialog(
|
||||
<AxoDialog.Content escape="cancel-is-noop" size="md">
|
||||
<AxoDialog.Body>
|
||||
<h3 className={tw('mt-6 mb-2 type-title-small')}>
|
||||
{i18n('icu:KeyTransparencyErrorDialog__Title')}
|
||||
{i18n('icu:KeyTransparencyErrorDialog__Title-v2')}
|
||||
</h3>
|
||||
<p className={tw('mb-3 type-body-medium text-primary')}>
|
||||
<AxoDialog.Description>
|
||||
<I18n
|
||||
i18n={i18n}
|
||||
id="icu:KeyTransparencyErrorDialog__Description"
|
||||
id="icu:KeyTransparencyErrorDialog__Description-v2"
|
||||
/>
|
||||
</AxoDialog.Description>
|
||||
</p>
|
||||
@@ -53,7 +57,7 @@ export function KeyTransparencyErrorDialog(
|
||||
onCheckedChange={setShareDebugLog}
|
||||
/>
|
||||
<label htmlFor={debugLogCheckboxId} className={tw('grow truncate')}>
|
||||
{i18n('icu:KeyTransparencyErrorDialog__ShareDebugLog__Label')}
|
||||
{i18n('icu:KeyTransparencyErrorDialog__ShareDebugLog__Label-v2')}
|
||||
</label>
|
||||
<AxoButton.Root
|
||||
variant="subtle-primary"
|
||||
@@ -68,8 +72,12 @@ export function KeyTransparencyErrorDialog(
|
||||
</AxoDialog.Body>
|
||||
<AxoDialog.Footer>
|
||||
<AxoDialog.Actions>
|
||||
<AxoDialog.Action variant="subtle-secondary" onClick={handleCancel}>
|
||||
{i18n('icu:KeyTransparencyErrorDialog__Cancel')}
|
||||
</AxoDialog.Action>
|
||||
<AxoDialog.Action
|
||||
variant="strong-primary"
|
||||
arrow="external-link"
|
||||
onClick={handleSubmit}
|
||||
pending={isSubmitting}
|
||||
>
|
||||
|
||||
@@ -1,29 +1,36 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { memo, useCallback, useState, useEffect, type JSX } from 'react';
|
||||
import { memo, useCallback, useState, type JSX } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import lodash from 'lodash';
|
||||
import { KeyTransparencyErrorDialog } from '../../components/KeyTransparencyErrorDialog.dom.tsx';
|
||||
import { createSupportUrl } from '../../util/createSupportUrl.std.ts';
|
||||
import { openLinkInWebBrowser } from '../../util/openLinkInWebBrowser.dom.ts';
|
||||
import { drop } from '../../util/drop.std.ts';
|
||||
import { useGlobalModalActions } from '../ducks/globalModals.preload.ts';
|
||||
import { getIntl } from '../selectors/user.std.ts';
|
||||
|
||||
const { noop } = lodash;
|
||||
async function uploadDebugLogs(): Promise<string | null> {
|
||||
try {
|
||||
const logData = await ipcRenderer.invoke('fetch-log');
|
||||
const logs: string = await ipcRenderer.invoke(
|
||||
'DebugLogs.getLogs',
|
||||
logData,
|
||||
window.navigator.userAgent
|
||||
);
|
||||
const debugLogUrl = await ipcRenderer.invoke('DebugLogs.upload', logs);
|
||||
return debugLogUrl;
|
||||
} catch {
|
||||
// Ignore
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const SmartKeyTransparencyErrorDialog = memo(
|
||||
function SmartKeyTransparencyErrorDialog(): JSX.Element | null {
|
||||
const i18n = useSelector(getIntl);
|
||||
const { hideKeyTransparencyErrorDialog } = useGlobalModalActions();
|
||||
const [request, setRequest] = useState<
|
||||
| undefined
|
||||
| Readonly<{
|
||||
shareDebugLog: boolean;
|
||||
}>
|
||||
>();
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
const handleOpenChange = useCallback(
|
||||
(open: boolean) => {
|
||||
@@ -34,62 +41,26 @@ export const SmartKeyTransparencyErrorDialog = memo(
|
||||
[hideKeyTransparencyErrorDialog]
|
||||
);
|
||||
|
||||
const handleSubmit = useCallback((shareDebugLog: boolean) => {
|
||||
setRequest({ shareDebugLog });
|
||||
}, []);
|
||||
const handleSubmit = useCallback(
|
||||
async (shareDebugLog: boolean) => {
|
||||
setSubmitting(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (request === undefined) {
|
||||
return noop;
|
||||
}
|
||||
let debugLogUrl: string | null = null;
|
||||
if (shareDebugLog) {
|
||||
debugLogUrl = await uploadDebugLogs();
|
||||
}
|
||||
|
||||
let canceled = false;
|
||||
const supportURL = createSupportUrl({
|
||||
locale: window.SignalContext.getI18nLocale(),
|
||||
query: debugLogUrl ? { kt: debugLogUrl } : undefined,
|
||||
});
|
||||
|
||||
drop(
|
||||
(async () => {
|
||||
const query: Record<string, string> = {
|
||||
kt: '',
|
||||
};
|
||||
|
||||
if (request.shareDebugLog) {
|
||||
try {
|
||||
const logData = await ipcRenderer.invoke('fetch-log');
|
||||
const logs: string = await ipcRenderer.invoke(
|
||||
'DebugLogs.getLogs',
|
||||
logData,
|
||||
window.navigator.userAgent
|
||||
);
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
query.debugLog = await ipcRenderer.invoke(
|
||||
'DebugLogs.upload',
|
||||
logs
|
||||
);
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
const supportURL = createSupportUrl({
|
||||
locale: window.SignalContext.getI18nLocale(),
|
||||
query,
|
||||
});
|
||||
|
||||
openLinkInWebBrowser(supportURL);
|
||||
|
||||
setRequest(undefined);
|
||||
hideKeyTransparencyErrorDialog();
|
||||
})()
|
||||
);
|
||||
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
}, [request, hideKeyTransparencyErrorDialog]);
|
||||
openLinkInWebBrowser(supportURL);
|
||||
setSubmitting(false);
|
||||
hideKeyTransparencyErrorDialog();
|
||||
},
|
||||
[hideKeyTransparencyErrorDialog]
|
||||
);
|
||||
|
||||
return (
|
||||
<KeyTransparencyErrorDialog
|
||||
@@ -98,7 +69,7 @@ export const SmartKeyTransparencyErrorDialog = memo(
|
||||
onOpenChange={handleOpenChange}
|
||||
onViewDebugLog={() => window.IPC.showDebugLog({ mode: 'close' })}
|
||||
onSubmit={handleSubmit}
|
||||
isSubmitting={request !== undefined}
|
||||
isSubmitting={submitting}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user