Skip to content

Commit

Permalink
Improve export copy & logic (#6023)
Browse files Browse the repository at this point in the history
Do not show in changelog
  • Loading branch information
ClementPasteau authored Dec 6, 2023
1 parent 4f04190 commit 1b4c5c1
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 74 deletions.
9 changes: 8 additions & 1 deletion newIDE/app/src/ExportAndShare/ShareDialog/ExportLauncher.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ export default class ExportLauncher extends Component<Props, State> {
buildsWatcher = new BuildsWatcher();
launchWholeExport: (i18n: I18nType) => Promise<void>;

componentWillMount() {
// Fetch limits when the export launcher is opened, to ensure we display the
// latest limits.
this.props.authenticatedUser.onRefreshLimits();
}

componentWillUnmount() {
this.buildsWatcher.stop();
}
Expand Down Expand Up @@ -133,7 +139,8 @@ export default class ExportLauncher extends Component<Props, State> {
onBuildUpdated: (build: Build) => {
this.setState({ build });
if (build.status !== 'pending') {
authenticatedUser.onRefreshUserProfile();
// Fetch limits again, as they may have changed.
authenticatedUser.onRefreshLimits();
}
},
});
Expand Down
6 changes: 4 additions & 2 deletions newIDE/app/src/Profile/AuthenticatedUserContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ export type AuthenticatedUser = {|
onCloudProjectsChanged: () => Promise<void>,
onRefreshUserProfile: () => Promise<void>,
onRefreshFirebaseProfile: () => Promise<void>,
onSubscriptionUpdated: () => Promise<void>,
onRefreshSubscription: () => Promise<void>,
onRefreshLimits: () => Promise<void>,
onPurchaseSuccessful: () => Promise<void>,
onSendEmailVerification: () => Promise<void>,
onOpenEmailVerificationDialog: ({|
Expand Down Expand Up @@ -102,7 +103,8 @@ export const initialAuthenticatedUser = {
onCloudProjectsChanged: async () => {},
onRefreshUserProfile: async () => {},
onRefreshFirebaseProfile: async () => {},
onSubscriptionUpdated: async () => {},
onRefreshSubscription: async () => {},
onRefreshLimits: async () => {},
onPurchaseSuccessful: async () => {},
onSendEmailVerification: async () => {},
onOpenEmailVerificationDialog: () => {},
Expand Down
37 changes: 29 additions & 8 deletions newIDE/app/src/Profile/AuthenticatedUserProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,8 @@ export default class AuthenticatedUserProvider extends React.Component<
onRefreshFirebaseProfile: async () => {
await this._reloadFirebaseProfile();
},
onSubscriptionUpdated: this._fetchUserSubscriptionLimitsAndUsages,
onRefreshSubscription: this._fetchUserSubscriptionLimitsAndUsages,
onRefreshLimits: this._fetchUserLimits,
onPurchaseSuccessful: this._fetchUserPurchases,
onSendEmailVerification: this._doSendEmailVerification,
onOpenEmailVerificationDialog: ({
Expand Down Expand Up @@ -484,40 +485,52 @@ export default class AuthenticatedUserProvider extends React.Component<
);
};

_fetchUserSubscriptionLimitsAndUsages = async () => {
_fetchUserSubscription = async () => {
const { authentication } = this.props;
const firebaseUser = this.state.authenticatedUser.firebaseUser;
if (!firebaseUser) return;

try {
const usages = await getUserUsages(
const subscription = await getUserSubscription(
authentication.getAuthorizationHeader,
firebaseUser.uid
);
this.setState(({ authenticatedUser }) => ({
authenticatedUser: {
...authenticatedUser,
usages,
subscription,
},
}));
} catch (error) {
console.error('Error while loading user usages:', error);
console.error('Error while loading user subscriptions:', error);
}
};

_fetchUserUsages = async () => {
const { authentication } = this.props;
const firebaseUser = this.state.authenticatedUser.firebaseUser;
if (!firebaseUser) return;

try {
const subscription = await getUserSubscription(
const usages = await getUserUsages(
authentication.getAuthorizationHeader,
firebaseUser.uid
);
this.setState(({ authenticatedUser }) => ({
authenticatedUser: {
...authenticatedUser,
subscription,
usages,
},
}));
} catch (error) {
console.error('Error while loading user subscriptions:', error);
console.error('Error while loading user usages:', error);
}
};

_fetchUserLimits = async () => {
const { authentication } = this.props;
const firebaseUser = this.state.authenticatedUser.firebaseUser;
if (!firebaseUser) return;

try {
const limits = await getUserLimits(
Expand All @@ -535,6 +548,14 @@ export default class AuthenticatedUserProvider extends React.Component<
}
};

_fetchUserSubscriptionLimitsAndUsages = async () => {
await Promise.all([
this._fetchUserSubscription(),
this._fetchUserUsages(),
this._fetchUserLimits(),
]);
};

_fetchUserAssetPacks = async () => {
const { authentication } = this.props;
const firebaseUser = this.state.authenticatedUser.firebaseUser;
Expand Down
20 changes: 16 additions & 4 deletions newIDE/app/src/Profile/CurrentUsageDisplayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,27 @@ const CurrentUsageDisplayer = ({
const hasSubscription = hasValidSubscriptionPlan(subscription);
const loadedButHasNoSubscription =
subscription && !hasValidSubscriptionPlan(subscription);
const remainingBuilds = Math.max(currentUsage.max - currentUsage.current, 0);
const remainingMultipleMessage = (
<Trans>
You have {remainingBuilds} builds remaining (You have used{' '}
{currentUsage.current}/{currentUsage.max} in the last 24h).
</Trans>
);
const remainingSingleMessage = (
<Trans>
You have {remainingBuilds} build remaining (You have used{' '}
{currentUsage.current}/{currentUsage.max} in the last 24h).
</Trans>
);

return (
<ColumnStackLayout noMargin>
<AlertMessage kind="info">
<Text>
<Trans>
You have {Math.max(currentUsage.max - currentUsage.current, 0)}{' '}
remaining builds for today (out of {currentUsage.max}).
</Trans>
{remainingBuilds === 1
? remainingSingleMessage
: remainingMultipleMessage}
</Text>
</AlertMessage>
{hasSubscription && currentUsage.limitReached && (
Expand Down
12 changes: 6 additions & 6 deletions newIDE/app/src/Profile/Subscription/SubscriptionDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,13 @@ export default function SubscriptionDialog({
await changeUserSubscription(getAuthorizationHeader, profile.id, {
planId: null,
});
await authenticatedUser.onSubscriptionUpdated();
await authenticatedUser.onRefreshSubscription();
showAlert({
title: t`Subscription cancelled`,
message: t`Your subscription is now cancelled.`,
});
} catch (rawError) {
await authenticatedUser.onSubscriptionUpdated();
await authenticatedUser.onRefreshSubscription();
showErrorBox({
message: i18n._(
t`Your subscription could not be cancelled. Please try again later!`
Expand Down Expand Up @@ -224,7 +224,7 @@ export default function SubscriptionDialog({
await changeUserSubscription(getAuthorizationHeader, profile.id, {
planId: plan.planId,
});
await authenticatedUser.onSubscriptionUpdated();
await authenticatedUser.onRefreshSubscription();
showAlert({
title: t`Subscription updated`,
message: t`Congratulations, your new subscription is now active! You can now use the services unlocked with this plan.`,
Expand All @@ -247,7 +247,7 @@ export default function SubscriptionDialog({
await changeUserSubscription(getAuthorizationHeader, profile.id, {
planId: '',
});
await authenticatedUser.onSubscriptionUpdated();
await authenticatedUser.onRefreshSubscription();
} catch (rawError) {
showErrorBox({
message: i18n._(
Expand Down Expand Up @@ -536,7 +536,7 @@ export default function SubscriptionDialog({
authenticatedUser={authenticatedUser}
onClose={() => {
setSubscriptionPendingDialogOpen(false);
authenticatedUser.onSubscriptionUpdated();
authenticatedUser.onRefreshSubscription();
}}
/>
)}
Expand All @@ -549,7 +549,7 @@ export default function SubscriptionDialog({
if (hasJustRedeemedCode) {
try {
setIsChangingSubscription(true);
await authenticatedUser.onSubscriptionUpdated();
await authenticatedUser.onRefreshSubscription();
} finally {
setIsChangingSubscription(false);
setSubscriptionPendingDialogOpen(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default function SubscriptionPendingDialog({
authenticatedUser.subscription.planId === 'gdevelop_startup');
useInterval(
() => {
authenticatedUser.onRefreshUserProfile().catch(() => {
authenticatedUser.onRefreshSubscription().catch(() => {
// Ignore any error, will be retried anyway.
});
},
Expand Down
4 changes: 2 additions & 2 deletions newIDE/app/src/ProjectCreation/NewProjectSetupDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -283,8 +283,8 @@ const NewProjectSetupDialog = ({
error.response.data.code === 'project-generation/quota-exceeded'
) {
setGenerationPrompt('');
// Fetch the user again to update the limits and show the subscription info.
await authenticatedUser.onSubscriptionUpdated();
// Fetch the limits again to show the warning about quota.
await authenticatedUser.onRefreshLimits();
} else {
showAlert({
title: t`Unable to generate project`,
Expand Down
7 changes: 5 additions & 2 deletions newIDE/app/src/fixtures/GDevelopServicesTestData/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -574,8 +574,11 @@ const defaultAuthenticatedUserWithNoSubscription: AuthenticatedUser = {
onRefreshUserProfile: async () => {
console.info('This should refresh the user profile');
},
onSubscriptionUpdated: async () => {
console.info('This should refresh the subscriptions');
onRefreshSubscription: async () => {
console.info('This should refresh the subscription');
},
onRefreshLimits: async () => {
console.info('This should refresh the limits');
},
onPurchaseSuccessful: async () => {
console.info('This should refresh the assets');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// @flow
import * as React from 'react';
import { action } from '@storybook/addon-actions';
import muiDecorator from '../../ThemeDecorator';
import paperDecorator from '../../PaperDecorator';
import CurrentUsageDisplayer from '../../../Profile/CurrentUsageDisplayer';
import subscriptionSuggestionDecorator from '../../SubscriptionSuggestionDecorator';
import {
limitsReached,
noSubscription,
silverSubscriptionWithExpiredRedemptionCode,
silverSubscriptionWithRedemptionCode,
subscriptionForIndieUser,
} from '../../../fixtures/GDevelopServicesTestData';

export default {
title: 'Profile/CurrentUsageDisplayer',
component: CurrentUsageDisplayer,
decorators: [subscriptionSuggestionDecorator, paperDecorator, muiDecorator],
};

export const Default = () => (
<CurrentUsageDisplayer
subscription={subscriptionForIndieUser}
currentUsage={{
current: 2,
max: 10,
limitReached: false,
}}
onChangeSubscription={action('on change subscription callback')}
/>
);

export const With1BuildRemaining = () => (
<CurrentUsageDisplayer
subscription={subscriptionForIndieUser}
currentUsage={{
current: 4,
max: 5,
limitReached: false,
}}
onChangeSubscription={action('on change subscription callback')}
/>
);

export const WithRedemptionCode = () => (
<CurrentUsageDisplayer
subscription={silverSubscriptionWithRedemptionCode}
currentUsage={{
current: 2,
max: 10,
limitReached: false,
}}
onChangeSubscription={action('on change subscription callback')}
/>
);

export const WithExpiredRedemptionCode = () => (
<CurrentUsageDisplayer
subscription={silverSubscriptionWithExpiredRedemptionCode}
currentUsage={{
current: 2,
max: 10,
limitReached: false,
}}
onChangeSubscription={action('on change subscription callback')}
/>
);

export const LimitReached = () => (
<CurrentUsageDisplayer
subscription={subscriptionForIndieUser}
currentUsage={limitsReached.limits['cordova-build']}
onChangeSubscription={action('on change subscription callback')}
/>
);

export const LimitsReachedWithoutSubscription = () => (
<CurrentUsageDisplayer
subscription={noSubscription}
currentUsage={limitsReached.limits['cordova-build']}
onChangeSubscription={action('on change subscription callback')}
/>
);
Loading

0 comments on commit 1b4c5c1

Please sign in to comment.