Improve design of SubscriptionDialog and SubscriptionCheckDialog

This commit is contained in:
Florian Rival
2018-02-25 16:40:48 +01:00
parent d79bdd9554
commit 7cbe34436c
8 changed files with 271 additions and 99 deletions

View File

@@ -208,6 +208,7 @@ export default class LocalPreviewLauncher extends React.Component<
if (this.props.onChangeSubscription)
this.props.onChangeSubscription();
}}
title="Preview over wifi"
/>
<LocalNetworkPreviewDialog
open={networkPreviewDialogOpen}

View File

@@ -1,12 +1,16 @@
// @flow
import React, { Component } from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import FlatButton from 'material-ui/FlatButton';
import Dialog from '../UI/Dialog';
import Star from 'material-ui/svg-icons/toggle/star';
import Favorite from 'material-ui/svg-icons/action/favorite';
import UserProfileContext, { type UserProfile } from './UserProfileContext';
import { Column, Line } from '../UI/Grid';
type DialogProps = {|
title: string,
userProfile: UserProfile,
onChangeSubscription?: () => void,
|};
@@ -15,6 +19,12 @@ type DialogState = {|
open: boolean,
|};
const styles = {
icon: { width: 40, height: 40, marginRight: 20 },
iconText: { flex: 1 },
thanksText: { textAlign: 'right', marginRight: 20, marginBottom: 0 },
};
export class SubscriptionCheckDialog extends Component<
DialogProps,
DialogState
@@ -29,9 +39,8 @@ export class SubscriptionCheckDialog extends Component<
this.setState({
open: false,
});
return true;
}
return true;
}
this.setState({
@@ -48,58 +57,67 @@ export class SubscriptionCheckDialog extends Component<
if (!open) return null;
return (
<UserProfileContext.Consumer>
{(userProfile: UserProfile) => (
<Dialog
actions={[
onChangeSubscription && (
<FlatButton
label="Get a subscription or login"
key="subscribe"
primary
onClick={() => {
this._closeDialog();
onChangeSubscription();
}}
/>
),
]}
secondaryActions={[
<FlatButton
label="Continue anyway"
key="close"
primary={false}
onClick={this._closeDialog}
/>,
]}
onRequestClose={this._closeDialog}
open={open}
autoScrollBodyContent
>
<Column>
<p>
You can try this feature, but if you're using it regularly, we
ask you to get a subscription to GDevelop. Having a
subscription allows you to use the one-click export for
Android, launch live previews over wifi, disable the GDevelop
splashscreen during loading and more!
</p>
<p>
You're also supporting the development of GDevelop, an
open-source software! In the future, more exports (iOS,
Windows/Mac/Linux) and online services will be available for
users with a subscription.
</p>
<p><b>Thanks!</b></p>
</Column>
</Dialog>
)}
</UserProfileContext.Consumer>
<Dialog
actions={[
onChangeSubscription && (
<RaisedButton
label="Get a subscription or login"
key="subscribe"
primary
onClick={() => {
this._closeDialog();
onChangeSubscription();
}}
/>
),
]}
secondaryActions={[
<FlatButton
label="Continue anyway"
key="close"
primary={false}
onClick={this._closeDialog}
/>,
]}
onRequestClose={this._closeDialog}
open={open}
autoScrollBodyContent
title={this.props.title}
>
<Column noMargin>
<Line noMargin alignItems="center">
<p>
You can try this feature, but if you're using it regularly, we ask
you to get a subscription to GDevelop.
</p>
</Line>
<Line noMargin alignItems="center">
<Star style={styles.icon} />
<p style={styles.iconText}>
Having a subscription allows you to use the one-click export for
Android, launch live previews over wifi, disable the GDevelop
splashscreen during loading and more!
</p>
</Line>
<Line noMargin alignItems="center">
<Favorite style={styles.icon} />
<p style={styles.iconText}>
You're also supporting the development of GDevelop, an open-source
software! In the future, more exports (iOS, Windows/Mac/Linux) and
online services will be available for users with a subscription.
</p>
</Line>
<p style={styles.thanksText}>
<b>Thanks!</b>
</p>
</Column>
</Dialog>
);
}
}
type Props = {|
title: string,
onChangeSubscription?: () => void,
|};
@@ -122,6 +140,7 @@ class SubscriptionChecker extends Component<Props, {}> {
userProfile={userProfile}
ref={dialog => (this._dialog = dialog)}
onChangeSubscription={this.props.onChangeSubscription}
title={this.props.title}
/>
)}
</UserProfileContext.Consumer>

