diff options
| author | Oliver <oliver@mohlin.dev> | 2025-01-07 11:40:51 +0100 |
|---|---|---|
| committer | Markus Pettersson <markus.pettersson@mullvad.net> | 2025-01-13 15:02:51 +0100 |
| commit | 92852eb1cffab60f6979877c12c8629a9694153d (patch) | |
| tree | 11a5ab89e45b7df325c784440c16d7e57b59e57b | |
| parent | 33caf981637a2391eb786293cf52c2c11d8dc548 (diff) | |
| download | mullvadvpn-92852eb1cffab60f6979877c12c8629a9694153d.tar.xz mullvadvpn-92852eb1cffab60f6979877c12c8629a9694153d.zip | |
Add MainHeader component
6 files changed, 198 insertions, 0 deletions
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/main-header/MainHeader.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/main-header/MainHeader.tsx new file mode 100644 index 0000000000..b3d2d7f676 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/main-header/MainHeader.tsx @@ -0,0 +1,80 @@ +import { TunnelState } from '../../../shared/daemon-rpc-types'; +import { Flex, Header, HeaderProps, Logo, LogoProps } from '../../lib/components'; +import { Spacings } from '../../lib/foundations'; +import { useSelector } from '../../redux/store'; +import { FocusFallback } from '../Focus'; +import { + MainHeaderBarAccountButton, + MainHeaderDeviceInfo, + MainHeaderSettingsButton, +} from './components'; + +export interface MainHeaderProps extends Omit<HeaderProps, 'variant' | 'size'> { + variant?: HeaderProps['variant'] | 'basedOnConnectionStatus'; + size?: HeaderProps['size'] | 'basedOnLoginStatus'; + logoVariant?: LogoProps['variant'] | 'none'; + children?: React.ReactNode; +} + +const MainHeader = ({ + logoVariant = 'both', + variant: variantProp, + size: sizeProp, + children, + ...props +}: MainHeaderProps) => { + const connectionStatus = useSelector((state) => state.connection.status); + + const variant = + variantProp === 'basedOnConnectionStatus' + ? getVariantByTunnelState(connectionStatus) + : variantProp; + + const loggedIn = useSelector((state) => state.account.status.type === 'ok'); + const size = sizeProp === 'basedOnLoginStatus' ? (loggedIn ? '2' : '1') : sizeProp; + + return ( + <Header variant={variant} size={size} {...props}> + <Header.MainRow> + <FocusFallback> + {logoVariant !== 'none' ? <Logo variant={logoVariant} /> : <div />} + </FocusFallback> + <Flex $gap={Spacings.spacing5} $alignItems="center"> + {children} + </Flex> + </Header.MainRow> + {size == '2' && ( + <Header.SubRow> + <MainHeaderDeviceInfo /> + </Header.SubRow> + )} + </Header> + ); +}; + +const MainHeaderNamespace = Object.assign(MainHeader, { + AccountButton: MainHeaderBarAccountButton, + SettingsButton: MainHeaderSettingsButton, +}); + +export { MainHeaderNamespace as MainHeader }; + +const getVariantByTunnelState = (tunnelState: TunnelState): HeaderProps['variant'] => { + switch (tunnelState.state) { + case 'disconnected': + return 'error'; + case 'connecting': + case 'connected': + return 'success'; + case 'error': + return !tunnelState.details.blockingError ? 'success' : 'error'; + case 'disconnecting': + switch (tunnelState.details) { + case 'block': + case 'reconnect': + return 'success'; + case 'nothing': + return 'error'; + } + } +}; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/main-header/components/MainHeaderAccountButton.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/main-header/components/MainHeaderAccountButton.tsx new file mode 100644 index 0000000000..b7a440a9d4 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/main-header/components/MainHeaderAccountButton.tsx @@ -0,0 +1,33 @@ +import { useCallback } from 'react'; + +import { messages } from '../../../../shared/gettext'; +import { IconButton, IconButtonProps } from '../../../lib/components'; +import { transitions, useHistory } from '../../../lib/history'; +import { RoutePath } from '../../../lib/routes'; +import { useSelector } from '../../../redux/store'; + +export type MainHeaderBarAccountButtonProps = Omit<IconButtonProps, 'icon'>; + +export const MainHeaderBarAccountButton = (props: MainHeaderBarAccountButtonProps) => { + const history = useHistory(); + const openAccount = useCallback( + () => history.push(RoutePath.account, { transition: transitions.show }), + [history], + ); + + const loggedIn = useSelector((state) => state.account.status.type === 'ok'); + if (!loggedIn) { + return null; + } + + return ( + <IconButton + icon="icon-account" + variant="secondary" + onClick={openAccount} + data-testid="account-button" + aria-label={messages.gettext('Account settings')} + {...props} + /> + ); +}; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/main-header/components/MainHeaderDeviceInfo.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/main-header/components/MainHeaderDeviceInfo.tsx new file mode 100644 index 0000000000..4bf2b6a142 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/main-header/components/MainHeaderDeviceInfo.tsx @@ -0,0 +1,54 @@ +import { sprintf } from 'sprintf-js'; +import styled from 'styled-components'; + +import { closeToExpiry, formatRemainingTime, hasExpired } from '../../../../shared/account-expiry'; +import { messages } from '../../../../shared/gettext'; +import { capitalizeEveryWord } from '../../../../shared/string-helpers'; +import { FootnoteMini } from '../../../lib/components'; +import { Colors } from '../../../lib/foundations'; +import { useSelector } from '../../../redux/store'; + +const StyledTimeLeftLabel = styled(FootnoteMini)({ + whiteSpace: 'nowrap', +}); + +const StyledDeviceLabel = styled(FootnoteMini)({ + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', +}); + +export const MainHeaderDeviceInfo = () => { + 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 ( + <> + <StyledDeviceLabel color={Colors.white80}> + {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 color={Colors.white80}> + {sprintf(messages.pgettext('device-management', 'Time left: %(timeLeft)s'), { + timeLeft: formattedExpiry, + })} + </StyledTimeLeftLabel> + )} + </> + ); +}; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/main-header/components/MainHeaderSettingsButton.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/main-header/components/MainHeaderSettingsButton.tsx new file mode 100644 index 0000000000..fa2b1f1c5f --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/main-header/components/MainHeaderSettingsButton.tsx @@ -0,0 +1,27 @@ +import { useCallback } from 'react'; + +import { messages } from '../../../../shared/gettext'; +import { IconButton, IconButtonProps } from '../../../lib/components'; +import { transitions, useHistory } from '../../../lib/history'; +import { RoutePath } from '../../../lib/routes'; + +export type MainHeaderSettingsButtonProps = Omit<IconButtonProps, 'icon'>; + +export function MainHeaderSettingsButton(props: MainHeaderSettingsButtonProps) { + const history = useHistory(); + + const openSettings = useCallback(() => { + if (!props.disabled) { + history.push(RoutePath.settings, { transition: transitions.show }); + } + }, [history, props.disabled]); + + return ( + <IconButton + icon="icon-settings" + variant="secondary" + onClick={openSettings} + aria-label={messages.gettext('Settings')} + /> + ); +} diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/main-header/components/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/main-header/components/index.ts new file mode 100644 index 0000000000..9ff843a82c --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/main-header/components/index.ts @@ -0,0 +1,3 @@ +export * from './MainHeaderDeviceInfo'; +export * from './MainHeaderAccountButton'; +export * from './MainHeaderSettingsButton'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/main-header/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/main-header/index.ts new file mode 100644 index 0000000000..3d8a1927af --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/main-header/index.ts @@ -0,0 +1 @@ +export * from './MainHeader'; |
