summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2023-06-14 09:59:24 +0200
committerOskar Nyberg <oskar@mullvad.net>2023-06-28 09:13:18 +0200
commitd46eb1205af3c00c27016b8affb79f66a6acced6 (patch)
tree10fc032ac175239bcab7552f49c0123c06c704dd /gui/src
parentee8c6577f91ddf60ffe944624091ceaa6a81ca34 (diff)
downloadmullvadvpn-d46eb1205af3c00c27016b8affb79f66a6acced6.tar.xz
mullvadvpn-d46eb1205af3c00c27016b8affb79f66a6acced6.zip
Add account info to headerbar
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/renderer/components/HeaderBar.tsx76
1 files changed, 72 insertions, 4 deletions
diff --git a/gui/src/renderer/components/HeaderBar.tsx b/gui/src/renderer/components/HeaderBar.tsx
index c1cda7369c..7ebf448e5c 100644
--- a/gui/src/renderer/components/HeaderBar.tsx
+++ b/gui/src/renderer/components/HeaderBar.tsx
@@ -1,12 +1,16 @@
import React, { useCallback } from 'react';
+import { sprintf } from 'sprintf-js';
import styled from 'styled-components';
import { colors } from '../../config.json';
+import { closeToExpiry, formatRemainingTime, hasExpired } from '../../shared/account-expiry';
import { TunnelState } from '../../shared/daemon-rpc-types';
import { messages } from '../../shared/gettext';
+import { capitalizeEveryWord } from '../../shared/string-helpers';
import { transitions, useHistory } from '../lib/history';
import { RoutePath } from '../lib/routes';
import { useSelector } from '../redux/store';
+import { tinyText } from './common-styles';
import { FocusFallback } from './Focus';
import ImageView from './ImageView';
@@ -26,26 +30,32 @@ const headerBarStyleColorMap = {
interface IHeaderBarContainerProps {
barStyle?: HeaderBarStyle;
+ accountInfoVisible: boolean;
unpinnedWindow: boolean;
}
const HeaderBarContainer = styled.header({}, (props: IHeaderBarContainerProps) => ({
- padding: '12px 16px',
+ padding: '15px 16px 0px',
+ minHeight: props.accountInfoVisible ? '80px' : '68px',
+ height: props.accountInfoVisible ? '80px' : '68px',
backgroundColor: headerBarStyleColorMap[props.barStyle ?? HeaderBarStyle.default],
+ transitionProperty: 'height, min-height',
+ transitionDuration: '250ms',
+ transitionTimingFunction: 'ease-in-out',
}));
const HeaderBarContent = styled.div({
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
- // In views without the brand components we still want the Header to have the same height.
- minHeight: '51px',
+ height: '38px',
});
interface IHeaderBarProps {
barStyle?: HeaderBarStyle;
className?: string;
children?: React.ReactNode;
+ showAccountInfo?: boolean;
}
export default function HeaderBar(props: IHeaderBarProps) {
@@ -55,8 +65,10 @@ export default function HeaderBar(props: IHeaderBarProps) {
<HeaderBarContainer
barStyle={props.barStyle}
className={props.className}
+ accountInfoVisible={props.showAccountInfo ?? false}
unpinnedWindow={unpinnedWindow}>
<HeaderBarContent>{props.children}</HeaderBarContent>
+ {props.showAccountInfo && <HeaderBarDeviceInfo />}
</HeaderBarContainer>
);
}
@@ -81,6 +93,62 @@ export function Brand(props: React.HTMLAttributes<HTMLDivElement>) {
);
}
+const StyledAccountInfo = styled.div({
+ display: 'flex',
+ marginTop: '2px',
+ maxWidth: '100%',
+});
+
+const StyledDeviceLabel = styled.div(tinyText, {
+ fontSize: '10px',
+ color: colors.white80,
+ whiteSpace: 'nowrap',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+});
+
+const StyledTimeLeftLabel = styled.div(tinyText, {
+ fontSize: '10px',
+ color: colors.white80,
+ marginLeft: '16px',
+ whiteSpace: 'nowrap',
+});
+
+function HeaderBarDeviceInfo() {
+ const deviceName = useSelector((state) => state.account.deviceName);
+ const accountExpiry = useSelector((state) => state.account.expiry);
+ const isOutOfTime = accountExpiry ? hasExpired(accountExpiry) : false;
+ const formattedExpiry = isOutOfTime
+ ? sprintf(messages.ngettext('1 day', '%d days', 0), 0)
+ : accountExpiry
+ ? formatRemainingTime(accountExpiry)
+ : '';
+
+ return (
+ <StyledAccountInfo>
+ <StyledDeviceLabel>
+ {sprintf(
+ // TRANSLATORS: A label that will display the newly created device name to inform the user
+ // TRANSLATORS: about it.
+ // TRANSLATORS: Available placeholders:
+ // TRANSLATORS: %(deviceName)s - The name of the current device
+ messages.pgettext('device-management', 'Device name: %(deviceName)s'),
+ {
+ deviceName: capitalizeEveryWord(deviceName ?? ''),
+ },
+ )}
+ </StyledDeviceLabel>
+ {accountExpiry && !closeToExpiry(accountExpiry) && !isOutOfTime && (
+ <StyledTimeLeftLabel>
+ {sprintf(messages.pgettext('device-management', 'Time left: %(timeLeft)s'), {
+ timeLeft: formattedExpiry,
+ })}
+ </StyledTimeLeftLabel>
+ )}
+ </StyledAccountInfo>
+ );
+}
+
const HeaderBarSettingsButtonContainer = styled.button({
cursor: 'default',
padding: 0,
@@ -147,7 +215,7 @@ export function DefaultHeaderBar(props: IHeaderBarProps) {
const loggedIn = useSelector((state) => state.account.status.type === 'ok');
return (
- <HeaderBar {...props}>
+ <HeaderBar showAccountInfo={loggedIn} {...props}>
<FocusFallback>
<Brand />
</FocusFallback>