View File

@@ -13,6 +13,7 @@ import {
} from '../Utils/GDevelopServices/Usage';
import { StripeCheckoutConfig } from '../Utils/GDevelopServices/ApiConfigs';
import RaisedButton from 'material-ui/RaisedButton';
import CheckCircle from 'material-ui/svg-icons/action/check-circle';
import EmptyMessage from '../UI/EmptyMessage';
import { showMessageBox, showErrorBox } from '../UI/Messages/MessageBox';
import LeftLoader from '../UI/LeftLoader';
@@ -33,6 +34,8 @@ const styles = {
display: 'flex',
justifyContent: 'flex-end',
},
bulletIcon: { width: 20, height: 20, marginLeft: 15, marginRight: 10 },
bulletText: { flex: 1, marginTop: 5, marginBottom: 5 },
};
type Props = {|
@@ -157,9 +160,10 @@ export default class SubscriptionDialog extends Component<Props, State> {
<Column>
<Line>
<p>
Get a subscription to package your games for Android. With a
subscription, you're also supporting the development of
GDevelop, an open-source software!
Get a subscription to package your games for Android, use live
preview over wifi and more. With a subscription, you're also
supporting the development of GDevelop, an open-source
software!
</p>
</Line>
</Column>
@@ -173,9 +177,14 @@ export default class SubscriptionDialog extends Component<Props, State> {
}
subtitle={plan.smallDescription}
/>
<p style={styles.descriptionText}>{plan.description || ''}</p>
{plan.descriptionBullets.map(bulletText => (
<Line noMargin alignItems="center">
<CheckCircle style={styles.bulletIcon} />
<p style={styles.bulletText}>{bulletText}</p>
</Line>
))}
<p style={styles.descriptionText}>
{plan.moreDescription1 || ''}
{plan.extraDescription || ''}
</p>
<CardActions style={styles.actions}>
{userProfile.subscription &&

View File

@@ -34,8 +34,8 @@ export type PlanDetails = {
name: string,
monthlyPriceInEuros: number,
smallDescription: string,
description: string,
moreDescription1?: string,
descriptionBullets: Array<string>,
extraDescription?: string,
};
export const getSubscriptionPlans = (): Array<PlanDetails> => [
@@ -44,8 +44,11 @@ export const getSubscriptionPlans = (): Array<PlanDetails> => [
name: 'GDevelop Pro',
monthlyPriceInEuros: 7,
smallDescription: 'Ideal for advanced game makers',
description: 'Allow to package your game for Android up to 70 times a day.',
moreDescription1:
descriptionBullets: [
'Allow to package your game for Android up to 70 times a day.',
'Use Live Preview over Wifi to quickly test your game on mobiles and tablets',
],
extraDescription:
"You'll also have access to online packaging for Windows, macOS and Linux when it's ready.",
},
{
@@ -53,8 +56,11 @@ export const getSubscriptionPlans = (): Array<PlanDetails> => [
name: 'GDevelop Indie',
monthlyPriceInEuros: 2,
smallDescription: 'Ideal for beginners',
description: 'Allow to package your game for Android up to 10 times a day.',
moreDescription1:
descriptionBullets: [
'Allow to package your game for Android up to 10 times a day.',
'Use Live Preview over Wifi to quickly test your game on mobiles and tablets',
],
extraDescription:
"You'll also have access to online packaging for Windows, macOS and Linux when it's ready.",
},
{
@@ -62,8 +68,9 @@ export const getSubscriptionPlans = (): Array<PlanDetails> => [
name: 'No subscription',
monthlyPriceInEuros: 0,
smallDescription: '',
description:
descriptionBullets: [
'You can use GDevelop for free, but online packaging for Android is limited to twice a day.',
],
},
];

View File

@@ -5,6 +5,7 @@ import {
type Limits,
} from '../Utils/GDevelopServices/Usage';
import { type Profile } from '../Utils/GDevelopServices/Authentification';
import { type UserProfile } from '../Profile/UserProfileContext';
export const profileForIndieUser: Profile = {
uid: 'indie-user',
@@ -35,7 +36,7 @@ export const usagesForIndieUser: Usages = [
];
export const subscriptionForIndieUser: Subscription = {
planId: 'gdevelop-indie',
planId: 'gdevelop_indie',
createdAt: 1515084011000,
updatedAt: 1515084011000,
userId: 'indie-user',
@@ -63,3 +64,47 @@ export const limitsReached: Limits = {
limitReached: true,
},
};
export const fakeIndieUserProfile: UserProfile = {
authenticated: true,
profile: profileForIndieUser,
subscription: subscriptionForIndieUser,
usages: usagesForIndieUser,
limits: limitsForIndieUser,
onLogout: () => {},
onLogin: () => {},
onRefreshUserProfile: () => {},
};
export const fakeNoSubscriptionUserProfile: UserProfile = {
authenticated: true,
profile: profileForIndieUser,
subscription: noSubscription,
usages: usagesForIndieUser,
limits: limitsForIndieUser,
onLogout: () => {},
onLogin: () => {},
onRefreshUserProfile: () => {},
};
export const fakeAuthenticatedButLoadingUserProfile: UserProfile = {
authenticated: true,
profile: null,
subscription: null,
usages: null,
limits: null,
onLogout: () => {},
onLogin: () => {},
onRefreshUserProfile: () => {},
};
export const fakeNotAuthenticatedUserProfile: UserProfile = {
authenticated: false,
profile: null,
subscription: null,
usages: null,
limits: null,
onLogout: () => {},
onLogin: () => {},
onRefreshUserProfile: () => {},
};

View File

@@ -69,7 +69,7 @@ if (electron) {
app = (
<ElectronEventsBridge>
<MainFrame
previewLauncher={<LocalPreviewLauncher authentification={authentification} />}
previewLauncher={<LocalPreviewLauncher />}
exportDialog={<ExportDialog exporters={getLocalExporters()} />}
createDialog={
<CreateProjectDialog examplesComponent={LocalExamples} />

View File

@@ -0,0 +1,15 @@
import React, { Component } from 'react';
export default class RefGetter extends Component {
componentDidMount() {
if (this._ref) {
this.props.onRef(this._ref);
}
}
render() {
return React.cloneElement(this.props.children, {
ref: ref => this._ref = ref,
});
}
}

View File

@@ -1,3 +1,4 @@
// @flow
import React from 'react';
import { storiesOf } from '@storybook/react';
@@ -43,6 +44,7 @@ import ObjectsGroupsList from '../ObjectsGroupsList';
import muiDecorator from './MuiDecorator';
import paperDecorator from './PaperDecorator';
import ValueStateHolder from './ValueStateHolder';
import RefGetter from './RefGetter';
import DragDropContextProvider from '../Utils/DragDropHelpers/DragDropContextProvider';
import ResourcesLoader from '../ResourcesLoader';
import VariablesList from '../VariablesList';
@@ -61,11 +63,17 @@ import {
noSubscription,
usagesForIndieUser,
profileForIndieUser,
fakeNoSubscriptionUserProfile,
fakeIndieUserProfile,
fakeNotAuthenticatedUserProfile,
fakeAuthenticatedButLoadingUserProfile,
} from '../fixtures/GDevelopServicesTestData';
import SubscriptionDetails from '../Profile/SubscriptionDetails';
import UsagesDetails from '../Profile/UsagesDetails';
import SubscriptionDialog from '../Profile/SubscriptionDialog';
import LoginDialog from '../Profile/LoginDialog';
import UserProfileContext from '../Profile/UserProfileContext';
import { SubscriptionCheckDialog } from '../Profile/SubscriptionChecker';
const gd = global.gd;
const {
@@ -300,6 +308,7 @@ storiesOf('InstructionSelector', module)
<InstructionSelector
selectedType=""
onChoose={action('Instruction chosen')}
isCondition
/>
));
@@ -498,10 +507,13 @@ storiesOf('VariablesList', module)
</SerializedObjectDisplay>
));
const fakeError = new Error('Fake error for storybook');
storiesOf('ErrorBoundary', module)
.addDecorator(paperDecorator)
.addDecorator(muiDecorator)
.add('default', () => <ErrorFallbackComponent />);
.add('default', () => (
<ErrorFallbackComponent componentStack="Fake stack" error={fakeError} />
));
storiesOf('CreateProfile', module)
.addDecorator(paperDecorator)
@@ -536,23 +548,23 @@ storiesOf('LimitDisplayer', module)
storiesOf('ProfileDetails', module)
.addDecorator(paperDecorator)
.addDecorator(muiDecorator)
.add('profile', () => (
<ProfileDetails
profile={{
email: 'test@example.com',
}}
/>
))
.add('profile', () => <ProfileDetails profile={profileForIndieUser} />)
.add('loading', () => <ProfileDetails profile={null} />);
storiesOf('SubscriptionDetails', module)
.addDecorator(paperDecorator)
.addDecorator(muiDecorator)
.add('default', () => (
<SubscriptionDetails subscription={subscriptionForIndieUser} />
<SubscriptionDetails
subscription={subscriptionForIndieUser}
onChangeSubscription={action('change subscription')}
/>
))
.add('limit reached', () => (
<SubscriptionDetails subscription={noSubscription} />
<SubscriptionDetails
subscription={noSubscription}
onChangeSubscription={action('change subscription')}
/>
));
storiesOf('UsagesDetails', module)
@@ -564,21 +576,25 @@ storiesOf('UsagesDetails', module)
storiesOf('SubscriptionDialog', module)
.addDecorator(paperDecorator)
.addDecorator(muiDecorator)
.add('default', () => (
<SubscriptionDialog
profile={profileForIndieUser}
subscription={subscriptionForIndieUser}
open
onClose={action('on close')}
/>
.add('not authenticated', () => (
<UserProfileContext.Provider value={fakeNotAuthenticatedUserProfile}>
<SubscriptionDialog open onClose={action('on close')} />
</UserProfileContext.Provider>
))
.add('loading (no profile/subscription)', () => (
<SubscriptionDialog
profile={null}
subscription={null}
open
onClose={action('on close')}
/>
.add('authenticated but loading', () => (
<UserProfileContext.Provider value={fakeAuthenticatedButLoadingUserProfile}>
<SubscriptionDialog open onClose={action('on close')} />
</UserProfileContext.Provider>
))
.add('authenticated user with subscription', () => (
<UserProfileContext.Provider value={fakeIndieUserProfile}>
<SubscriptionDialog open onClose={action('on close')} />
</UserProfileContext.Provider>
))
.add('authenticated user with no subscription', () => (
<UserProfileContext.Provider value={fakeNoSubscriptionUserProfile}>
<SubscriptionDialog open onClose={action('on close')} />
</UserProfileContext.Provider>
));
storiesOf('LoginDialog', module)
@@ -589,6 +605,13 @@ storiesOf('LoginDialog', module)
onClose={action('on close')}
loginInProgress={false}
createAccountInProgress={false}
onCreateAccount={action('on create account')}
onLogin={action('on login')}
onForgotPassword={action('on forgot password')}
onCloseResetPasswordDialog={action('on close reset password dialog')}
resetPasswordDialogOpen={false}
forgotPasswordInProgress={false}
error={null}
/>
))
.add('login in progress', () => (
@@ -597,6 +620,13 @@ storiesOf('LoginDialog', module)
onClose={action('on close')}
loginInProgress
createAccountInProgress={false}
onCreateAccount={action('on create account')}
onLogin={action('on login')}
onForgotPassword={action('on forgot password')}
onCloseResetPasswordDialog={action('on close reset password dialog')}
resetPasswordDialogOpen={false}
forgotPasswordInProgress={false}
error={null}
/>
))
.add('create account in progress', () => (
@@ -605,6 +635,13 @@ storiesOf('LoginDialog', module)
onClose={action('on close')}
loginInProgress={false}
createAccountInProgress
onCreateAccount={action('on create account')}
onLogin={action('on login')}
onForgotPassword={action('on forgot password')}
onCloseResetPasswordDialog={action('on close reset password dialog')}
resetPasswordDialogOpen={false}
forgotPasswordInProgress={false}
error={null}
/>
))
.add('weak-password error', () => (
@@ -613,6 +650,12 @@ storiesOf('LoginDialog', module)
onClose={action('on close')}
loginInProgress={false}
createAccountInProgress={false}
onCreateAccount={action('on create account')}
onLogin={action('on login')}
onForgotPassword={action('on forgot password')}
onCloseResetPasswordDialog={action('on close reset password dialog')}
resetPasswordDialogOpen={false}
forgotPasswordInProgress={false}
error={{
code: 'auth/weak-password',
}}
@@ -624,6 +667,12 @@ storiesOf('LoginDialog', module)
onClose={action('on close')}
loginInProgress={false}
createAccountInProgress={false}
onCreateAccount={action('on create account')}
onLogin={action('on login')}
onForgotPassword={action('on forgot password')}
onCloseResetPasswordDialog={action('on close reset password dialog')}
resetPasswordDialogOpen={false}
forgotPasswordInProgress={false}
error={{
code: 'auth/invalid-email',
}}
@@ -635,17 +684,13 @@ storiesOf('LoginDialog', module)
onClose={action('on close')}
loginInProgress={false}
createAccountInProgress={false}
onCreateAccount={action('on create account')}
onLogin={action('on login')}
onForgotPassword={action('on forgot password')}
onCloseResetPasswordDialog={action('on close reset password dialog')}
forgotPasswordInProgress={false}
resetPasswordDialogOpen
/>
))
.add('Reset password (invalid-action-code error)', () => (
<LoginDialog
open
onClose={action('on close')}
loginInProgress={false}
createAccountInProgress={false}
resetPasswordDialogOpen
resetError={{ code: 'auth/invalid-action-code' }}
error={null}
/>
));
@@ -653,13 +698,44 @@ storiesOf('LocalNetworkPreviewDialog', module)
.addDecorator(paperDecorator)
.addDecorator(muiDecorator)
.add('default', () => (
<LocalNetworkPreviewDialog open url="192.168.0.1:2929" />
<LocalNetworkPreviewDialog
open
url="192.168.0.1:2929"
error={null}
onRunPreviewLocally={action('on run preview locally')}
onExport={action('on export')}
onClose={action('on close')}
/>
))
.add('waiting for url', () => (
<LocalNetworkPreviewDialog
open
url=""
error={null}
onRunPreviewLocally={action('on run preview locally')}
onExport={action('on export')}
onClose={action('on close')}
/>
))
.add('waiting for url', () => <LocalNetworkPreviewDialog open />)
.add('error', () => (
<LocalNetworkPreviewDialog
open
url="192.168.0.1:2929"
error={{ message: 'Oops' }}
onRunPreviewLocally={action('on run preview locally')}
onExport={action('on export')}
onClose={action('on close')}
/>
));
storiesOf('SubscriptionCheckDialog', module)
.addDecorator(muiDecorator)
.add('default', () => (
<RefGetter onRef={ref => ref.checkHasSubscription()}>
<SubscriptionCheckDialog
title="Preview over wifi"
userProfile={fakeNoSubscriptionUserProfile}
onChangeSubscription={action('change subscription')}
/>
</RefGetter>
));