Update key transparency error dialog

Co-authored-by: Jamie <113370520+jamiebuilds-signal@users.noreply.github.com>
This commit is contained in:
automated-signal
2026-09-01 17:23:56 +00:00
committed by GitHub
co-authored by Jamie
parent 7679a21bd6
commit 032eb916b3
10 changed files with 99 additions and 79 deletions
+16
View File
@@ -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"
+2 -2
View File
@@ -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}
+5 -3
View File
@@ -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}
+19
View File
@@ -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>
);
}
+3 -2
View File
@@ -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.
*/
+3 -2
View File
@@ -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'
+4 -2
View File
@@ -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"
+1 -1
View File
@@ -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}
/>
);
}