diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2023-06-14 09:59:24 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2023-06-28 09:13:18 +0200 |
| commit | d46eb1205af3c00c27016b8affb79f66a6acced6 (patch) | |
| tree | 10fc032ac175239bcab7552f49c0123c06c704dd /gui/src | |
| parent | ee8c6577f91ddf60ffe944624091ceaa6a81ca34 (diff) | |
| download | mullvadvpn-d46eb1205af3c00c27016b8affb79f66a6acced6.tar.xz mullvadvpn-d46eb1205af3c00c27016b8affb79f66a6acced6.zip | |
Add account info to headerbar
Diffstat (limited to 'gui/src')
| -rw-r--r-- | gui/src/renderer/components/HeaderBar.tsx | 76 |
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> |
