summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2021-12-03 12:03:08 +0100
committerOskar Nyberg <oskar@mullvad.net>2021-12-03 12:03:08 +0100
commit91ba2ee587eab108c9f651215f8806ba353a3ecd (patch)
tree7ead7cffea497a9ab264fc28101c5d4ea94b6280 /gui/src
parenta671b5208ff4942a760239b761ed1b6e2487007c (diff)
parent5e646f2ca5cd54092f7edc19be75bdfa74446e13 (diff)
downloadmullvadvpn-91ba2ee587eab108c9f651215f8806ba353a3ecd.tar.xz
mullvadvpn-91ba2ee587eab108c9f651215f8806ba353a3ecd.zip
Merge branch 'update-account-number-label'
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/renderer/components/AccountTokenLabel.tsx2
-rw-r--r--gui/src/renderer/components/ClipboardLabel.tsx114
-rw-r--r--gui/src/renderer/components/ExpiredAccountErrorView.tsx5
-rw-r--r--gui/src/renderer/components/ExpiredAccountErrorViewStyles.tsx6
-rw-r--r--gui/src/renderer/components/WireguardKeys.tsx6
5 files changed, 94 insertions, 39 deletions
diff --git a/gui/src/renderer/components/AccountTokenLabel.tsx b/gui/src/renderer/components/AccountTokenLabel.tsx
index b2623a5ad2..704f41af10 100644
--- a/gui/src/renderer/components/AccountTokenLabel.tsx
+++ b/gui/src/renderer/components/AccountTokenLabel.tsx
@@ -3,6 +3,7 @@ import ClipboardLabel from './ClipboardLabel';
interface IAccountTokenLabelProps {
accountToken: string;
+ obscureValue?: boolean;
className?: string;
}
@@ -11,6 +12,7 @@ export default function AccountTokenLabel(props: IAccountTokenLabelProps) {
<ClipboardLabel
value={props.accountToken}
displayValue={formatAccountToken(props.accountToken)}
+ obscureValue={props.obscureValue}
className={props.className}
/>
);
diff --git a/gui/src/renderer/components/ClipboardLabel.tsx b/gui/src/renderer/components/ClipboardLabel.tsx
index 43b0d2c764..071a6991a8 100644
--- a/gui/src/renderer/components/ClipboardLabel.tsx
+++ b/gui/src/renderer/components/ClipboardLabel.tsx
@@ -1,58 +1,104 @@
-import * as React from 'react';
+import { useCallback } from 'react';
import styled from 'styled-components';
+import { colors } from '../../config.json';
import { messages } from '../../shared/gettext';
import log from '../../shared/logging';
-import { Scheduler } from '../../shared/scheduler';
+import { useScheduler } from '../../shared/scheduler';
+import { useBoolean } from '../lib/utilityHooks';
+import ImageView from './ImageView';
+
+const COPIED_ICON_DURATION = 2000;
interface IProps {
value: string;
displayValue?: string;
- delay: number;
+ obscureValue?: boolean;
message?: string;
className?: string;
}
-interface IState {
- showsMessage: boolean;
-}
-
-const Label = styled.span({
- cursor: 'pointer',
+const StyledLabelContainer = styled.div({
+ display: 'flex',
+ flex: 1,
+ height: '19px',
+ alignItems: 'center',
});
-export default class ClipboardLabel extends React.Component<IProps, IState> {
- public static defaultProps: Partial<IProps> = {
- delay: 3000,
- };
+const StyledLabel = styled.span({
+ flex: 1,
+});
- public state: IState = {
- showsMessage: false,
- };
+const StyledButton = styled.button({
+ cursor: 'default',
+ padding: 0,
+ marginLeft: '20px',
+ backgroundColor: 'transparent',
+ border: 'none',
+});
- private scheduler = new Scheduler();
+const StyledCopyButton = styled(StyledButton)({
+ width: '24px',
+});
- public componentWillUnmount() {
- this.scheduler.cancel();
- }
+export default function ClipboardLabel(props: IProps) {
+ const [obscured, , , toggleObscured] = useBoolean(props.obscureValue ?? true);
+ const [justCopied, setJustCopied, resetJustCopied] = useBoolean(false);
- public render() {
- const message = this.props.message ?? messages.gettext('COPIED TO CLIPBOARD!');
- const displayValue = this.props.displayValue ?? this.props.value;
- return (
- <Label className={this.props.className} onClick={this.handlePress}>
- {this.state.showsMessage ? message : displayValue}
- </Label>
- );
- }
+ const copiedScheduler = useScheduler();
- private handlePress = async () => {
+ const onCopy = useCallback(async () => {
try {
- await navigator.clipboard.writeText(this.props.value);
- this.scheduler.schedule(() => this.setState({ showsMessage: false }), this.props.delay);
- this.setState({ showsMessage: true });
+ await navigator.clipboard.writeText(props.value);
+ copiedScheduler.schedule(resetJustCopied, COPIED_ICON_DURATION);
+ setJustCopied();
} catch (e) {
const error = e as Error;
log.error(`Failed to copy to clipboard: ${error.message}`);
}
- };
+ }, [props.value, copiedScheduler, setJustCopied, resetJustCopied]);
+
+ const value = props.displayValue ?? props.value;
+ return (
+ <StyledLabelContainer>
+ <StyledLabel className={props.className} aria-hidden={obscured}>
+ {obscured ? '●●●● ●●●● ●●●● ●●●●' : value}
+ </StyledLabel>
+ {props.obscureValue !== false && (
+ <StyledButton
+ onClick={toggleObscured}
+ aria-label={
+ obscured
+ ? // This line is here to prevent the following one to be moved up here by prettier
+ // TRANSLATORS: Provided to accessibility tools such as screenreaders to describe
+ // TRANSLATORS: the button which unobscures the account number.
+ messages.pgettext('accessibility', 'Show account number')
+ : // This line is here to prevent the following one to be moved up here by prettier
+ // TRANSLATORS: Provided to accessibility tools such as screenreaders to describe
+ // TRANSLATORS: the button which obscures the account number.
+ messages.pgettext('accessibility', 'Hide account number')
+ }>
+ <ImageView
+ source={obscured ? 'icon-unobscure' : 'icon-obscure'}
+ tintColor={colors.white}
+ tintHoverColor={colors.white80}
+ width={24}
+ />
+ </StyledButton>
+ )}
+ <StyledCopyButton
+ onClick={onCopy}
+ aria-label={
+ // TRANSLATORS: Provided to accessibility tools such as screenreaders to describe a button
+ // TRANSLATORS: which copies the account number to the clipboard.
+ messages.pgettext('accessibility', 'Copy account number')
+ }>
+ <ImageView
+ source={justCopied ? 'icon-tick' : 'icon-copy'}
+ tintColor={justCopied ? colors.green : colors.white}
+ tintHoverColor={justCopied ? colors.green : colors.white80}
+ width={justCopied ? 22 : 24}
+ />
+ </StyledCopyButton>
+ </StyledLabelContainer>
+ );
}
diff --git a/gui/src/renderer/components/ExpiredAccountErrorView.tsx b/gui/src/renderer/components/ExpiredAccountErrorView.tsx
index d4ac395c2c..0fedb67fdf 100644
--- a/gui/src/renderer/components/ExpiredAccountErrorView.tsx
+++ b/gui/src/renderer/components/ExpiredAccountErrorView.tsx
@@ -127,7 +127,10 @@ export default class ExpiredAccountErrorView extends React.Component<
<StyledAccountTokenMessage>
{messages.pgettext('connect-view', 'Here’s your account number. Save it!')}
<StyledAccountTokenContainer>
- <StyledAccountTokenLabel accountToken={this.props.accountToken || ''} />
+ <StyledAccountTokenLabel
+ accountToken={this.props.accountToken || ''}
+ obscureValue={false}
+ />
</StyledAccountTokenContainer>
</StyledAccountTokenMessage>
diff --git a/gui/src/renderer/components/ExpiredAccountErrorViewStyles.tsx b/gui/src/renderer/components/ExpiredAccountErrorViewStyles.tsx
index 8872bbb312..5aa4e6ea63 100644
--- a/gui/src/renderer/components/ExpiredAccountErrorViewStyles.tsx
+++ b/gui/src/renderer/components/ExpiredAccountErrorViewStyles.tsx
@@ -14,8 +14,8 @@ export const StyledHeader = styled(DefaultHeaderBar)({
export const StyledAccountTokenLabel = styled(AccountTokenLabel)({
fontFamily: 'Open Sans',
- lineHeight: '24px',
- fontSize: '24px',
+ lineHeight: '20px',
+ fontSize: '20px',
fontWeight: 800,
color: colors.white,
});
@@ -80,6 +80,6 @@ export const StyledStatusIcon = styled.div({
export const StyledAccountTokenContainer = styled.div({
display: 'flex',
- height: '68px',
+ height: '50px',
alignItems: 'center',
});
diff --git a/gui/src/renderer/components/WireguardKeys.tsx b/gui/src/renderer/components/WireguardKeys.tsx
index 3201e0a708..be8c043458 100644
--- a/gui/src/renderer/components/WireguardKeys.tsx
+++ b/gui/src/renderer/components/WireguardKeys.tsx
@@ -242,7 +242,11 @@ export default class WireguardKeys extends React.Component<IProps, IState> {
const publicKey = this.props.keyState.key.publicKey;
return (
<StyledRowValue title={this.props.keyState.key.publicKey}>
- <ClipboardLabel value={publicKey} displayValue={publicKey.substring(0, 20) + '...'} />
+ <ClipboardLabel
+ value={publicKey}
+ displayValue={publicKey.substring(0, 20) + '...'}
+ obscureValue={false}
+ />
</StyledRowValue>
);
}