mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Additional bundles are added to the Learn & Shop sections (#7805)
* A premium bundle, including multiple courses, asset packs, templates and a gold subscription * A curated platformer-specific bundle, including everything needed to learn & create a platformer game
This commit is contained in:
@@ -259,7 +259,7 @@ const BundleInformationPage = ({
|
|||||||
padding: 0,
|
padding: 0,
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...(bundleListingData.visibleUntil
|
...(bundleListingData.visibleUntil && !noPadding
|
||||||
? {
|
? {
|
||||||
backgroundAttachment: 'local',
|
backgroundAttachment: 'local',
|
||||||
backgroundRepeat: 'no-repeat',
|
backgroundRepeat: 'no-repeat',
|
||||||
|
@@ -389,41 +389,39 @@ const BundlePageHeader = ({
|
|||||||
</SectionRow>
|
</SectionRow>
|
||||||
)}
|
)}
|
||||||
<SectionRow>
|
<SectionRow>
|
||||||
<Paper background="dark" variant="outlined" style={{ padding: 16 }}>
|
<ResponsiveLineStackLayout
|
||||||
<ResponsiveLineStackLayout
|
noMargin
|
||||||
noMargin
|
alignItems="center"
|
||||||
alignItems="center"
|
justifyContent="flex-start"
|
||||||
justifyContent="flex-start"
|
forceMobileLayout={isMediumScreen}
|
||||||
forceMobileLayout={isMediumScreen}
|
expand
|
||||||
|
>
|
||||||
|
<div style={styles.imageContainer}>
|
||||||
|
<img
|
||||||
|
src={bundle.previewImageUrls[0]}
|
||||||
|
style={styles.image}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ColumnStackLayout
|
||||||
expand
|
expand
|
||||||
|
justifyContent="flex-start"
|
||||||
|
noMargin={isMobile}
|
||||||
>
|
>
|
||||||
<div style={styles.imageContainer}>
|
<Text size="title" noMargin style={styles.title}>
|
||||||
<img
|
{selectMessageByLocale(i18n, bundle.nameByLocale)}
|
||||||
src={bundle.previewImageUrls[0]}
|
</Text>
|
||||||
style={styles.image}
|
<Line noMargin>
|
||||||
alt=""
|
<Text noMargin>
|
||||||
/>
|
{selectMessageByLocale(
|
||||||
</div>
|
i18n,
|
||||||
<ColumnStackLayout
|
bundle.longDescriptionByLocale
|
||||||
expand
|
)}
|
||||||
justifyContent="flex-start"
|
|
||||||
noMargin={isMobile}
|
|
||||||
>
|
|
||||||
<Text size="title" noMargin style={styles.title}>
|
|
||||||
{selectMessageByLocale(i18n, bundle.nameByLocale)}
|
|
||||||
</Text>
|
</Text>
|
||||||
<Line noMargin>
|
</Line>
|
||||||
<Text noMargin>
|
{summaryLines}
|
||||||
{selectMessageByLocale(
|
</ColumnStackLayout>
|
||||||
i18n,
|
</ResponsiveLineStackLayout>
|
||||||
bundle.longDescriptionByLocale
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
</Line>
|
|
||||||
{summaryLines}
|
|
||||||
</ColumnStackLayout>
|
|
||||||
</ResponsiveLineStackLayout>
|
|
||||||
</Paper>
|
|
||||||
</SectionRow>
|
</SectionRow>
|
||||||
{!isAlreadyReceived && isMobileOrMediumScreen && bundleListingData && (
|
{!isAlreadyReceived && isMobileOrMediumScreen && bundleListingData && (
|
||||||
<SectionRow>
|
<SectionRow>
|
||||||
|
@@ -260,9 +260,10 @@ const getColumnsFromWindowSize = (windowSize: WindowSizeType) => {
|
|||||||
type Props = {|
|
type Props = {|
|
||||||
onDisplayBundle: (bundleListingData: BundleListingData) => void,
|
onDisplayBundle: (bundleListingData: BundleListingData) => void,
|
||||||
i18n: I18nType,
|
i18n: I18nType,
|
||||||
|
category: string,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
const BundlePreviewBanner = ({ onDisplayBundle, i18n }: Props) => {
|
const BundlePreviewBanner = ({ onDisplayBundle, i18n, category }: Props) => {
|
||||||
const { isMobile, isLandscape, windowSize } = useResponsiveWindowSize();
|
const { isMobile, isLandscape, windowSize } = useResponsiveWindowSize();
|
||||||
const numberOfTilesToDisplay = getColumnsFromWindowSize(windowSize) - 1; // Reserve one tile for the bundle preview.
|
const numberOfTilesToDisplay = getColumnsFromWindowSize(windowSize) - 1; // Reserve one tile for the bundle preview.
|
||||||
const { privateGameTemplateListingDatas } = React.useContext(
|
const { privateGameTemplateListingDatas } = React.useContext(
|
||||||
@@ -278,28 +279,34 @@ const BundlePreviewBanner = ({ onDisplayBundle, i18n }: Props) => {
|
|||||||
const { bundlePurchases, receivedBundles } = authenticatedUser;
|
const { bundlePurchases, receivedBundles } = authenticatedUser;
|
||||||
|
|
||||||
// For the moment, we either display:
|
// For the moment, we either display:
|
||||||
// - the first bundle in the list if none are owned.
|
// - the first bundle of that category if none are owned.
|
||||||
// - the first owned bundle (as a listing data if still listed, or as an archived listing data otherwise)
|
// - the first owned bundle of that category (as a listing data if still listed, or as an archived listing data otherwise)
|
||||||
// TODO: improve that logic when we'll have more bundles.
|
|
||||||
const bundleListingData: BundleListingData | null = React.useMemo(
|
const bundleListingData: BundleListingData | null = React.useMemo(
|
||||||
() => {
|
() => {
|
||||||
if (!bundleListingDatas || !receivedBundles) return null;
|
if (!bundleListingDatas || !receivedBundles) return null;
|
||||||
if (receivedBundles.length === 0) {
|
const bundleListingDataOfCategory = bundleListingDatas.filter(bundle =>
|
||||||
return bundleListingDatas[0]; // Display the first bundle if none are owned.
|
bundle.categories.includes(category)
|
||||||
|
)[0];
|
||||||
|
const receivedBundleOfCategory = receivedBundles.filter(bundle =>
|
||||||
|
bundle.categories.includes(category)
|
||||||
|
)[0];
|
||||||
|
if (!receivedBundleOfCategory) {
|
||||||
|
// Display the first bundle if none are found with that category.
|
||||||
|
return bundleListingDataOfCategory || bundleListingDatas[0];
|
||||||
}
|
}
|
||||||
const receivedBundle = receivedBundles[0];
|
const bundleListingDataMatchingOwnedBundle = bundleListingDatas.find(
|
||||||
const bundleListingData = bundleListingDatas.find(
|
bundleListingData =>
|
||||||
bundleListingData => bundleListingData.id === receivedBundle.id
|
bundleListingData.id === receivedBundleOfCategory.id
|
||||||
);
|
);
|
||||||
if (bundleListingData) {
|
|
||||||
return bundleListingData; // Display the first owned bundle that is still listed.
|
|
||||||
}
|
|
||||||
// If this bundle is not listed anymore, get an archived listing data for that bundle.
|
// If this bundle is not listed anymore, get an archived listing data for that bundle.
|
||||||
return getArchivedBundleListingData({
|
return (
|
||||||
bundle: receivedBundle,
|
bundleListingDataMatchingOwnedBundle ||
|
||||||
});
|
getArchivedBundleListingData({
|
||||||
|
bundle: receivedBundleOfCategory,
|
||||||
|
})
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[bundleListingDatas, receivedBundles]
|
[bundleListingDatas, receivedBundles, category]
|
||||||
);
|
);
|
||||||
|
|
||||||
const userBundlePurchaseUsageType = React.useMemo(
|
const userBundlePurchaseUsageType = React.useMemo(
|
||||||
|
@@ -13,7 +13,7 @@ import * as React from 'react';
|
|||||||
import { Trans } from '@lingui/macro';
|
import { Trans } from '@lingui/macro';
|
||||||
import { I18n } from '@lingui/react';
|
import { I18n } from '@lingui/react';
|
||||||
import Text from '../../UI/Text';
|
import Text from '../../UI/Text';
|
||||||
import { Column } from '../../UI/Grid';
|
import { Column, Line, Spacer } from '../../UI/Grid';
|
||||||
import { LineStackLayout, ResponsiveLineStackLayout } from '../../UI/Layout';
|
import { LineStackLayout, ResponsiveLineStackLayout } from '../../UI/Layout';
|
||||||
import {
|
import {
|
||||||
getPlanIcon,
|
getPlanIcon,
|
||||||
@@ -677,9 +677,13 @@ export const getSummaryLines = ({
|
|||||||
summaryLineItems.forEach((item, index) => {
|
summaryLineItems.forEach((item, index) => {
|
||||||
if (index !== 0) {
|
if (index !== 0) {
|
||||||
desktopLineItems.push(
|
desktopLineItems.push(
|
||||||
<Column key={`divider-${index}`}>
|
<Line noMargin key={`divider-${index}`}>
|
||||||
<Divider orientation="vertical" />
|
<Spacer />
|
||||||
</Column>
|
<Column>
|
||||||
|
<Divider orientation="vertical" />
|
||||||
|
</Column>
|
||||||
|
<Spacer />
|
||||||
|
</Line>
|
||||||
);
|
);
|
||||||
if (index % 2 === 1) {
|
if (index % 2 === 1) {
|
||||||
mobileLineItems.push(
|
mobileLineItems.push(
|
||||||
|
@@ -131,6 +131,7 @@ const CoursesPage = ({
|
|||||||
<SectionRow>
|
<SectionRow>
|
||||||
<BundlePreviewBanner
|
<BundlePreviewBanner
|
||||||
onDisplayBundle={onSelectBundle}
|
onDisplayBundle={onSelectBundle}
|
||||||
|
category="starter"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
/>
|
/>
|
||||||
</SectionRow>
|
</SectionRow>
|
||||||
@@ -144,28 +145,72 @@ const CoursesPage = ({
|
|||||||
cellHeight="auto"
|
cellHeight="auto"
|
||||||
spacing={ITEMS_SPACING * 2}
|
spacing={ITEMS_SPACING * 2}
|
||||||
>
|
>
|
||||||
{courses.slice(numberOfItemsOnOneRow).map(course => {
|
{courses
|
||||||
const completion = getCourseCompletion(course.id);
|
.slice(numberOfItemsOnOneRow, 2 * numberOfItemsOnOneRow)
|
||||||
const courseListingData = listedCourses.find(
|
.map(course => {
|
||||||
listedCourse => listedCourse.id === course.id
|
const completion = getCourseCompletion(course.id);
|
||||||
);
|
const courseListingData = listedCourses.find(
|
||||||
return (
|
listedCourse => listedCourse.id === course.id
|
||||||
<GridListTile key={course.id}>
|
);
|
||||||
<CourseCard
|
return (
|
||||||
course={course}
|
<GridListTile key={course.id}>
|
||||||
courseListingData={courseListingData}
|
<CourseCard
|
||||||
completion={completion}
|
course={course}
|
||||||
onClick={() => {
|
courseListingData={courseListingData}
|
||||||
onSelectCourse(course.id);
|
completion={completion}
|
||||||
}}
|
onClick={() => {
|
||||||
/>
|
onSelectCourse(course.id);
|
||||||
</GridListTile>
|
}}
|
||||||
);
|
/>
|
||||||
})}
|
</GridListTile>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</GridList>
|
</GridList>
|
||||||
</Line>
|
</Line>
|
||||||
</SectionRow>
|
</SectionRow>
|
||||||
)}
|
)}
|
||||||
|
{!hidePremiumProducts && (
|
||||||
|
<SectionRow>
|
||||||
|
<BundlePreviewBanner
|
||||||
|
onDisplayBundle={onSelectBundle}
|
||||||
|
category="premium"
|
||||||
|
i18n={i18n}
|
||||||
|
/>
|
||||||
|
</SectionRow>
|
||||||
|
)}
|
||||||
|
{courses &&
|
||||||
|
listedCourses &&
|
||||||
|
courses.length > 2 * numberOfItemsOnOneRow && (
|
||||||
|
<SectionRow>
|
||||||
|
<Line>
|
||||||
|
<GridList
|
||||||
|
cols={numberOfItemsOnOneRow}
|
||||||
|
style={styles.grid}
|
||||||
|
cellHeight="auto"
|
||||||
|
spacing={ITEMS_SPACING * 2}
|
||||||
|
>
|
||||||
|
{courses.slice(2 * numberOfItemsOnOneRow).map(course => {
|
||||||
|
const completion = getCourseCompletion(course.id);
|
||||||
|
const courseListingData = listedCourses.find(
|
||||||
|
listedCourse => listedCourse.id === course.id
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<GridListTile key={course.id}>
|
||||||
|
<CourseCard
|
||||||
|
course={course}
|
||||||
|
courseListingData={courseListingData}
|
||||||
|
completion={completion}
|
||||||
|
onClick={() => {
|
||||||
|
onSelectCourse(course.id);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</GridListTile>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</GridList>
|
||||||
|
</Line>
|
||||||
|
</SectionRow>
|
||||||
|
)}
|
||||||
</SectionContainer>
|
</SectionContainer>
|
||||||
)}
|
)}
|
||||||
</I18n>
|
</I18n>
|
||||||
|
@@ -242,6 +242,7 @@ const MainPage = ({
|
|||||||
<SectionRow>
|
<SectionRow>
|
||||||
<BundlePreviewBanner
|
<BundlePreviewBanner
|
||||||
onDisplayBundle={onSelectBundle}
|
onDisplayBundle={onSelectBundle}
|
||||||
|
category="starter"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
/>
|
/>
|
||||||
</SectionRow>
|
</SectionRow>
|
||||||
|
@@ -162,6 +162,7 @@ export type Bundle = {|
|
|||||||
// If the bundle is archived, it will not be available for purchase anymore.
|
// If the bundle is archived, it will not be available for purchase anymore.
|
||||||
// But it will still be available for users who already purchased it.
|
// But it will still be available for users who already purchased it.
|
||||||
archivedAt?: string,
|
archivedAt?: string,
|
||||||
|
categories: string[],
|
||||||
longDescription: string,
|
longDescription: string,
|
||||||
longDescriptionByLocale: MessageByLocale,
|
longDescriptionByLocale: MessageByLocale,
|
||||||
previewImageUrls: Array<string>,
|
previewImageUrls: Array<string>,
|
||||||
|
Reference in New Issue
Block a user