mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
1 Commits
v5.5.241
...
rework-get
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3625acf975 |
@@ -28,7 +28,7 @@ import {
|
||||
import { useDebounce } from '../Utils/UseDebounce';
|
||||
import PromotionsSlideshow from '../Promotions/PromotionsSlideshow';
|
||||
import { ColumnStackLayout } from '../UI/Layout';
|
||||
import { EarnCredits } from '../MainFrame/EditorContainers/HomePage/GetStartedSection/EarnCredits';
|
||||
import { EarnCredits } from '../GameDashboard/Wallet/EarnCredits';
|
||||
|
||||
const cellSpacing = 2;
|
||||
|
||||
|
@@ -1,24 +1,24 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Text from '../../../../UI/Text';
|
||||
import Text from '../../UI/Text';
|
||||
import {
|
||||
ColumnStackLayout,
|
||||
LineStackLayout,
|
||||
ResponsiveLineStackLayout,
|
||||
} from '../../../../UI/Layout';
|
||||
} from '../../UI/Layout';
|
||||
import {
|
||||
type Badge,
|
||||
type Achievement,
|
||||
} from '../../../../Utils/GDevelopServices/Badge';
|
||||
import { Column } from '../../../../UI/Grid';
|
||||
import Window from '../../../../Utils/Window';
|
||||
import Coin from '../../../../Credits/Icons/Coin';
|
||||
import { selectMessageByLocale } from '../../../../Utils/i18n/MessageByLocale';
|
||||
} from '../../Utils/GDevelopServices/Badge';
|
||||
import { Column } from '../../UI/Grid';
|
||||
import Window from '../../Utils/Window';
|
||||
import Coin from '../../Credits/Icons/Coin';
|
||||
import { selectMessageByLocale } from '../../Utils/i18n/MessageByLocale';
|
||||
import { I18n } from '@lingui/react';
|
||||
import { useResponsiveWindowSize } from '../../../../UI/Responsive/ResponsiveWindowMeasurer';
|
||||
import TextButton from '../../../../UI/TextButton';
|
||||
import RouterContext from '../../../RouterContext';
|
||||
import { useResponsiveWindowSize } from '../../UI/Responsive/ResponsiveWindowMeasurer';
|
||||
import TextButton from '../../UI/TextButton';
|
||||
import RouterContext from '../../MainFrame/RouterContext';
|
||||
|
||||
type CreditItemType = 'badge' | 'feedback';
|
||||
type BadgeInfo = {|
|
@@ -7,7 +7,7 @@ import DashboardWidget, {
|
||||
import { ColumnStackLayout } from '../../UI/Layout';
|
||||
import Coin from '../../Credits/Icons/Coin';
|
||||
import AuthenticatedUserContext from '../../Profile/AuthenticatedUserContext';
|
||||
import { EarnCredits } from '../../MainFrame/EditorContainers/HomePage/GetStartedSection/EarnCredits';
|
||||
import { EarnCredits } from './EarnCredits';
|
||||
import TextButton from '../../UI/TextButton';
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
|
@@ -1,185 +0,0 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { t, Trans } from '@lingui/macro';
|
||||
import { I18n as I18nType } from '@lingui/core';
|
||||
import { useResponsiveWindowSize } from '../../../../UI/Responsive/ResponsiveWindowMeasurer';
|
||||
import GDevelopThemeContext from '../../../../UI/Theme/GDevelopThemeContext';
|
||||
import { type SubscriptionPlanWithPricingSystems } from '../../../../Utils/GDevelopServices/Usage';
|
||||
import { Column, Line } from '../../../../UI/Grid';
|
||||
import Paper from '../../../../UI/Paper';
|
||||
import {
|
||||
ColumnStackLayout,
|
||||
ResponsiveLineStackLayout,
|
||||
} from '../../../../UI/Layout';
|
||||
import Text from '../../../../UI/Text';
|
||||
import CheckCircle from '../../../../UI/CustomSvgIcons/CheckCircle';
|
||||
import RaisedButton from '../../../../UI/RaisedButton';
|
||||
import Window from '../../../../Utils/Window';
|
||||
import { selectMessageByLocale } from '../../../../Utils/i18n/MessageByLocale';
|
||||
|
||||
const styles = {
|
||||
bulletIcon: { width: 20, height: 20, marginRight: 10 },
|
||||
bulletText: { flex: 1 },
|
||||
planRecommendationThumbnail: { maxWidth: 350, flex: 1 },
|
||||
planRecommendationContainer: { borderRadius: 8, maxWidth: 850, padding: 8 },
|
||||
};
|
||||
|
||||
const planImages = {
|
||||
individual: {
|
||||
path: 'res/plan-individual.svg',
|
||||
alt: t`Red hero taking care of their diamond`,
|
||||
},
|
||||
education: {
|
||||
path: 'res/plan-education.svg',
|
||||
alt: t`Red hero sharing knowledge with pink cloud students`,
|
||||
},
|
||||
professional: {
|
||||
path: 'res/plan-professional.svg',
|
||||
alt: t`Red and Green heroes running side by side carrying their diamonds`,
|
||||
},
|
||||
};
|
||||
|
||||
const planDetailsById = {
|
||||
silver: {
|
||||
title: <Trans>GDevelop's Silver plan</Trans>,
|
||||
description: (
|
||||
<Trans>Unlock GDevelop's features to build more and faster.</Trans>
|
||||
),
|
||||
image: planImages.individual,
|
||||
link: 'https://gdevelop.io/pricing/individual',
|
||||
},
|
||||
gold: {
|
||||
title: <Trans>GDevelop's Gold plan</Trans>,
|
||||
description: (
|
||||
<Trans>Unlock GDevelop's features to build more and faster.</Trans>
|
||||
),
|
||||
image: planImages.individual,
|
||||
link: 'https://gdevelop.io/pricing/individual',
|
||||
},
|
||||
education: {
|
||||
title: <Trans>GDevelop's Education plan</Trans>,
|
||||
description: (
|
||||
<Trans>
|
||||
For universities, extra curricular classes and summer camps.
|
||||
</Trans>
|
||||
),
|
||||
image: planImages.education,
|
||||
link: 'https://gdevelop.io/pricing/education',
|
||||
},
|
||||
startup: {
|
||||
title: <Trans>GDevelop's Startup plan</Trans>,
|
||||
description: (
|
||||
<Trans>
|
||||
Get the most out of GDevelop and get your games out in no time.
|
||||
</Trans>
|
||||
),
|
||||
image: planImages.professional,
|
||||
link: 'https://gdevelop.io/pricing/business',
|
||||
},
|
||||
business: {
|
||||
title: <Trans>GDevelop's Business plan</Trans>,
|
||||
description: (
|
||||
<Trans>
|
||||
Get the most out of GDevelop and get your games out in no time.
|
||||
</Trans>
|
||||
),
|
||||
image: planImages.professional,
|
||||
link: 'https://gdevelop.io/pricing/business',
|
||||
},
|
||||
};
|
||||
|
||||
const PlanRecommendationRow = ({
|
||||
recommendationPlanId,
|
||||
subscriptionPlansWithPricingSystems,
|
||||
i18n,
|
||||
}: {|
|
||||
recommendationPlanId: string,
|
||||
subscriptionPlansWithPricingSystems: SubscriptionPlanWithPricingSystems[],
|
||||
i18n: I18nType,
|
||||
|}) => {
|
||||
const gdevelopTheme = React.useContext(GDevelopThemeContext);
|
||||
const { isMobile } = useResponsiveWindowSize();
|
||||
const planToUse =
|
||||
recommendationPlanId === 'silver'
|
||||
? 'gdevelop_silver'
|
||||
: recommendationPlanId === 'gold'
|
||||
? 'gdevelop_gold'
|
||||
: recommendationPlanId === 'education'
|
||||
? 'gdevelop_education'
|
||||
: recommendationPlanId === 'startup' ||
|
||||
recommendationPlanId === 'business'
|
||||
? 'gdevelop_startup'
|
||||
: null;
|
||||
if (!planToUse) return null;
|
||||
|
||||
const plan = subscriptionPlansWithPricingSystems.find(
|
||||
plan => plan.id === planToUse
|
||||
);
|
||||
if (!plan) return null;
|
||||
|
||||
const planDetails = planDetailsById[recommendationPlanId];
|
||||
|
||||
return (
|
||||
<Line justifyContent="center">
|
||||
<Paper
|
||||
background="dark"
|
||||
style={{
|
||||
...styles.planRecommendationContainer,
|
||||
border: `1px solid ${gdevelopTheme.palette.secondary}`,
|
||||
}}
|
||||
>
|
||||
<ResponsiveLineStackLayout noColumnMargin noMargin>
|
||||
<img
|
||||
src={planDetails.image.path}
|
||||
alt={i18n._(planDetails.image.alt)}
|
||||
style={styles.planRecommendationThumbnail}
|
||||
/>
|
||||
<Line expand>
|
||||
<ColumnStackLayout>
|
||||
<Text
|
||||
noMargin
|
||||
align={isMobile ? 'center' : 'left'}
|
||||
size="section-title"
|
||||
>
|
||||
{planDetails.title}
|
||||
</Text>
|
||||
<Text align={isMobile ? 'center' : 'left'}>
|
||||
{planDetails.description}
|
||||
</Text>
|
||||
<div style={{ padding: `0 20px` }}>
|
||||
<ColumnStackLayout noMargin>
|
||||
{plan.bulletPointsByLocale.map(
|
||||
(bulletPointByLocale, index) => (
|
||||
<Column key={index} expand noMargin>
|
||||
<Line noMargin alignItems="center">
|
||||
<CheckCircle
|
||||
style={{
|
||||
...styles.bulletIcon,
|
||||
color: gdevelopTheme.message.valid,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Text style={styles.bulletText} size="body2" noMargin>
|
||||
{selectMessageByLocale(i18n, bulletPointByLocale)}
|
||||
</Text>
|
||||
</Line>
|
||||
</Column>
|
||||
)
|
||||
)}
|
||||
</ColumnStackLayout>
|
||||
</div>
|
||||
<Column noMargin>
|
||||
<RaisedButton
|
||||
primary
|
||||
label={<Trans>Learn More</Trans>}
|
||||
onClick={() => Window.openExternalURL(planDetails.link)}
|
||||
/>
|
||||
</Column>
|
||||
</ColumnStackLayout>
|
||||
</Line>
|
||||
</ResponsiveLineStackLayout>
|
||||
</Paper>
|
||||
</Line>
|
||||
);
|
||||
};
|
||||
export default PlanRecommendationRow;
|
@@ -1,419 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import { I18n } from '@lingui/react';
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { makeStyles } from '@material-ui/styles';
|
||||
import GridList from '@material-ui/core/GridList';
|
||||
import GridListTile from '@material-ui/core/GridListTile';
|
||||
import { type AuthenticatedUser } from '../../../../Profile/AuthenticatedUserContext';
|
||||
import { type Subscription } from '../../../../Utils/GDevelopServices/Usage';
|
||||
import { TutorialContext } from '../../../../Tutorial/TutorialContext';
|
||||
import { SectionRow } from '../SectionContainer';
|
||||
import GuidedLessons from '../InAppTutorials/GuidedLessons';
|
||||
import { formatTutorialToImageTileComponent } from '../LearnSection';
|
||||
import ImageTileRow from '../../../../UI/ImageTileRow';
|
||||
import {
|
||||
useResponsiveWindowSize,
|
||||
type WindowSizeType,
|
||||
} from '../../../../UI/Responsive/ResponsiveWindowMeasurer';
|
||||
import Text from '../../../../UI/Text';
|
||||
import { Column, Line, Spacer } from '../../../../UI/Grid';
|
||||
import { type Tutorial } from '../../../../Utils/GDevelopServices/Tutorial';
|
||||
import { type SubscriptionPlanWithPricingSystems } from '../../../../Utils/GDevelopServices/Usage';
|
||||
import { CardWidget } from '../CardWidget';
|
||||
import Window from '../../../../Utils/Window';
|
||||
import { ColumnStackLayout } from '../../../../UI/Layout';
|
||||
import {
|
||||
type GuidedLessonsRecommendation,
|
||||
type PlanRecommendation,
|
||||
} from '../../../../Utils/GDevelopServices/User';
|
||||
import PreferencesContext from '../../../Preferences/PreferencesContext';
|
||||
import PlanRecommendationRow from './PlanRecommendationRow';
|
||||
import { SurveyCard } from './SurveyCard';
|
||||
import PlaceholderLoader from '../../../../UI/PlaceholderLoader';
|
||||
import PromotionsSlideshow from '../../../../Promotions/PromotionsSlideshow';
|
||||
import { PrivateTutorialViewDialog } from '../../../../AssetStore/PrivateTutorials/PrivateTutorialViewDialog';
|
||||
import FlatButton from '../../../../UI/FlatButton';
|
||||
import InAppTutorialContext from '../../../../InAppTutorial/InAppTutorialContext';
|
||||
import { type NewProjectSetup } from '../../../../ProjectCreation/NewProjectSetupDialog';
|
||||
import { type ExampleShortHeader } from '../../../../Utils/GDevelopServices/Example';
|
||||
import { selectMessageByLocale } from '../../../../Utils/i18n/MessageByLocale';
|
||||
|
||||
const styles = {
|
||||
textTutorialContent: {
|
||||
padding: 20,
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
},
|
||||
};
|
||||
|
||||
const useStyles = makeStyles({
|
||||
tile: {
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
const getTextTutorialsColumnsFromWidth = (
|
||||
windowSize: WindowSizeType,
|
||||
isLandscape: boolean
|
||||
) => {
|
||||
switch (windowSize) {
|
||||
case 'small':
|
||||
return isLandscape ? 4 : 2;
|
||||
case 'medium':
|
||||
return 2;
|
||||
case 'large':
|
||||
return 4;
|
||||
case 'xlarge':
|
||||
return 5;
|
||||
default:
|
||||
return 3;
|
||||
}
|
||||
};
|
||||
const getVideoTutorialsColumnsFromWidth = (
|
||||
windowSize: WindowSizeType,
|
||||
isLandscape: boolean
|
||||
) => {
|
||||
switch (windowSize) {
|
||||
case 'small':
|
||||
return isLandscape ? 5 : 2;
|
||||
case 'medium':
|
||||
return 3;
|
||||
case 'large':
|
||||
return 5;
|
||||
case 'xlarge':
|
||||
return 6;
|
||||
default:
|
||||
return 3;
|
||||
}
|
||||
};
|
||||
const getTutorialsLimitsFromWidth = (
|
||||
windowSize: WindowSizeType,
|
||||
isLandscape: boolean
|
||||
) => {
|
||||
switch (windowSize) {
|
||||
case 'small':
|
||||
return isLandscape ? 5 : 3;
|
||||
case 'medium':
|
||||
return 3;
|
||||
case 'large':
|
||||
return 5;
|
||||
case 'xlarge':
|
||||
return 5;
|
||||
default:
|
||||
return 3;
|
||||
}
|
||||
};
|
||||
|
||||
const isPlanRecommendationRelevant = (
|
||||
subscription: Subscription,
|
||||
planRecommendation: PlanRecommendation
|
||||
): boolean => {
|
||||
// Don't recommend plans to holders of education plan.
|
||||
if (subscription.planId === 'gdevelop_education') return false;
|
||||
|
||||
const relevantPlans =
|
||||
subscription.planId === 'gdevelop_silver' ||
|
||||
subscription.planId === 'gdevelop_indie'
|
||||
? ['gold', 'startup', 'business', 'education']
|
||||
: subscription.planId === 'gdevelop_gold' ||
|
||||
subscription.planId === 'gdevelop_pro'
|
||||
? ['startup', 'business', 'education']
|
||||
: subscription.planId === 'gdevelop_startup'
|
||||
? ['business']
|
||||
: [];
|
||||
return relevantPlans.includes(planRecommendation.id);
|
||||
};
|
||||
|
||||
type TextTutorialsRowProps = {|
|
||||
tutorials: Array<Tutorial>,
|
||||
i18n: I18nType,
|
||||
|};
|
||||
|
||||
const TextTutorialsRow = ({ tutorials, i18n }: TextTutorialsRowProps) => {
|
||||
const classes = useStyles();
|
||||
const { isLandscape, windowSize } = useResponsiveWindowSize();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Column noMargin>
|
||||
<Text size="section-title" noMargin>
|
||||
<Trans>Read</Trans>
|
||||
</Text>
|
||||
<Text>
|
||||
<Trans>
|
||||
Text-based content directly from GDevelop’s site and Wiki.
|
||||
</Trans>
|
||||
</Text>
|
||||
</Column>
|
||||
<GridList
|
||||
cols={getTextTutorialsColumnsFromWidth(windowSize, isLandscape)}
|
||||
cellHeight="auto"
|
||||
spacing={10}
|
||||
>
|
||||
{tutorials.map(tutorial => (
|
||||
<GridListTile key={tutorial.id} classes={{ tile: classes.tile }}>
|
||||
<CardWidget
|
||||
onClick={() =>
|
||||
Window.openExternalURL(
|
||||
selectMessageByLocale(i18n, tutorial.linkByLocale)
|
||||
)
|
||||
}
|
||||
size="large"
|
||||
>
|
||||
<div style={styles.textTutorialContent}>
|
||||
<ColumnStackLayout expand justifyContent="center" useFullHeight>
|
||||
<Text noMargin size="block-title">
|
||||
{selectMessageByLocale(i18n, tutorial.titleByLocale)}
|
||||
</Text>
|
||||
<Text noMargin size="body" color="secondary">
|
||||
{selectMessageByLocale(i18n, tutorial.descriptionByLocale)}
|
||||
</Text>
|
||||
</ColumnStackLayout>
|
||||
</div>
|
||||
</CardWidget>
|
||||
</GridListTile>
|
||||
))}
|
||||
</GridList>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type Props = {|
|
||||
authenticatedUser: AuthenticatedUser,
|
||||
selectInAppTutorial: (tutorialId: string) => void,
|
||||
subscriptionPlansWithPricingSystems: ?(SubscriptionPlanWithPricingSystems[]),
|
||||
onStartSurvey: null | (() => void),
|
||||
hasFilledSurveyAlready: boolean,
|
||||
onOpenProfile: () => void,
|
||||
onCreateProjectFromExample: (
|
||||
exampleShortHeader: ExampleShortHeader,
|
||||
newProjectSetup: NewProjectSetup,
|
||||
i18n: I18nType,
|
||||
isQuickCustomization?: boolean
|
||||
) => Promise<void>,
|
||||
askToCloseProject: () => Promise<boolean>,
|
||||
|};
|
||||
|
||||
const RecommendationList = ({
|
||||
authenticatedUser,
|
||||
selectInAppTutorial,
|
||||
subscriptionPlansWithPricingSystems,
|
||||
onStartSurvey,
|
||||
hasFilledSurveyAlready,
|
||||
onOpenProfile,
|
||||
onCreateProjectFromExample,
|
||||
askToCloseProject,
|
||||
}: Props) => {
|
||||
const { recommendations, subscription, limits } = authenticatedUser;
|
||||
const { tutorials } = React.useContext(TutorialContext);
|
||||
const {
|
||||
getTutorialProgress,
|
||||
values: { showInAppTutorialDeveloperMode },
|
||||
} = React.useContext(PreferencesContext);
|
||||
const { onLoadInAppTutorialFromLocalFile } = React.useContext(
|
||||
InAppTutorialContext
|
||||
);
|
||||
|
||||
const [
|
||||
selectedTutorial,
|
||||
setSelectedTutorial,
|
||||
] = React.useState<Tutorial | null>(null);
|
||||
|
||||
if (!recommendations) return <PlaceholderLoader />;
|
||||
|
||||
const recommendedTutorials = tutorials
|
||||
? recommendations
|
||||
.map(recommendation =>
|
||||
recommendation.type === 'gdevelop-tutorial'
|
||||
? tutorials.find(tutorial => tutorial.id === recommendation.id)
|
||||
: null
|
||||
)
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
|
||||
const recommendedVideoTutorials = recommendedTutorials.filter(
|
||||
tutorial => tutorial.type === 'video'
|
||||
);
|
||||
const recommendedTextTutorials = recommendedTutorials.filter(
|
||||
tutorial => tutorial.type === 'text'
|
||||
);
|
||||
|
||||
// $FlowIgnore
|
||||
const guidedLessonsRecommendation: ?GuidedLessonsRecommendation = recommendations.find(
|
||||
recommendation => recommendation.type === 'guided-lessons'
|
||||
);
|
||||
const guidedLessonsIds = guidedLessonsRecommendation
|
||||
? guidedLessonsRecommendation.lessonsIds
|
||||
: null;
|
||||
|
||||
// $FlowIgnore
|
||||
const planRecommendation: ?PlanRecommendation = recommendations.find(
|
||||
recommendation => recommendation.type === 'plan'
|
||||
);
|
||||
|
||||
const getInAppTutorialPartProgress = ({
|
||||
tutorialId,
|
||||
}: {
|
||||
tutorialId: string,
|
||||
}) => {
|
||||
const tutorialProgress = getTutorialProgress({
|
||||
tutorialId,
|
||||
userId: authenticatedUser.profile
|
||||
? authenticatedUser.profile.id
|
||||
: undefined,
|
||||
});
|
||||
if (!tutorialProgress || !tutorialProgress.progress) return 0;
|
||||
return tutorialProgress.progress[0]; // guided lessons only have one part.
|
||||
};
|
||||
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => {
|
||||
const items = [];
|
||||
|
||||
if (onStartSurvey && !hasFilledSurveyAlready)
|
||||
items.push(
|
||||
<SectionRow key="start-survey">
|
||||
<SurveyCard
|
||||
onStartSurvey={onStartSurvey}
|
||||
hasFilledSurveyAlready={false}
|
||||
/>
|
||||
</SectionRow>
|
||||
);
|
||||
|
||||
if (guidedLessonsRecommendation) {
|
||||
const displayTextAfterGuidedLessons = guidedLessonsIds
|
||||
? guidedLessonsIds
|
||||
.map(tutorialId => getInAppTutorialPartProgress({ tutorialId }))
|
||||
.every(progress => progress === 100)
|
||||
: false;
|
||||
|
||||
items.push(
|
||||
<SectionRow key="guided-lessons">
|
||||
<Line justifyContent="space-between" noMargin alignItems="center">
|
||||
<Text size="section-title" noMargin>
|
||||
<Trans>Build game mechanics</Trans>
|
||||
</Text>
|
||||
{showInAppTutorialDeveloperMode && (
|
||||
<FlatButton
|
||||
label={<Trans>Load local lesson</Trans>}
|
||||
onClick={onLoadInAppTutorialFromLocalFile}
|
||||
/>
|
||||
)}
|
||||
</Line>
|
||||
<GuidedLessons
|
||||
selectInAppTutorial={selectInAppTutorial}
|
||||
lessonsIds={guidedLessonsIds}
|
||||
/>
|
||||
{displayTextAfterGuidedLessons && (
|
||||
<Text>
|
||||
<Trans>
|
||||
Congratulations on completing this selection of guided
|
||||
lessons! Find all lessons in the Learn section.
|
||||
</Trans>
|
||||
</Text>
|
||||
)}
|
||||
</SectionRow>
|
||||
);
|
||||
}
|
||||
|
||||
if (recommendedVideoTutorials.length) {
|
||||
items.push(
|
||||
<SectionRow key="videos">
|
||||
<ImageTileRow
|
||||
title={<Trans>Get started with game creation</Trans>}
|
||||
margin="dense"
|
||||
items={recommendedVideoTutorials.map(tutorial =>
|
||||
formatTutorialToImageTileComponent({
|
||||
i18n,
|
||||
limits,
|
||||
tutorial,
|
||||
onSelectTutorial: setSelectedTutorial,
|
||||
})
|
||||
)}
|
||||
getColumnsFromWindowSize={getVideoTutorialsColumnsFromWidth}
|
||||
getLimitFromWindowSize={getTutorialsLimitsFromWidth}
|
||||
/>
|
||||
</SectionRow>
|
||||
);
|
||||
}
|
||||
|
||||
if (onStartSurvey && hasFilledSurveyAlready)
|
||||
items.push(
|
||||
<SectionRow key="start-survey">
|
||||
<SurveyCard
|
||||
onStartSurvey={onStartSurvey}
|
||||
hasFilledSurveyAlready
|
||||
/>
|
||||
</SectionRow>
|
||||
);
|
||||
|
||||
items.push(
|
||||
<SectionRow key="promotions">
|
||||
<Text size="section-title" noMargin>
|
||||
<Trans>Discover the ecosystem</Trans>
|
||||
</Text>
|
||||
<Spacer />
|
||||
<PromotionsSlideshow />
|
||||
</SectionRow>
|
||||
);
|
||||
|
||||
if (recommendedTextTutorials.length) {
|
||||
items.push(
|
||||
<SectionRow key="texts">
|
||||
<TextTutorialsRow
|
||||
tutorials={recommendedTextTutorials}
|
||||
i18n={i18n}
|
||||
/>
|
||||
</SectionRow>
|
||||
);
|
||||
}
|
||||
if (planRecommendation) {
|
||||
const shouldDisplayPlanRecommendation =
|
||||
limits &&
|
||||
!(
|
||||
limits.capabilities.classrooms &&
|
||||
limits.capabilities.classrooms.hideUpgradeNotice
|
||||
) &&
|
||||
(!subscription ||
|
||||
isPlanRecommendationRelevant(subscription, planRecommendation));
|
||||
if (
|
||||
shouldDisplayPlanRecommendation &&
|
||||
subscriptionPlansWithPricingSystems
|
||||
) {
|
||||
items.push(
|
||||
<SectionRow key="plan">
|
||||
<PlanRecommendationRow
|
||||
recommendationPlanId={planRecommendation.id}
|
||||
subscriptionPlansWithPricingSystems={
|
||||
subscriptionPlansWithPricingSystems
|
||||
}
|
||||
i18n={i18n}
|
||||
/>
|
||||
</SectionRow>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{items}
|
||||
{selectedTutorial && (
|
||||
<PrivateTutorialViewDialog
|
||||
tutorial={selectedTutorial}
|
||||
onClose={() => setSelectedTutorial(null)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</I18n>
|
||||
);
|
||||
};
|
||||
|
||||
export default RecommendationList;
|
@@ -1,299 +0,0 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
import Text from '../../../../UI/Text';
|
||||
import { ColumnStackLayout } from '../../../../UI/Layout';
|
||||
import AuthenticatedUserContext from '../../../../Profile/AuthenticatedUserContext';
|
||||
import { useOnlineStatus } from '../../../../Utils/OnlineStatus';
|
||||
import TreeLeaves from '../../../../UI/CustomSvgIcons/TreeLeaves';
|
||||
import SectionContainer from '../SectionContainer';
|
||||
import RaisedButton from '../../../../UI/RaisedButton';
|
||||
import useForceUpdate from '../../../../Utils/UseForceUpdate';
|
||||
import { LargeSpacer, Line } from '../../../../UI/Grid';
|
||||
import CircularProgress from '../../../../UI/CircularProgress';
|
||||
import { type UserSurvey as UserSurveyType } from '../../../../Utils/GDevelopServices/User';
|
||||
import UserSurvey from './UserSurvey';
|
||||
import {
|
||||
clearUserSurveyPersistedState,
|
||||
hasStartedUserSurvey,
|
||||
} from './UserSurveyStorage';
|
||||
import LinearProgress from '../../../../UI/LinearProgress';
|
||||
import PreferencesContext from '../../../Preferences/PreferencesContext';
|
||||
import RecommendationList from './RecommendationList';
|
||||
import ErrorBoundary from '../../../../UI/ErrorBoundary';
|
||||
import { delay } from '../../../../Utils/Delay';
|
||||
import { type SubscriptionPlanWithPricingSystems } from '../../../../Utils/GDevelopServices/Usage';
|
||||
import Checkbox from '../../../../UI/Checkbox';
|
||||
import { sendUserSurveyCompleted } from '../../../../Utils/Analytics/EventSender';
|
||||
import { type NewProjectSetup } from '../../../../ProjectCreation/NewProjectSetupDialog';
|
||||
import { type ExampleShortHeader } from '../../../../Utils/GDevelopServices/Example';
|
||||
|
||||
const styles = {
|
||||
icon: {
|
||||
width: 80,
|
||||
height: 80,
|
||||
margin: 20,
|
||||
},
|
||||
middlePageButtonContainer: {
|
||||
width: '100%',
|
||||
maxWidth: 300, // Make buttons larger but not too much.
|
||||
marginBottom: '15%', // Used to display the content of the section higher than at the center.
|
||||
},
|
||||
bottomPageButtonContainer: {
|
||||
width: '100%',
|
||||
maxWidth: 300, // Make buttons larger but not too much.
|
||||
marginBottom: 30, // Used to giver some space between the buttons and the screen bottom border.
|
||||
},
|
||||
linearProgress: { width: 200 },
|
||||
getFormContainerStyle: (isMobile: boolean) => ({
|
||||
marginTop: 20,
|
||||
// Take full width on mobile.
|
||||
width: isMobile ? '95%' : 300,
|
||||
}),
|
||||
questionnaireFinishedImage: { aspectRatio: '263 / 154' },
|
||||
};
|
||||
|
||||
const questionnaireFinishedImageSource = 'res/questionnaire/welcome-back.svg';
|
||||
|
||||
type Props = {|
|
||||
onUserSurveyStarted: () => void,
|
||||
onUserSurveyHidden: () => void,
|
||||
selectInAppTutorial: (tutorialId: string) => void,
|
||||
subscriptionPlansWithPricingSystems: ?(SubscriptionPlanWithPricingSystems[]),
|
||||
onOpenProfile: () => void,
|
||||
onCreateProjectFromExample: (
|
||||
exampleShortHeader: ExampleShortHeader,
|
||||
newProjectSetup: NewProjectSetup,
|
||||
i18n: I18nType,
|
||||
isQuickCustomization?: boolean
|
||||
) => Promise<void>,
|
||||
askToCloseProject: () => Promise<boolean>,
|
||||
|};
|
||||
|
||||
const GetStartedSection = ({
|
||||
selectInAppTutorial,
|
||||
onUserSurveyStarted,
|
||||
onUserSurveyHidden,
|
||||
subscriptionPlansWithPricingSystems,
|
||||
onOpenProfile,
|
||||
onCreateProjectFromExample,
|
||||
askToCloseProject,
|
||||
}: Props) => {
|
||||
const isFillingOutSurvey = hasStartedUserSurvey();
|
||||
const isOnline = useOnlineStatus();
|
||||
const authenticatedUser = React.useContext(AuthenticatedUserContext);
|
||||
const {
|
||||
profile,
|
||||
creatingOrLoggingInAccount,
|
||||
onEditProfile,
|
||||
loginState,
|
||||
} = authenticatedUser;
|
||||
const {
|
||||
values: preferences,
|
||||
setShowGetStartedSectionByDefault,
|
||||
} = React.useContext(PreferencesContext);
|
||||
const recommendationsGettingDelayPromise = React.useRef<?Promise<void>>(null);
|
||||
const forceUpdate = useForceUpdate();
|
||||
const [step, setStep] = React.useState<
|
||||
'survey' | 'surveyFinished' | 'recommendations'
|
||||
>(isFillingOutSurvey ? 'survey' : 'recommendations');
|
||||
|
||||
const [errorSendingSurvey, setErrorSendingSurvey] = React.useState<boolean>(
|
||||
false
|
||||
);
|
||||
|
||||
const onSurveyFinished = async (survey: UserSurveyType) => {
|
||||
try {
|
||||
setStep('surveyFinished');
|
||||
// Artificial delay to build up expectations.
|
||||
recommendationsGettingDelayPromise.current = delay(2500);
|
||||
await Promise.all([
|
||||
onEditProfile({ survey }, preferences),
|
||||
recommendationsGettingDelayPromise.current,
|
||||
]);
|
||||
sendUserSurveyCompleted();
|
||||
clearUserSurveyPersistedState();
|
||||
} catch (error) {
|
||||
console.error('An error occurred when sending survey:', error);
|
||||
setErrorSendingSurvey(true);
|
||||
} finally {
|
||||
recommendationsGettingDelayPromise.current = null;
|
||||
setStep('recommendations');
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(
|
||||
() => {
|
||||
if (!authenticatedUser.authenticated) clearUserSurveyPersistedState();
|
||||
},
|
||||
[authenticatedUser.authenticated]
|
||||
);
|
||||
|
||||
if (
|
||||
(creatingOrLoggingInAccount || loginState === 'loggingIn') &&
|
||||
// Do not display loader if the user is already seeing the recommendations.
|
||||
// It can happen when the user profile is refreshed while the recommendations
|
||||
// are displayed. This way, the loader is not displayed unnecessarily.
|
||||
step !== 'recommendations' &&
|
||||
!recommendationsGettingDelayPromise.current
|
||||
) {
|
||||
return (
|
||||
<SectionContainer flexBody>
|
||||
<ColumnStackLayout
|
||||
noMargin
|
||||
expand
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<ColumnStackLayout
|
||||
noMargin
|
||||
expand
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<CircularProgress size={40} />
|
||||
</ColumnStackLayout>
|
||||
</ColumnStackLayout>
|
||||
</SectionContainer>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isOnline || errorSendingSurvey) {
|
||||
return (
|
||||
<SectionContainer flexBody>
|
||||
<ColumnStackLayout
|
||||
noMargin
|
||||
expand
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
{errorSendingSurvey ? (
|
||||
<>
|
||||
<Text size="title" align="center">
|
||||
<Trans>Error when sending survey.</Trans>
|
||||
</Text>
|
||||
<TreeLeaves style={styles.icon} />
|
||||
<Text size="body2" noMargin align="center">
|
||||
<Trans>
|
||||
Verify your internet connection and try again later.
|
||||
</Trans>
|
||||
</Text>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Text size="title" align="center">
|
||||
<Trans>You seem to be offline</Trans>
|
||||
</Text>
|
||||
<TreeLeaves style={styles.icon} />
|
||||
<Text size="body2" noMargin align="center">
|
||||
<Trans>
|
||||
Verify your internet connection to access your personalized
|
||||
content.
|
||||
</Trans>
|
||||
</Text>
|
||||
<div style={styles.middlePageButtonContainer}>
|
||||
<Line expand>
|
||||
<RaisedButton
|
||||
primary
|
||||
label={<Trans>Refresh</Trans>}
|
||||
onClick={forceUpdate}
|
||||
fullWidth
|
||||
/>
|
||||
</Line>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</ColumnStackLayout>
|
||||
</SectionContainer>
|
||||
);
|
||||
}
|
||||
|
||||
if (step === 'surveyFinished') {
|
||||
return (
|
||||
<SectionContainer flexBody>
|
||||
<ColumnStackLayout
|
||||
noMargin
|
||||
expand
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text size="title" align="center">
|
||||
<Trans>Alright let's see what we have for you...</Trans>
|
||||
</Text>
|
||||
<img
|
||||
src={questionnaireFinishedImageSource}
|
||||
alt="You as the red hero coming back to life"
|
||||
style={styles.questionnaireFinishedImage}
|
||||
/>
|
||||
<Text size="body2" noMargin align="center">
|
||||
<Trans>Just one second please...</Trans>
|
||||
</Text>
|
||||
<Line>
|
||||
<LinearProgress
|
||||
variant="indeterminate"
|
||||
style={styles.linearProgress}
|
||||
/>
|
||||
</Line>
|
||||
</ColumnStackLayout>
|
||||
</SectionContainer>
|
||||
);
|
||||
}
|
||||
|
||||
if (step === 'recommendations') {
|
||||
return (
|
||||
<>
|
||||
<SectionContainer flexBody showUrgentAnnouncements>
|
||||
<RecommendationList
|
||||
authenticatedUser={authenticatedUser}
|
||||
selectInAppTutorial={selectInAppTutorial}
|
||||
subscriptionPlansWithPricingSystems={
|
||||
subscriptionPlansWithPricingSystems
|
||||
}
|
||||
onOpenProfile={onOpenProfile}
|
||||
onStartSurvey={
|
||||
profile
|
||||
? () => {
|
||||
setStep('survey');
|
||||
}
|
||||
: null
|
||||
}
|
||||
hasFilledSurveyAlready={profile ? !!profile.survey : false}
|
||||
onCreateProjectFromExample={onCreateProjectFromExample}
|
||||
askToCloseProject={askToCloseProject}
|
||||
/>
|
||||
{authenticatedUser.recommendations && (
|
||||
<Line justifyContent="center" alignItems="center">
|
||||
<Checkbox
|
||||
label={<Trans>Don't show this screen on next startup</Trans>}
|
||||
checked={!preferences.showGetStartedSectionByDefault}
|
||||
onCheck={(e, checked) =>
|
||||
setShowGetStartedSectionByDefault(!checked)
|
||||
}
|
||||
/>
|
||||
</Line>
|
||||
)}
|
||||
<LargeSpacer />
|
||||
</SectionContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<UserSurvey
|
||||
onCompleted={onSurveyFinished}
|
||||
onStarted={onUserSurveyStarted}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const GetStartedSectionWithErrorBoundary = (props: Props) => (
|
||||
<ErrorBoundary
|
||||
componentTitle={<Trans>Get started section</Trans>}
|
||||
scope="start-page-get-started"
|
||||
>
|
||||
<GetStartedSection {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
export default GetStartedSectionWithErrorBoundary;
|
@@ -10,7 +10,6 @@ import HammerIcon from '../../../UI/CustomSvgIcons/Hammer';
|
||||
import SchoolIcon from '../../../UI/CustomSvgIcons/School';
|
||||
import ControllerIcon from '../../../UI/CustomSvgIcons/Controller';
|
||||
import BookLeafIcon from '../../../UI/CustomSvgIcons/BookLeaf';
|
||||
import SunIcon from '../../../UI/CustomSvgIcons/Sun';
|
||||
import StoreIcon from '../../../UI/CustomSvgIcons/Store';
|
||||
import Preferences from '../../../UI/CustomSvgIcons/Preferences';
|
||||
import GDevelopGLogo from '../../../UI/CustomSvgIcons/GDevelopGLogo';
|
||||
@@ -43,13 +42,7 @@ export const styles = {
|
||||
},
|
||||
};
|
||||
|
||||
export type HomeTab =
|
||||
| 'get-started'
|
||||
| 'create'
|
||||
| 'learn'
|
||||
| 'play'
|
||||
| 'shop'
|
||||
| 'team-view';
|
||||
export type HomeTab = 'create' | 'learn' | 'play' | 'shop' | 'team-view';
|
||||
|
||||
export type GetIconFunction = ({
|
||||
color: string,
|
||||
@@ -64,14 +57,6 @@ export type HomePageMenuTab = {|
|
||||
|};
|
||||
|
||||
const homePageMenuTabs: { [tab: HomeTab]: HomePageMenuTab } = {
|
||||
'get-started': {
|
||||
label: <Trans>Start</Trans>,
|
||||
tab: 'get-started',
|
||||
id: 'home-get-started-tab',
|
||||
getIcon: ({ color, fontSize }) => (
|
||||
<SunIcon fontSize={fontSize} color={color} />
|
||||
),
|
||||
},
|
||||
create: {
|
||||
label: <Trans>Create</Trans>,
|
||||
tab: 'create',
|
||||
@@ -131,15 +116,14 @@ export const getTabsToDisplay = ({
|
||||
limits.capabilities.classrooms &&
|
||||
limits.capabilities.classrooms.hidePremiumProducts
|
||||
);
|
||||
const displayTeachTab =
|
||||
!shouldHideClassroomTab(limits) && !isNativeMobileApp();
|
||||
const tabs: HomeTab[] = [
|
||||
'get-started',
|
||||
'create',
|
||||
!shouldHideClassroomTab(limits) && !isNativeMobileApp()
|
||||
? 'team-view'
|
||||
: null,
|
||||
displayShopTab ? 'shop' : null,
|
||||
'learn',
|
||||
'create',
|
||||
displayPlayTab ? 'play' : null,
|
||||
displayShopTab ? 'shop' : null,
|
||||
displayTeachTab ? 'team-view' : null,
|
||||
].filter(Boolean);
|
||||
return tabs.map(tab => homePageMenuTabs[tab]);
|
||||
};
|
||||
|
@@ -0,0 +1,60 @@
|
||||
// @flow
|
||||
import { getTabsToDisplay } from './HomePageMenu';
|
||||
import { isNativeMobileApp } from '../../../Utils/Platform';
|
||||
import { limitsForStudentUser } from '../../../fixtures/GDevelopServicesTestData';
|
||||
|
||||
jest.mock('../../../Utils/Platform');
|
||||
const mockFn = (fn: Function): JestMockFn<any, any> => fn;
|
||||
|
||||
describe('HomePageMenu', () => {
|
||||
describe('getTabsToDisplay', () => {
|
||||
beforeEach(() => {
|
||||
mockFn(isNativeMobileApp).mockReset();
|
||||
});
|
||||
|
||||
test('Default desktop user', () => {
|
||||
mockFn(isNativeMobileApp).mockReturnValue(false);
|
||||
|
||||
const tabs = getTabsToDisplay({ limits: null });
|
||||
|
||||
expect(tabs.map(tab => tab.tab)).toEqual([
|
||||
'learn',
|
||||
'create',
|
||||
'play',
|
||||
'shop',
|
||||
'team-view',
|
||||
]);
|
||||
});
|
||||
|
||||
test('Desktop user student', () => {
|
||||
mockFn(isNativeMobileApp).mockReturnValue(false);
|
||||
|
||||
const tabs = getTabsToDisplay({
|
||||
limits: limitsForStudentUser,
|
||||
});
|
||||
|
||||
expect(tabs.map(tab => tab.tab)).toEqual(['learn', 'create']);
|
||||
});
|
||||
|
||||
test('Default mobile user', () => {
|
||||
mockFn(isNativeMobileApp).mockReturnValue(true);
|
||||
|
||||
const tabs = getTabsToDisplay({ limits: null });
|
||||
|
||||
expect(tabs.map(tab => tab.tab)).toEqual([
|
||||
'learn',
|
||||
'create',
|
||||
'play',
|
||||
'shop',
|
||||
]);
|
||||
});
|
||||
|
||||
test('Mobile student user', () => {
|
||||
mockFn(isNativeMobileApp).mockReturnValue(true);
|
||||
|
||||
const tabs = getTabsToDisplay({ limits: limitsForStudentUser });
|
||||
|
||||
expect(tabs.map(tab => tab.tab)).toEqual(['learn', 'create']);
|
||||
});
|
||||
});
|
||||
});
|
@@ -8,7 +8,6 @@ import {
|
||||
type FileMetadata,
|
||||
type StorageProvider,
|
||||
} from '../../../ProjectsStorage';
|
||||
import GetStartedSection from './GetStartedSection';
|
||||
import LearnSection from './LearnSection';
|
||||
import PlaySection from './PlaySection';
|
||||
import CreateSection from './CreateSection';
|
||||
@@ -27,13 +26,6 @@ import TeamProvider from '../../../Profile/Team/TeamProvider';
|
||||
import { useResponsiveWindowSize } from '../../../UI/Responsive/ResponsiveWindowMeasurer';
|
||||
import { type PrivateGameTemplateListingData } from '../../../Utils/GDevelopServices/Shop';
|
||||
import { PrivateGameTemplateStoreContext } from '../../../AssetStore/PrivateGameTemplates/PrivateGameTemplateStoreContext';
|
||||
import PreferencesContext from '../../Preferences/PreferencesContext';
|
||||
import useSubscriptionPlans from '../../../Utils/UseSubscriptionPlans';
|
||||
import { incrementGetStartedSectionViewCount } from '../../../Utils/Analytics/LocalStats';
|
||||
import {
|
||||
sendUserSurveyHidden,
|
||||
sendUserSurveyStarted,
|
||||
} from '../../../Utils/Analytics/EventSender';
|
||||
import RouterContext, { type RouteArguments } from '../../RouterContext';
|
||||
import { type GameDetailsTab } from '../../../GameDashboard';
|
||||
import { canUseClassroomFeature } from '../../../Utils/GDevelopServices/Usage';
|
||||
@@ -45,6 +37,8 @@ import { type GamesList } from '../../../GameDashboard/UseGamesList';
|
||||
import { type GamesPlatformFrameTools } from './PlaySection/UseGamesPlatformFrame';
|
||||
import { type CourseChapter } from '../../../Utils/GDevelopServices/Asset';
|
||||
import useCourses from './UseCourses';
|
||||
import { getProgramOpeningCount } from '../../../Utils/Analytics/LocalStats';
|
||||
import { isNativeMobileApp } from '../../../Utils/Platform';
|
||||
|
||||
const getRequestedTab = (routeArguments: RouteArguments): HomeTab | null => {
|
||||
if (
|
||||
@@ -66,8 +60,6 @@ const getRequestedTab = (routeArguments: RouteArguments): HomeTab | null => {
|
||||
return 'play';
|
||||
} else if (routeArguments['initial-dialog'] === 'learn') {
|
||||
return 'learn';
|
||||
} else if (routeArguments['initial-dialog'] === 'get-started') {
|
||||
return 'get-started';
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -218,8 +210,6 @@ export const HomePage = React.memo<Props>(
|
||||
startTimeoutToUnloadIframe,
|
||||
loadIframeOrRemoveTimeout,
|
||||
} = gamesPlatformFrameTools;
|
||||
const userSurveyStartedRef = React.useRef<boolean>(false);
|
||||
const userSurveyHiddenRef = React.useRef<boolean>(false);
|
||||
const { fetchTutorials } = React.useContext(TutorialContext);
|
||||
const { fetchExamplesAndFilters } = React.useContext(ExampleStoreContext);
|
||||
const {
|
||||
@@ -267,17 +257,17 @@ export const HomePage = React.memo<Props>(
|
||||
] = React.useState<TutorialCategory | null>(null);
|
||||
|
||||
const { isMobile } = useResponsiveWindowSize();
|
||||
const {
|
||||
values: { showGetStartedSectionByDefault },
|
||||
} = React.useContext(PreferencesContext);
|
||||
const tabRequestedAtOpening = React.useRef<HomeTab | null>(
|
||||
getRequestedTab(routeArguments)
|
||||
);
|
||||
const programOpeningCount = getProgramOpeningCount();
|
||||
const initialTab = tabRequestedAtOpening.current
|
||||
? tabRequestedAtOpening.current
|
||||
: showGetStartedSectionByDefault
|
||||
? 'get-started'
|
||||
: 'create';
|
||||
: isNativeMobileApp()
|
||||
? 'play'
|
||||
: programOpeningCount > 1
|
||||
? 'create'
|
||||
: 'learn';
|
||||
|
||||
const [activeTab, setActiveTab] = React.useState<HomeTab>(initialTab);
|
||||
|
||||
@@ -291,9 +281,6 @@ export const HomePage = React.memo<Props>(
|
||||
: games.find(game => game.id === openedGameId),
|
||||
[games, openedGameId]
|
||||
);
|
||||
const { subscriptionPlansWithPricingSystems } = useSubscriptionPlans({
|
||||
includeLegacy: false,
|
||||
});
|
||||
|
||||
// Open the store and a pack or game template if asked to do so, either at
|
||||
// app opening, either when the route changes (when clicking on an announcement
|
||||
@@ -354,15 +341,6 @@ export const HomePage = React.memo<Props>(
|
||||
]
|
||||
);
|
||||
|
||||
React.useEffect(
|
||||
() => {
|
||||
if (initialTab === 'get-started') {
|
||||
incrementGetStartedSectionViewCount();
|
||||
}
|
||||
},
|
||||
[initialTab]
|
||||
);
|
||||
|
||||
// Load everything when the user opens the home page, to avoid future loading times.
|
||||
React.useEffect(
|
||||
() => {
|
||||
@@ -373,18 +351,6 @@ export const HomePage = React.memo<Props>(
|
||||
[fetchExamplesAndFilters, fetchTutorials, fetchGameTemplates]
|
||||
);
|
||||
|
||||
// Only fetch games if the user decides to open the games dashboard tab
|
||||
// or the build tab to enable the context menu on project list items that
|
||||
// redirects to the games dashboard.
|
||||
React.useEffect(
|
||||
() => {
|
||||
if (activeTab === 'create' && !games) {
|
||||
fetchGames();
|
||||
}
|
||||
},
|
||||
[fetchGames, activeTab, games]
|
||||
);
|
||||
|
||||
// Only fetch courses if the user decides to open the Learn section.
|
||||
React.useEffect(
|
||||
() => {
|
||||
@@ -490,28 +456,6 @@ export const HomePage = React.memo<Props>(
|
||||
onSceneObjectEdited,
|
||||
}));
|
||||
|
||||
const onUserSurveyStarted = React.useCallback(() => {
|
||||
if (userSurveyStartedRef.current) return;
|
||||
sendUserSurveyStarted();
|
||||
userSurveyStartedRef.current = true;
|
||||
}, []);
|
||||
const onUserSurveyHidden = React.useCallback(() => {
|
||||
if (userSurveyHiddenRef.current) return;
|
||||
sendUserSurveyHidden();
|
||||
userSurveyHiddenRef.current = true;
|
||||
}, []);
|
||||
|
||||
React.useEffect(
|
||||
() => {
|
||||
if (!authenticated) {
|
||||
userSurveyStartedRef.current = false;
|
||||
userSurveyHiddenRef.current = false;
|
||||
}
|
||||
},
|
||||
// Reset flag that prevents multiple send of the same event on user change.
|
||||
[authenticated]
|
||||
);
|
||||
|
||||
// As the homepage is never unmounted, we need to ensure the games platform
|
||||
// iframe is unloaded & loaded from here,
|
||||
// allowing to handle when the user navigates to another tab.
|
||||
@@ -581,19 +525,6 @@ export const HomePage = React.memo<Props>(
|
||||
canSaveProject={canSave}
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'get-started' && (
|
||||
<GetStartedSection
|
||||
selectInAppTutorial={selectInAppTutorial}
|
||||
onUserSurveyStarted={onUserSurveyStarted}
|
||||
onUserSurveyHidden={onUserSurveyHidden}
|
||||
subscriptionPlansWithPricingSystems={
|
||||
subscriptionPlansWithPricingSystems
|
||||
}
|
||||
onOpenProfile={onOpenProfile}
|
||||
onCreateProjectFromExample={onCreateProjectFromExample}
|
||||
askToCloseProject={askToCloseProject}
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'learn' && (
|
||||
<LearnSection
|
||||
onTabChange={setActiveTab}
|
||||
|
@@ -216,7 +216,6 @@ export type PreferencesValues = {|
|
||||
resourcesImporationBehavior: ResourceImportationBehavior,
|
||||
eventsSheetCancelInlineParameter: 'cancel' | 'apply',
|
||||
showCommunityExtensions: boolean,
|
||||
showGetStartedSectionByDefault: boolean,
|
||||
showInAppTutorialDeveloperMode: boolean,
|
||||
showDeprecatedInstructionWarning: boolean,
|
||||
openDiagnosticReportAutomatically: boolean,
|
||||
@@ -302,7 +301,6 @@ export type Preferences = {|
|
||||
setIsAlwaysOnTopInPreview: (enabled: boolean) => void,
|
||||
setEventsSheetCancelInlineParameter: (value: string) => void,
|
||||
setShowCommunityExtensions: (enabled: boolean) => void,
|
||||
setShowGetStartedSectionByDefault: (enabled: boolean) => void,
|
||||
setShowInAppTutorialDeveloperMode: (enabled: boolean) => void,
|
||||
setOpenDiagnosticReportAutomatically: (enabled: boolean) => void,
|
||||
getOpenDiagnosticReportAutomatically: () => boolean,
|
||||
@@ -381,7 +379,6 @@ export const initialPreferences = {
|
||||
resourcesImporationBehavior: 'ask',
|
||||
eventsSheetCancelInlineParameter: 'apply',
|
||||
showCommunityExtensions: false,
|
||||
showGetStartedSectionByDefault: true,
|
||||
showInAppTutorialDeveloperMode: false,
|
||||
openDiagnosticReportAutomatically: true,
|
||||
showDeprecatedInstructionWarning: false,
|
||||
@@ -449,7 +446,6 @@ export const initialPreferences = {
|
||||
setIsAlwaysOnTopInPreview: () => {},
|
||||
setEventsSheetCancelInlineParameter: () => {},
|
||||
setShowCommunityExtensions: () => {},
|
||||
setShowGetStartedSectionByDefault: (enabled: boolean) => {},
|
||||
setShowInAppTutorialDeveloperMode: (enabled: boolean) => {},
|
||||
setShowDeprecatedInstructionWarning: (enabled: boolean) => {},
|
||||
getOpenDiagnosticReportAutomatically: () => true,
|
||||
|
@@ -149,9 +149,6 @@ export default class PreferencesProvider extends React.Component<Props, State> {
|
||||
this
|
||||
),
|
||||
setShowCommunityExtensions: this._setShowCommunityExtensions.bind(this),
|
||||
setShowGetStartedSectionByDefault: this._setShowGetStartedSection.bind(
|
||||
this
|
||||
),
|
||||
setShowInAppTutorialDeveloperMode: this._setShowInAppTutorialDeveloperMode.bind(
|
||||
this
|
||||
),
|
||||
@@ -353,18 +350,6 @@ export default class PreferencesProvider extends React.Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
_setShowGetStartedSection(showGetStartedSectionByDefault: boolean) {
|
||||
this.setState(
|
||||
state => ({
|
||||
values: {
|
||||
...state.values,
|
||||
showGetStartedSectionByDefault,
|
||||
},
|
||||
}),
|
||||
() => this._persistValuesToLocalStorage(this.state)
|
||||
);
|
||||
}
|
||||
|
||||
_setThemeName(themeName: string) {
|
||||
this.setState(
|
||||
state => ({
|
||||
|
@@ -12,8 +12,8 @@ export type Route =
|
||||
| 'build' // Old way of opening the build section
|
||||
| 'create' // New way of opening the build section
|
||||
| 'education'
|
||||
| 'play'
|
||||
| 'get-started';
|
||||
| 'learn'
|
||||
| 'play';
|
||||
type RouteKey =
|
||||
| 'initial-dialog'
|
||||
| 'game-id'
|
||||
|
@@ -1,7 +1,18 @@
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import RouterContext from './RouterContext';
|
||||
import RouterContext, { type Route } from './RouterContext';
|
||||
|
||||
const homePageRoutes: Route[] = [
|
||||
'play',
|
||||
'learn',
|
||||
'build',
|
||||
'create',
|
||||
'games-dashboard',
|
||||
'asset-store',
|
||||
'store',
|
||||
'education',
|
||||
];
|
||||
|
||||
/**
|
||||
* This hook is used to be able to use route arguments from anywhere to open the homepage.
|
||||
@@ -22,18 +33,7 @@ const useHomepageWitchForRouting = ({
|
||||
const initialDialog = routeArguments['initial-dialog'];
|
||||
if (!initialDialog) return;
|
||||
|
||||
if (
|
||||
[
|
||||
'games-dashboard',
|
||||
'asset-store',
|
||||
'store',
|
||||
'build',
|
||||
'education',
|
||||
'play',
|
||||
'community',
|
||||
'get-started',
|
||||
].includes(initialDialog)
|
||||
) {
|
||||
if (homePageRoutes.includes(initialDialog)) {
|
||||
closeDialogs();
|
||||
openHomePage();
|
||||
}
|
||||
|
@@ -34,7 +34,6 @@ type ErrorBoundaryScope =
|
||||
| 'app'
|
||||
| 'editor'
|
||||
| 'start-page'
|
||||
| 'start-page-get-started'
|
||||
| 'start-page-shop'
|
||||
| 'start-page-learn'
|
||||
| 'start-page-play'
|
||||
|
@@ -26,29 +26,3 @@ export const incrementProgramOpeningCount = () => {
|
||||
console.warn('Unable to store program opening count', e);
|
||||
}
|
||||
};
|
||||
|
||||
export const getGetStartedSectionViewCount = (): number => {
|
||||
try {
|
||||
const count = localStorage.getItem(
|
||||
`${localStorageLocalStatsPrefix}-get-started-section-view-count`
|
||||
);
|
||||
if (count !== null) return parseInt(count, 10);
|
||||
} catch (e) {
|
||||
console.warn('Unable to load stored get started section view count', e);
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
export const incrementGetStartedSectionViewCount = () => {
|
||||
const count = getGetStartedSectionViewCount() + 1;
|
||||
|
||||
try {
|
||||
localStorage.setItem(
|
||||
`${localStorageLocalStatsPrefix}-get-started-section-view-count`,
|
||||
'' + count
|
||||
);
|
||||
} catch (e) {
|
||||
console.warn('Unable to store get started section view count', e);
|
||||
}
|
||||
};
|
||||
|
@@ -1,104 +0,0 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import paperDecorator from '../../../PaperDecorator';
|
||||
import { fakeAuthenticatedUserWithNoSubscription } from '../../../../fixtures/GDevelopServicesTestData';
|
||||
import RecommendationList from '../../../../MainFrame/EditorContainers/HomePage/GetStartedSection/RecommendationList';
|
||||
import PreferencesContext, {
|
||||
initialPreferences,
|
||||
} from '../../../../MainFrame/Preferences/PreferencesContext';
|
||||
import inAppTutorialDecorator from '../../../InAppTutorialDecorator';
|
||||
import { TutorialStateProvider } from '../../../../Tutorial/TutorialContext';
|
||||
import useSubscriptionPlans, {
|
||||
getAvailableSubscriptionPlansWithPrices,
|
||||
} from '../../../../Utils/UseSubscriptionPlans';
|
||||
import LoaderModal from '../../../../UI/LoaderModal';
|
||||
|
||||
export default {
|
||||
title: 'HomePage/GetStartedSectionSection/RecommendationList',
|
||||
component: RecommendationList,
|
||||
decorators: [paperDecorator, inAppTutorialDecorator],
|
||||
};
|
||||
|
||||
export const Default = () => {
|
||||
const { subscriptionPlansWithPricingSystems } = useSubscriptionPlans({
|
||||
includeLegacy: true,
|
||||
authenticatedUser: fakeAuthenticatedUserWithNoSubscription,
|
||||
});
|
||||
return subscriptionPlansWithPricingSystems ? (
|
||||
<PreferencesContext.Provider value={initialPreferences}>
|
||||
<TutorialStateProvider>
|
||||
<RecommendationList
|
||||
onOpenProfile={action('onOpenProfile')}
|
||||
authenticatedUser={fakeAuthenticatedUserWithNoSubscription}
|
||||
selectInAppTutorial={action('selectInAppTutorial')}
|
||||
subscriptionPlansWithPricingSystems={getAvailableSubscriptionPlansWithPrices(
|
||||
subscriptionPlansWithPricingSystems
|
||||
)}
|
||||
onStartSurvey={null}
|
||||
hasFilledSurveyAlready={false}
|
||||
askToCloseProject={async () => true}
|
||||
onCreateProjectFromExample={action('onCreateProjectFromExample')}
|
||||
/>
|
||||
</TutorialStateProvider>
|
||||
</PreferencesContext.Provider>
|
||||
) : (
|
||||
<LoaderModal show />
|
||||
);
|
||||
};
|
||||
|
||||
export const WithSurvey = () => {
|
||||
const { subscriptionPlansWithPricingSystems } = useSubscriptionPlans({
|
||||
includeLegacy: true,
|
||||
authenticatedUser: fakeAuthenticatedUserWithNoSubscription,
|
||||
});
|
||||
|
||||
return subscriptionPlansWithPricingSystems ? (
|
||||
<PreferencesContext.Provider value={initialPreferences}>
|
||||
<TutorialStateProvider>
|
||||
<RecommendationList
|
||||
onOpenProfile={action('onOpenProfile')}
|
||||
authenticatedUser={fakeAuthenticatedUserWithNoSubscription}
|
||||
selectInAppTutorial={action('selectInAppTutorial')}
|
||||
subscriptionPlansWithPricingSystems={getAvailableSubscriptionPlansWithPrices(
|
||||
subscriptionPlansWithPricingSystems
|
||||
)}
|
||||
onStartSurvey={action('onStartSurvey')}
|
||||
hasFilledSurveyAlready={false}
|
||||
askToCloseProject={async () => true}
|
||||
onCreateProjectFromExample={action('onCreateProjectFromExample')}
|
||||
/>
|
||||
</TutorialStateProvider>
|
||||
</PreferencesContext.Provider>
|
||||
) : (
|
||||
<LoaderModal show />
|
||||
);
|
||||
};
|
||||
|
||||
export const WithSurveyAlreadyFilled = () => {
|
||||
const { subscriptionPlansWithPricingSystems } = useSubscriptionPlans({
|
||||
includeLegacy: true,
|
||||
authenticatedUser: fakeAuthenticatedUserWithNoSubscription,
|
||||
});
|
||||
|
||||
return subscriptionPlansWithPricingSystems ? (
|
||||
<PreferencesContext.Provider value={initialPreferences}>
|
||||
<TutorialStateProvider>
|
||||
<RecommendationList
|
||||
onOpenProfile={action('onOpenProfile')}
|
||||
authenticatedUser={fakeAuthenticatedUserWithNoSubscription}
|
||||
selectInAppTutorial={action('selectInAppTutorial')}
|
||||
subscriptionPlansWithPricingSystems={getAvailableSubscriptionPlansWithPrices(
|
||||
subscriptionPlansWithPricingSystems
|
||||
)}
|
||||
onStartSurvey={action('onStartSurvey')}
|
||||
hasFilledSurveyAlready={true}
|
||||
askToCloseProject={async () => true}
|
||||
onCreateProjectFromExample={action('onCreateProjectFromExample')}
|
||||
/>
|
||||
</TutorialStateProvider>
|
||||
</PreferencesContext.Provider>
|
||||
) : (
|
||||
<LoaderModal show />
|
||||
);
|
||||
};
|
Reference in New Issue
Block a user