diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2020-02-12 21:12:51 +0100 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2020-02-12 21:12:51 +0100 |
| commit | 2dfd3c06693dbf1ff85b4ea05e5557dac98f046b (patch) | |
| tree | 9917fe7caa30a7a85dd902edfbffcd85d1a57832 | |
| parent | 8300c27a0895da2f4aec6d7767dfd178a7ae2c08 (diff) | |
| parent | 970635630b1318c7c469a9e52405148dc845dd94 (diff) | |
| download | mullvadvpn-2dfd3c06693dbf1ff85b4ea05e5557dac98f046b.tar.xz mullvadvpn-2dfd3c06693dbf1ff85b4ea05e5557dac98f046b.zip | |
Merge branch 'problem-report-confirm-old-version'
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | gui/assets/images/icon-reload.svg | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/AppButton.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/Modal.tsx | 153 | ||||
| -rw-r--r-- | gui/src/renderer/components/Support.tsx | 110 | ||||
| -rw-r--r-- | gui/src/renderer/components/SupportStyles.tsx | 22 | ||||
| -rw-r--r-- | gui/src/renderer/components/WireguardKeys.tsx | 12 | ||||
| -rw-r--r-- | gui/src/renderer/containers/SupportPage.tsx | 2 |
8 files changed, 187 insertions, 117 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index cff66c23d4..f6f429bf02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Line wrap the file at 100 chars. Th - Add reconnect button to the desktop app. - Add monochrome option for the tray icon on Windows and Linux. - Show OS notification when account is close to expiry on desktop platforms. +- Warn users running old app versions when creating problem report. #### Android - Add option to enable or disable local network sharing. diff --git a/gui/assets/images/icon-reload.svg b/gui/assets/images/icon-reload.svg index 33c7e5ec2c..873727a0a5 100644 --- a/gui/assets/images/icon-reload.svg +++ b/gui/assets/images/icon-reload.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" class="feather feather-rotate-cw"><polyline points="23 4 23 10 17 10"></polyline><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path></svg>
\ No newline at end of file +<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="redo-alt" class="svg-inline--fa fa-redo-alt fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#000000" d="M256.455 8c66.269.119 126.437 26.233 170.859 68.685l35.715-35.715C478.149 25.851 504 36.559 504 57.941V192c0 13.255-10.745 24-24 24H345.941c-21.382 0-32.09-25.851-16.971-40.971l41.75-41.75c-30.864-28.899-70.801-44.907-113.23-45.273-92.398-.798-170.283 73.977-169.484 169.442C88.764 348.009 162.184 424 256 424c41.127 0 79.997-14.678 110.629-41.556 4.743-4.161 11.906-3.908 16.368.553l39.662 39.662c4.872 4.872 4.631 12.815-.482 17.433C378.202 479.813 319.926 504 256 504 119.034 504 8.001 392.967 8 256.002 7.999 119.193 119.646 7.755 256.455 8z"></path></svg> diff --git a/gui/src/renderer/components/AppButton.tsx b/gui/src/renderer/components/AppButton.tsx index 212e5cb1a3..9f6fe860b3 100644 --- a/gui/src/renderer/components/AppButton.tsx +++ b/gui/src/renderer/components/AppButton.tsx @@ -84,7 +84,7 @@ interface IState { textAdjustment: number; } -class BaseButton extends Component<IProps, IState> { +export class BaseButton extends Component<IProps, IState> { public state: IState = { hovered: false, textAdjustment: 0, diff --git a/gui/src/renderer/components/Modal.tsx b/gui/src/renderer/components/Modal.tsx index 5742e68f2b..b0a972955d 100644 --- a/gui/src/renderer/components/Modal.tsx +++ b/gui/src/renderer/components/Modal.tsx @@ -1,52 +1,123 @@ import * as React from 'react'; +import { Component, Styles, Text, View } from 'reactxp'; +import { colors } from '../../config.json'; +import ImageView from './ImageView'; -export class ModalContent extends React.Component { - public render() { - return ( - <div - style={{ - position: 'absolute', - display: 'flex', - flexDirection: 'column', - flex: 1, - top: 0, - left: 0, - right: 0, - bottom: 0, - }}> - {this.props.children} - </div> - ); - } +const styles = { + modalAlertBackground: Styles.createViewStyle({ + flex: 1, + justifyContent: 'center', + paddingLeft: 14, + paddingRight: 14, + }), + modalAlert: Styles.createViewStyle({ + backgroundColor: colors.darkBlue, + borderRadius: 11, + padding: 16, + }), + modalAlertIcon: Styles.createViewStyle({ + alignItems: 'center', + marginBottom: 12, + marginTop: 4, + }), + modalAlertMessage: Styles.createTextStyle({ + fontFamily: 'Open Sans', + fontSize: 16, + fontWeight: '500', + lineHeight: 20, + color: colors.white80, + }), + modalAlertButtonContainer: Styles.createViewStyle({ + marginTop: 16, + }), +}; + +export const ModalContent: React.FC = ({ children }) => { + return ( + <div + style={{ + position: 'absolute', + display: 'flex', + flexDirection: 'column', + flex: 1, + top: 0, + left: 0, + right: 0, + bottom: 0, + }}> + {children} + </div> + ); +}; + +const ModalBackground: React.FC = ({ children }) => { + return ( + <div + style={{ + backgroundColor: 'rgba(0,0,0,0.5)', + position: 'absolute', + display: 'flex', + flexDirection: 'column', + flex: 1, + top: 0, + left: 0, + right: 0, + bottom: 0, + }}> + {children} + </div> + ); +}; + +export const ModalContainer: React.FC = ({ children }) => { + return <div style={{ position: 'relative', flex: 1 }}>{children}</div>; +}; + +export enum ModalAlertType { + Info = 1, + Warning, +} + +interface IModalAlertProps { + type?: ModalAlertType; + message: string; + buttons: React.ReactNode[]; } -export class ModalAlert extends React.Component { +export class ModalAlert extends Component<IModalAlertProps> { public render() { return ( - <div - style={{ - backgroundColor: 'rgba(0,0,0,0.5)', - position: 'absolute', - display: 'flex', - flexDirection: 'column', - flex: 1, - top: 0, - left: 0, - right: 0, - bottom: 0, - }}> - {this.props.children} - </div> + <ModalBackground> + <View style={styles.modalAlertBackground}> + <View style={styles.modalAlert}> + {this.props.type && ( + <View style={styles.modalAlertIcon}>{this.renderTypeIcon(this.props.type)}</View> + )} + <Text style={styles.modalAlertMessage}>{this.props.message}</Text> + {this.props.buttons.map((button, index) => ( + <View key={index} style={styles.modalAlertButtonContainer}> + {button} + </View> + ))} + </View> + </View> + </ModalBackground> ); } -} -interface IModalContainerProps { - children?: React.ReactNode; -} - -export class ModalContainer extends React.Component<IModalContainerProps> { - public render() { - return <div style={{ position: 'relative', flex: 1 }}>{this.props.children}</div>; + private renderTypeIcon(type: ModalAlertType) { + let source = ''; + let color = ''; + switch (type) { + case ModalAlertType.Info: + source = 'icon-alert'; + color = colors.white; + break; + case ModalAlertType.Warning: + source = 'icon-alert'; + color = colors.red; + break; + } + return <ImageView height={44} width={44} source={source} tintColor={color} />; } } diff --git a/gui/src/renderer/components/Support.tsx b/gui/src/renderer/components/Support.tsx index 5313e7449c..c7cae9889d 100644 --- a/gui/src/renderer/components/Support.tsx +++ b/gui/src/renderer/components/Support.tsx @@ -1,10 +1,11 @@ import * as React from 'react'; import { Component, Text, TextInput, View } from 'reactxp'; +import { links } from '../../config.json'; import { messages } from '../../shared/gettext'; import * as AppButton from './AppButton'; import ImageView from './ImageView'; import { Container, Layout } from './Layout'; -import { ModalAlert, ModalContainer, ModalContent } from './Modal'; +import { ModalAlert, ModalAlertType, ModalContainer, ModalContent } from './Modal'; import { BackBarItem, NavigationBar, NavigationItems } from './NavigationBar'; import SettingsHeader, { HeaderSubTitle, HeaderTitle } from './SettingsHeader'; import styles from './SupportStyles'; @@ -26,6 +27,7 @@ interface ISupportState { savedReport?: string; sendState: SendState; disableActions: boolean; + showOutdatedVersionWarning: boolean; } interface ISupportProps { @@ -39,6 +41,8 @@ interface ISupportProps { clearReportForm: () => void; collectProblemReport: (accountsToRedact: string[]) => Promise<string>; sendProblemReport: (email: string, message: string, savedReport: string) => Promise<void>; + outdatedVersion: boolean; + onExternalLink: (url: string) => void; } export default class Support extends Component<ISupportProps, ISupportState> { @@ -48,6 +52,7 @@ export default class Support extends Component<ISupportProps, ISupportState> { savedReport: undefined, sendState: SendState.Initial, disableActions: false, + showOutdatedVersionWarning: false, }; private collectLogPromise?: Promise<string>; @@ -58,6 +63,7 @@ export default class Support extends Component<ISupportProps, ISupportState> { // seed initial data from props this.state.email = props.defaultEmail; this.state.message = props.defaultMessage; + this.state.showOutdatedVersionWarning = props.outdatedVersion; } public validate() { @@ -110,12 +116,12 @@ export default class Support extends Component<ISupportProps, ISupportState> { } }; - public onCancelConfirmation = () => { + public onCancelNoEmailDialog = () => { this.setState({ sendState: SendState.Initial }); }; public render() { - const { sendState } = this.state; + const { sendState, showOutdatedVersionWarning } = this.state; const header = ( <SettingsHeader> <HeaderTitle>{messages.pgettext('support-view', 'Report a problem')}</HeaderTitle> @@ -152,11 +158,8 @@ export default class Support extends Component<ISupportProps, ISupportState> { </View> </View> </ModalContent> - {sendState === SendState.Confirm ? ( - <ModalAlert>{this.renderConfirm()}</ModalAlert> - ) : ( - undefined - )} + {sendState === SendState.Confirm && this.renderNoEmailDialog()} + {showOutdatedVersionWarning && this.renderOutdateVersionWarningDialog()} </ModalContainer> </Container> </Layout> @@ -228,8 +231,59 @@ export default class Support extends Component<ISupportProps, ISupportState> { } } - private renderConfirm() { - return <ConfirmNoEmailDialog onConfirm={this.onSend} onDismiss={this.onCancelConfirmation} />; + private renderNoEmailDialog() { + const message = messages.pgettext( + 'support-view', + 'You are about to send the problem report without a way for us to get back to you. If you want an answer to your report you will have to enter an email address.', + ); + return ( + <ModalAlert + type={ModalAlertType.Warning} + message={message} + buttons={[ + <AppButton.RedButton key="proceed" onPress={this.onSend}> + {messages.pgettext('support-view', 'Send anyway')} + </AppButton.RedButton>, + <AppButton.BlueButton key="cancel" onPress={this.onCancelNoEmailDialog}> + {messages.pgettext('support-view', 'Back')} + </AppButton.BlueButton>, + ]} + /> + ); + } + + private acknowledgeOutdateVersion = () => { + this.setState({ showOutdatedVersionWarning: false }); + }; + + private openDownloadLink = () => this.props.onExternalLink(links.download); + + private renderOutdateVersionWarningDialog() { + const message = messages.pgettext( + 'support-view', + 'You are using an old version of the app. Please upgrade and see if the problem still exists before sending a report.', + ); + return ( + <ModalAlert + type={ModalAlertType.Warning} + message={message} + buttons={[ + <AppButton.GreenButton + key="upgrade" + disabled={this.props.isOffline} + onPress={this.openDownloadLink}> + <AppButton.Label>{messages.pgettext('support-view', 'Upgrade app')}</AppButton.Label> + <AppButton.Icon height={16} width={16} source="icon-extLink" /> + </AppButton.GreenButton>, + <AppButton.RedButton key="proceed" onPress={this.acknowledgeOutdateVersion}> + {messages.pgettext('support-view', 'Continue anyway')} + </AppButton.RedButton>, + <AppButton.BlueButton key="cancel" onPress={this.props.onClose}> + {messages.pgettext('support-view', 'Cancel')} + </AppButton.BlueButton>, + ]} + /> + ); } private renderForm() { @@ -388,39 +442,3 @@ export default class Support extends Component<ISupportProps, ISupportState> { }); } } - -interface IConfirmNoEmailDialogProps { - onConfirm: () => void; - onDismiss: () => void; -} - -class ConfirmNoEmailDialog extends Component<IConfirmNoEmailDialogProps> { - public render() { - return ( - <View style={styles.confirm_no_email_background}> - <View style={styles.confirm_no_email_dialog}> - <Text style={styles.confirm_no_email_warning}> - {messages.pgettext( - 'support-view', - 'You are about to send the problem report without a way for us to get back to you. If you want an answer to your report you will have to enter an email address.', - )} - </Text> - <AppButton.GreenButton onPress={this.confirm}> - {messages.pgettext('support-view', 'Send anyway')} - </AppButton.GreenButton> - <AppButton.RedButton onPress={this.dismiss} style={styles.confirm_no_email_back_button}> - {messages.pgettext('support-view', 'Back')} - </AppButton.RedButton> - </View> - </View> - ); - } - - private confirm = () => { - this.props.onConfirm(); - }; - - private dismiss = () => { - this.props.onDismiss(); - }; -} diff --git a/gui/src/renderer/components/SupportStyles.tsx b/gui/src/renderer/components/SupportStyles.tsx index 05fd89bfd0..d883c806ba 100644 --- a/gui/src/renderer/components/SupportStyles.tsx +++ b/gui/src/renderer/components/SupportStyles.tsx @@ -114,26 +114,4 @@ export default { color: colors.white, marginBottom: 4, }), - confirm_no_email_background: Styles.createViewStyle({ - flex: 1, - justifyContent: 'center', - paddingLeft: 14, - paddingRight: 14, - }), - confirm_no_email_dialog: Styles.createViewStyle({ - backgroundColor: colors.darkBlue, - borderRadius: 11, - padding: 16, - }), - confirm_no_email_warning: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 16, - fontWeight: '500', - lineHeight: 20, - color: colors.white80, - marginBottom: 12, - }), - confirm_no_email_back_button: Styles.createViewStyle({ - marginTop: 16, - }), }; diff --git a/gui/src/renderer/components/WireguardKeys.tsx b/gui/src/renderer/components/WireguardKeys.tsx index 4451b8e341..dacdfcdc72 100644 --- a/gui/src/renderer/components/WireguardKeys.tsx +++ b/gui/src/renderer/components/WireguardKeys.tsx @@ -78,24 +78,24 @@ export default class WireguardKeys extends Component<IProps> { <View style={styles.wgkeys__row}>{this.getGenerateButton()}</View> <View style={styles.wgkeys__row}> - <AppButton.GreenButton + <AppButton.BlueButton disabled={this.isVerifyButtonDisabled()} onPress={this.getOnVerifyKeyCb()}> <AppButton.Label> {messages.pgettext('wireguard-key-view', 'Verify key')} </AppButton.Label> - </AppButton.GreenButton> + </AppButton.BlueButton> </View> <View style={styles.wgkeys__row}> <AppButton.BlockingButton disabled={this.props.isOffline} onPress={this.props.onVisitWebsiteKey}> - <AppButton.GreenButton> + <AppButton.BlueButton> <AppButton.Label> {messages.pgettext('wireguard-key-view', 'Manage keys')} </AppButton.Label> <AppButton.Icon source="icon-extLink" height={16} width={16} /> - </AppButton.GreenButton> + </AppButton.BlueButton> </AppButton.BlockingButton> </View> </View> @@ -155,10 +155,10 @@ export default class WireguardKeys extends Component<IProps> { private busyButton(message: string) { return ( - <AppButton.GreenButton disabled={true}> + <AppButton.BlueButton disabled={true}> <AppButton.Label>{message}</AppButton.Label> <AppButton.Icon source="icon-spinner" height={16} width={16} /> - </AppButton.GreenButton> + </AppButton.BlueButton> ); } diff --git a/gui/src/renderer/containers/SupportPage.tsx b/gui/src/renderer/containers/SupportPage.tsx index 755e67f4de..d666afbd55 100644 --- a/gui/src/renderer/containers/SupportPage.tsx +++ b/gui/src/renderer/containers/SupportPage.tsx @@ -12,6 +12,7 @@ const mapStateToProps = (state: IReduxState) => ({ defaultMessage: state.support.message, accountHistory: state.account.accountHistory, isOffline: state.connection.isBlocked, + outdatedVersion: state.version.currentIsOutdated, }); const mapDispatchToProps = (dispatch: ReduxDispatch) => { @@ -29,6 +30,7 @@ const mapDispatchToProps = (dispatch: ReduxDispatch) => { clearReportForm, collectProblemReport, sendProblemReport, + onExternalLink: (url: string) => shell.openExternal(url), }; }; |
