diff options
Diffstat (limited to 'gui')
| -rw-r--r-- | gui/src/renderer/app.tsx | 4 | ||||
| -rw-r--r-- | gui/src/renderer/components/Account.tsx | 337 | ||||
| -rw-r--r-- | gui/src/renderer/components/AppRouter.tsx | 4 | ||||
| -rw-r--r-- | gui/src/renderer/containers/AccountPage.tsx | 32 |
4 files changed, 176 insertions, 201 deletions
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx index c991e07104..ca349b073b 100644 --- a/gui/src/renderer/app.tsx +++ b/gui/src/renderer/app.tsx @@ -367,7 +367,7 @@ export default class AppRenderer { this.loginState = 'none'; }; - public async logout(transition = transitions.dismiss) { + public logout = async (transition = transitions.dismiss) => { try { this.history.reset(RoutePath.login, transition); await IpcRendererEventChannel.account.logout(); @@ -375,7 +375,7 @@ export default class AppRenderer { const error = e as Error; log.info('Failed to logout: ', error.message); } - } + }; public leaveRevokedDevice = async () => { await this.logout(transitions.pop); diff --git a/gui/src/renderer/components/Account.tsx b/gui/src/renderer/components/Account.tsx index 83d06fa290..d46b9e3a03 100644 --- a/gui/src/renderer/components/Account.tsx +++ b/gui/src/renderer/components/Account.tsx @@ -1,8 +1,12 @@ -import * as React from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import { links } from '../../config.json'; import { formatDate, hasExpired } from '../../shared/account-expiry'; -import { DeviceState } from '../../shared/daemon-rpc-types'; import { messages } from '../../shared/gettext'; +import { useAppContext } from '../context'; +import useActions from '../lib/actionsHook'; +import { useHistory } from '../lib/history'; +import account from '../redux/account/actions'; import { useSelector } from '../redux/store'; import { AccountContainer, @@ -25,185 +29,193 @@ import { NavigationBar, NavigationItems, TitleBarItem } from './NavigationBar'; import { RedeemVoucherButton } from './RedeemVoucher'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; -interface IProps { - isOffline: boolean; - prepareLogout: () => void; - cancelLogout: () => void; - onLogout: () => void; - onClose: () => void; - onBuyMore: () => Promise<void>; - updateAccountData: () => void; - getDeviceState: () => Promise<DeviceState | undefined>; -} +type LogoutDialogStage = 'checking-ports' | 'confirm' | undefined; -interface IState { - logoutDialogVisible: boolean; - logoutDialogStage?: 'checking-ports' | 'confirm'; -} +export default function Account() { + const history = useHistory(); + const [logoutDialogStage, setLogoutDialogStage] = useState<LogoutDialogStage>(); + const [logoutDialogVisible, setLogoutDialogVisible] = useState(false); + const isOffline = useSelector((state) => state.connection.isBlocked); -export default class Account extends React.Component<IProps, IState> { - public state: IState = { - logoutDialogVisible: false, - }; + const { logout, updateAccountData, openLinkWithAuth, getDeviceState } = useAppContext(); - public componentDidMount() { - this.props.updateAccountData(); - } + const { cancelLogout, prepareLogout } = useActions(account); - public render() { - return ( - <BackAction action={this.props.onClose}> - <Layout> - <SettingsContainer> - <NavigationBar> - <NavigationItems> - <TitleBarItem> - { - // TRANSLATORS: Title label in navigation bar - messages.pgettext('account-view', 'Account') - } - </TitleBarItem> - </NavigationItems> - </NavigationBar> + const confirmLogout = useCallback(async () => { + onHideLogoutConfirmationDialog(); + await logout(); + }, []); - <AccountContainer> - <SettingsHeader> - <HeaderTitle>{messages.pgettext('account-view', 'Account')}</HeaderTitle> - </SettingsHeader> + const onCancelLogout = useCallback(() => { + cancelLogout(); + onHideLogoutConfirmationDialog(); + }, []); - <AccountRows> - <AccountRow> - <AccountRowLabel> - {messages.pgettext('device-management', 'Device name')} - </AccountRowLabel> - <DeviceNameRow /> - </AccountRow> + const onBuyMore = useCallback(async () => { + await openLinkWithAuth(links.purchase); + }, []); - <AccountRow> - <AccountRowLabel> - {messages.pgettext('account-view', 'Account number')} - </AccountRowLabel> - <AccountNumberRow /> - </AccountRow> + const onHideLogoutConfirmationDialog = () => setLogoutDialogVisible(false); - <AccountRow> - <AccountRowLabel> - {messages.pgettext('account-view', 'Paid until')} - </AccountRowLabel> - <AccountExpiryRow /> - </AccountRow> - </AccountRows> + const onTryLogout = useCallback(async () => { + setLogoutDialogVisible(true); + setLogoutDialogStage('checking-ports'); + prepareLogout(); - <Footer> - <AppButton.ButtonGroup> - <AppButton.BlockingButton - disabled={this.props.isOffline} - onClick={this.props.onBuyMore}> - <AriaDescriptionGroup> - <AriaDescribed> - <AppButton.GreenButton> - <AppButton.Label>{messages.gettext('Buy more credit')}</AppButton.Label> - <AriaDescription> - <AppButton.Icon - source="icon-extLink" - height={16} - width={16} - aria-label={messages.pgettext('accessibility', 'Opens externally')} - /> - </AriaDescription> - </AppButton.GreenButton> - </AriaDescribed> - </AriaDescriptionGroup> - </AppButton.BlockingButton> + const deviceState = await getDeviceState(); - <RedeemVoucherButton /> + if ( + deviceState?.type === 'logged in' && + deviceState.accountAndDevice.device?.ports !== undefined && + deviceState.accountAndDevice.device.ports.length > 0 + ) { + setLogoutDialogStage('confirm'); + } else { + await confirmLogout(); + } + }, []); - <AppButton.RedButton onClick={this.onTryLogout}> - {messages.pgettext('account-view', 'Log out')} - </AppButton.RedButton> - </AppButton.ButtonGroup> - </Footer> - </AccountContainer> - </SettingsContainer> + useEffect(() => { + updateAccountData(); + }, []); - {this.renderLogoutDialog()} - </Layout> - </BackAction> - ); - } + return ( + <BackAction action={history.pop}> + <Layout> + <SettingsContainer> + <NavigationBar> + <NavigationItems> + <TitleBarItem> + { + // TRANSLATORS: Title label in navigation bar + messages.pgettext('account-view', 'Account') + } + </TitleBarItem> + </NavigationItems> + </NavigationBar> - private renderLogoutDialog() { - const modalType = - this.state.logoutDialogStage === 'checking-ports' ? undefined : ModalAlertType.warning; + <AccountContainer> + <SettingsHeader> + <HeaderTitle>{messages.pgettext('account-view', 'Account')}</HeaderTitle> + </SettingsHeader> - const message = - this.state.logoutDialogStage === 'checking-ports' ? ( - <StyledSpinnerContainer> - <ImageView source="icon-spinner" width={60} height={60} /> - </StyledSpinnerContainer> - ) : ( - <ModalMessage> - { - // TRANSLATORS: This is is a further explanation of what happens when logging out. - messages.pgettext( - 'device-management', - 'The ports forwarded to this device will be deleted if you log out.', - ) - } - </ModalMessage> - ); + <AccountRows> + <AccountRow> + <AccountRowLabel> + {messages.pgettext('device-management', 'Device name')} + </AccountRowLabel> + <DeviceNameRow /> + </AccountRow> - const buttons = - this.state.logoutDialogStage === 'checking-ports' - ? [] - : [ - <AppButton.RedButton key="logout" onClick={this.confirmLogout}> - { - // TRANSLATORS: Confirmation button when logging out - messages.pgettext('device-management', 'Log out anyway') - } - </AppButton.RedButton>, - <AppButton.BlueButton key="back" onClick={this.cancelLogout}> - {messages.gettext('Back')} - </AppButton.BlueButton>, - ]; + <AccountRow> + <AccountRowLabel> + {messages.pgettext('account-view', 'Account number')} + </AccountRowLabel> + <AccountNumberRow /> + </AccountRow> - return ( - <ModalAlert isOpen={this.state.logoutDialogVisible} type={modalType} buttons={buttons}> - {message} - </ModalAlert> - ); - } + <AccountRow> + <AccountRowLabel>{messages.pgettext('account-view', 'Paid until')}</AccountRowLabel> + <AccountExpiryRow /> + </AccountRow> + </AccountRows> - private onTryLogout = async () => { - this.setState({ logoutDialogVisible: true, logoutDialogStage: 'checking-ports' }); - this.props.prepareLogout(); + <Footer> + <AppButton.ButtonGroup> + <AppButton.BlockingButton disabled={isOffline} onClick={onBuyMore}> + <AriaDescriptionGroup> + <AriaDescribed> + <AppButton.GreenButton> + <AppButton.Label>{messages.gettext('Buy more credit')}</AppButton.Label> + <AriaDescription> + <AppButton.Icon + source="icon-extLink" + height={16} + width={16} + aria-label={messages.pgettext('accessibility', 'Opens externally')} + /> + </AriaDescription> + </AppButton.GreenButton> + </AriaDescribed> + </AriaDescriptionGroup> + </AppButton.BlockingButton> - const deviceState = await this.props.getDeviceState(); - if ( - deviceState?.type === 'logged in' && - deviceState.accountAndDevice.device?.ports !== undefined && - deviceState.accountAndDevice.device.ports.length > 0 - ) { - this.setState({ logoutDialogStage: 'confirm' }); - } else { - this.confirmLogout(); - } - }; + <RedeemVoucherButton /> - private confirmLogout = () => { - this.onHideLogoutConfirmationDialog(); - this.props.onLogout(); - }; + <AppButton.RedButton onClick={onTryLogout}> + {messages.pgettext('account-view', 'Log out')} + </AppButton.RedButton> + </AppButton.ButtonGroup> + </Footer> + </AccountContainer> + </SettingsContainer> - private cancelLogout = () => { - this.props.cancelLogout(); - this.onHideLogoutConfirmationDialog(); - }; + <LogoutDialog + logoutDialogStage={logoutDialogStage} + logoutDialogVisible={logoutDialogVisible} + confirmLogout={confirmLogout} + cancelLogout={onCancelLogout} + /> + </Layout> + </BackAction> + ); +} + +type LogoutDialogProps = { + logoutDialogStage: LogoutDialogStage; + logoutDialogVisible: boolean; + confirmLogout: () => void; + cancelLogout: () => void; +}; + +function LogoutDialog({ + logoutDialogStage, + logoutDialogVisible, + confirmLogout, + cancelLogout, +}: LogoutDialogProps) { + const modalType = logoutDialogStage === 'checking-ports' ? undefined : ModalAlertType.warning; + const message = + logoutDialogStage === 'checking-ports' ? ( + <StyledSpinnerContainer> + <ImageView source="icon-spinner" width={60} height={60} /> + </StyledSpinnerContainer> + ) : ( + <ModalMessage> + { + // TRANSLATORS: This is a further explanation of what happens when logging out. + messages.pgettext( + 'device-management', + 'The ports forwarded to this device will be deleted if you log out.', + ) + } + </ModalMessage> + ); - private onHideLogoutConfirmationDialog = () => { - this.setState({ logoutDialogVisible: false }); - }; + const buttons = + logoutDialogStage === 'checking-ports' + ? [] + : [ + <AppButton.RedButton key="logout" onClick={confirmLogout}> + { + // TRANSLATORS: Confirmation button when logging out + messages.pgettext('device-management', 'Log out anyway') + } + </AppButton.RedButton>, + <AppButton.BlueButton key="back" onClick={cancelLogout}> + {messages.gettext('Back')} + </AppButton.BlueButton>, + ]; + return ( + <ModalAlert isOpen={logoutDialogVisible} type={modalType} buttons={buttons}> + {message} + </ModalAlert> + ); +} + +function DeviceNameRow() { + const deviceName = useSelector((state) => state.account.deviceName); + return <DeviceRowValue>{deviceName}</DeviceRowValue>; } function AccountNumberRow() { @@ -217,11 +229,6 @@ function AccountExpiryRow() { return <FormattedAccountExpiry expiry={accountExpiry} locale={expiryLocale} />; } -function DeviceNameRow() { - const deviceName = useSelector((state) => state.account.deviceName); - return <DeviceRowValue>{deviceName}</DeviceRowValue>; -} - function FormattedAccountExpiry(props: { expiry?: string; locale: string }) { if (props.expiry) { if (hasExpired(props.expiry)) { diff --git a/gui/src/renderer/components/AppRouter.tsx b/gui/src/renderer/components/AppRouter.tsx index 36e7ea2239..1f0d36fc24 100644 --- a/gui/src/renderer/components/AppRouter.tsx +++ b/gui/src/renderer/components/AppRouter.tsx @@ -1,13 +1,13 @@ import { createRef, useCallback, useEffect, useState } from 'react'; import { Route, Switch } from 'react-router'; -import AccountPage from '../containers/AccountPage'; import LoginPage from '../containers/LoginPage'; import ProblemReportPage from '../containers/ProblemReportPage'; import SelectLocationPage from '../containers/SelectLocationPage'; import { useAppContext } from '../context'; import { ITransitionSpecification, transitions, useHistory } from '../lib/history'; import { RoutePath } from '../lib/routes'; +import Account from './Account'; import { DeviceRevokedView } from './DeviceRevokedView'; import { SetupFinished, @@ -71,7 +71,7 @@ export default function AppRouter() { <Route exact path={RoutePath.setupFinished} component={SetupFinished} /> <Route exact path={RoutePath.settings} component={Settings} /> <Route exact path={RoutePath.selectLanguage} component={SelectLanguage} /> - <Route exact path={RoutePath.accountSettings} component={AccountPage} /> + <Route exact path={RoutePath.accountSettings} component={Account} /> <Route exact path={RoutePath.userInterfaceSettings} component={UserInterfaceSettings} /> <Route exact path={RoutePath.vpnSettings} component={VpnSettings} /> <Route exact path={RoutePath.wireguardSettings} component={WireguardSettings} /> diff --git a/gui/src/renderer/containers/AccountPage.tsx b/gui/src/renderer/containers/AccountPage.tsx deleted file mode 100644 index 359d33b5c0..0000000000 --- a/gui/src/renderer/containers/AccountPage.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; - -import { links } from '../../config.json'; -import Account from '../components/Account'; -import withAppContext, { IAppContext } from '../context'; -import { IHistoryProps, withHistory } from '../lib/history'; -import accountActions from '../redux/account/actions'; -import { IReduxState, ReduxDispatch } from '../redux/store'; - -const mapStateToProps = (state: IReduxState) => ({ - isOffline: state.connection.isBlocked, -}); -const mapDispatchToProps = (dispatch: ReduxDispatch, props: IHistoryProps & IAppContext) => { - const account = bindActionCreators(accountActions, dispatch); - - return { - prepareLogout: () => account.prepareLogout(), - cancelLogout: () => account.cancelLogout(), - onLogout: () => { - void props.app.logout(); - }, - onClose: () => { - props.history.pop(); - }, - onBuyMore: () => props.app.openLinkWithAuth(links.purchase), - updateAccountData: () => props.app.updateAccountData(), - getDeviceState: () => props.app.getDeviceState(), - }; -}; - -export default withAppContext(withHistory(connect(mapStateToProps, mapDispatchToProps)(Account))); |
