diff --git a/newIDE/app/src/AssetStore/Bundles/BundleInformationPage.js b/newIDE/app/src/AssetStore/Bundles/BundleInformationPage.js index e7b90dc80411..1a26e544fa22 100644 --- a/newIDE/app/src/AssetStore/Bundles/BundleInformationPage.js +++ b/newIDE/app/src/AssetStore/Bundles/BundleInformationPage.js @@ -85,9 +85,11 @@ type Props = {| ) => void, onCourseOpen: CourseListingData => void, courses: ?Array, - receivedCourses: ?Array, getCourseCompletion: (courseId: string) => CourseCompletion | null, noPadding?: boolean, + noActions?: boolean, + simpleCheckout?: boolean, + onPurchaseDone?: () => void, |}; const BundleInformationPage = ({ @@ -99,9 +101,11 @@ const BundleInformationPage = ({ onBundleOpen, onCourseOpen, courses, - receivedCourses, getCourseCompletion, noPadding, + noActions, + simpleCheckout, + onPurchaseDone, }: Props) => { const { windowSize, isLandscape, isMobile } = useResponsiveWindowSize(); const { bundleListingDatas } = React.useContext(BundleStoreContext); // If archived, should use the one passed. @@ -175,6 +179,7 @@ const BundleInformationPage = ({ onBundleOpen, onCourseOpen, discountedPrice: true, + disabled: noActions, }) : null, [ @@ -190,6 +195,7 @@ const BundleInformationPage = ({ onGameTemplateOpen, onBundleOpen, onCourseOpen, + noActions, ] ); @@ -311,6 +317,8 @@ const BundleInformationPage = ({ bundleListingData={bundleListingData} bundle={bundle} i18n={i18n} + simpleCheckout={simpleCheckout} + onPurchaseDone={onPurchaseDone} /> @@ -339,6 +347,7 @@ const BundleInformationPage = ({ onCourseOpen(courseListingData); }} discountedPrice + disabled={noActions} /> ); diff --git a/newIDE/app/src/AssetStore/Bundles/BundlePageHeader.js b/newIDE/app/src/AssetStore/Bundles/BundlePageHeader.js index dd6d3001d265..28194fd80a53 100644 --- a/newIDE/app/src/AssetStore/Bundles/BundlePageHeader.js +++ b/newIDE/app/src/AssetStore/Bundles/BundlePageHeader.js @@ -68,6 +68,8 @@ type Props = {| bundle: Bundle, simulateAppStoreProduct?: boolean, i18n: I18nType, + simpleCheckout?: boolean, + onPurchaseDone?: () => void, |}; const BundlePageHeader = ({ @@ -75,6 +77,8 @@ const BundlePageHeader = ({ bundleListingData, simulateAppStoreProduct, i18n, + simpleCheckout, + onPurchaseDone, }: Props) => { const { privateGameTemplateListingDatas } = React.useContext( PrivateGameTemplateStoreContext @@ -467,6 +471,7 @@ const BundlePageHeader = ({ isAlreadyReceived={isAlreadyReceived} onClickBuy={onClickBuy} onClickBuyWithCredits={() => {}} + fullWidth customLabel={ Buy now and save{' '} @@ -502,6 +507,8 @@ const BundlePageHeader = ({ bundleListingData={purchasingBundleListingData} usageType="default" onClose={() => setPurchasingBundleListingData(null)} + simpleCheckout={simpleCheckout} + onPurchaseDone={onPurchaseDone} /> )} {isRedemptionCodesDialogOpen && ( diff --git a/newIDE/app/src/AssetStore/Bundles/BundlePurchaseDialog.js b/newIDE/app/src/AssetStore/Bundles/BundlePurchaseDialog.js index 154c01801e19..b739c25ffee4 100644 --- a/newIDE/app/src/AssetStore/Bundles/BundlePurchaseDialog.js +++ b/newIDE/app/src/AssetStore/Bundles/BundlePurchaseDialog.js @@ -1,7 +1,10 @@ // @flow import * as React from 'react'; import { t, Trans } from '@lingui/macro'; -import { type BundleListingData } from '../../Utils/GDevelopServices/Shop'; +import { + getStripeCheckoutUrl, + type BundleListingData, +} from '../../Utils/GDevelopServices/Shop'; import Dialog, { DialogPrimaryButton } from '../../UI/Dialog'; import AuthenticatedUserContext from '../../Profile/AuthenticatedUserContext'; import CreateProfile from '../../Profile/CreateProfile'; @@ -28,6 +31,8 @@ type Props = {| usageType: string, onClose: () => void, simulateAppStoreProduct?: boolean, + simpleCheckout?: boolean, + onPurchaseDone?: () => void, |}; const BundlePurchaseDialog = ({ @@ -35,6 +40,8 @@ const BundlePurchaseDialog = ({ usageType, onClose, simulateAppStoreProduct, + simpleCheckout, + onPurchaseDone, }: Props) => { const { profile, @@ -91,13 +98,21 @@ const BundlePurchaseDialog = ({ // Purchase with web. try { setIsPurchasing(true); - const checkoutUrl = getPurchaseCheckoutUrl({ - productId: bundleListingData.id, - priceName: price.name, - userId: profile.id, - userEmail: profile.email, - ...(password ? { password } : undefined), - }); + const checkoutUrl = simpleCheckout + ? getStripeCheckoutUrl({ + productId: bundleListingData.id, + priceName: price.name, + userId: profile.id, + userEmail: profile.email, + ...(password ? { password } : undefined), + }) + : getPurchaseCheckoutUrl({ + productId: bundleListingData.id, + priceName: price.name, + userId: profile.id, + userEmail: profile.email, + ...(password ? { password } : undefined), + }); Window.openExternalURL(checkoutUrl); } catch (error) { const extractedStatusAndCode = extractGDevelopApiErrorStatusAndCode( @@ -155,6 +170,7 @@ const BundlePurchaseDialog = ({ // We found the purchase, the user has bought the bundle. // We do not close the dialog yet, as we need to trigger a refresh of the products received. await onPurchaseSuccessful(); + if (onPurchaseDone) onPurchaseDone(); } }; checkIfPurchaseIsDone(); @@ -164,6 +180,7 @@ const BundlePurchaseDialog = ({ bundlePurchases, bundleListingData, onPurchaseSuccessful, + onPurchaseDone, onRefreshBundlePurchases, ] ); diff --git a/newIDE/app/src/AssetStore/ProductPageHelper.js b/newIDE/app/src/AssetStore/ProductPageHelper.js index bed34ea86125..973f804b3665 100644 --- a/newIDE/app/src/AssetStore/ProductPageHelper.js +++ b/newIDE/app/src/AssetStore/ProductPageHelper.js @@ -356,6 +356,7 @@ export const getProductsIncludedInBundleTiles = ({ onBundleOpen, onCourseOpen, discountedPrice, + disabled, }: {| product: ?PrivateAssetPack | PrivateGameTemplate | Bundle | Course, productListingDatas: ?Array< @@ -381,6 +382,7 @@ export const getProductsIncludedInBundleTiles = ({ onBundleOpen?: (bundleListingData: BundleListingData) => void, onCourseOpen?: (courseListingData: CourseListingData) => void, discountedPrice?: boolean, + disabled?: boolean, |}): ?Array => { if (!product || !productListingDatas) return null; @@ -417,6 +419,7 @@ export const getProductsIncludedInBundleTiles = ({ } owned={isProductOwned} discountedPrice={discountedPrice} + disabled={disabled} /> ); } @@ -435,6 +438,7 @@ export const getProductsIncludedInBundleTiles = ({ onSelect={() => onPrivateAssetPackOpen(includedProductListingData)} owned={isProductOwned} discountedPrice={discountedPrice} + disabled={disabled} /> ); } @@ -453,6 +457,7 @@ export const getProductsIncludedInBundleTiles = ({ onSelect={() => onBundleOpen(includedProductListingData)} owned={isProductOwned} discountedPrice={discountedPrice} + disabled={disabled} /> ); } @@ -471,6 +476,7 @@ export const getProductsIncludedInBundleTiles = ({ onSelect={() => onCourseOpen(includedProductListingData)} owned={isProductOwned} discountedPrice={discountedPrice} + disabled={disabled} /> ); } @@ -577,6 +583,7 @@ export const PurchaseProductButtons = < onClickBuy, onClickBuyWithCredits, customLabel, + fullWidth, }: {| productListingData: T, selectedUsageType: string, @@ -587,6 +594,7 @@ export const PurchaseProductButtons = < onClickBuy: () => void | Promise, onClickBuyWithCredits?: () => void | Promise, customLabel?: React.Node, + fullWidth?: boolean, |}) => { const { authenticated } = React.useContext(AuthenticatedUserContext); const shouldUseOrSimulateAppStoreProduct = @@ -643,6 +651,15 @@ export const PurchaseProductButtons = < )} + ) : fullWidth && !creditPrice ? ( + Buy for {formattedProductPriceText}} + onClick={onClickBuy} + id={`buy-${productType}`} + size="medium" + fullWidth={fullWidth} + /> ) : ( {creditPrice && ( diff --git a/newIDE/app/src/AssetStore/index.js b/newIDE/app/src/AssetStore/index.js index 58cdecc9a6cb..fccb69be29b3 100644 --- a/newIDE/app/src/AssetStore/index.js +++ b/newIDE/app/src/AssetStore/index.js @@ -80,7 +80,6 @@ type Props = {| ) => void, onOpenProfile?: () => void, courses?: ?Array, - receivedCourses?: ?Array, onCourseOpen?: (courseId: string) => void, getSubscriptionPlansWithPricingSystems?: () => Array | null, getCourseCompletion?: (courseId: string) => CourseCompletion | null, @@ -125,7 +124,6 @@ export const AssetStore = React.forwardRef( onOpenPrivateGameTemplateListingData, onOpenProfile, courses, - receivedCourses, onCourseOpen, getSubscriptionPlansWithPricingSystems, getCourseCompletion, @@ -975,7 +973,6 @@ export const AssetStore = React.forwardRef( getSubscriptionPlansWithPricingSystems } courses={courses} - receivedCourses={receivedCourses} getCourseCompletion={getCourseCompletion} /> ) : null} diff --git a/newIDE/app/src/MainFrame/EditorContainers/HomePage/LearnSection/CourseCard.js b/newIDE/app/src/MainFrame/EditorContainers/HomePage/LearnSection/CourseCard.js index c55b3d673347..c93e5981c2bd 100644 --- a/newIDE/app/src/MainFrame/EditorContainers/HomePage/LearnSection/CourseCard.js +++ b/newIDE/app/src/MainFrame/EditorContainers/HomePage/LearnSection/CourseCard.js @@ -132,6 +132,7 @@ type Props = {| courseListingData: ?CourseListingData, onClick?: () => void, discountedPrice?: boolean, + disabled?: boolean, |}; const CourseCard = ({ @@ -140,6 +141,7 @@ const CourseCard = ({ courseListingData, onClick, discountedPrice, + disabled, }: Props) => { const gdevelopTheme = React.useContext(GDevelopThemeContext); const specializationConfig = getSpecializationConfig( @@ -148,8 +150,8 @@ const CourseCard = ({ return ( {({ i18n }) => ( - - {course && courseListingData && onClick ? ( + + {course && courseListingData ? (
void, onSelectExampleShortHeader: (exampleShortHeader: ExampleShortHeader) => void, getSubscriptionPlansWithPricingSystems: () => Array | null, - receivedCourses: ?Array, initialBundleUserFriendlySlug: ?string, initialBundleCategory: ?string, clearInitialBundleValues: () => void, @@ -114,7 +113,6 @@ const LearnSection = ({ onSelectPrivateGameTemplateListingData, onSelectExampleShortHeader, getSubscriptionPlansWithPricingSystems, - receivedCourses, initialBundleUserFriendlySlug, initialBundleCategory, clearInitialBundleValues, @@ -296,7 +294,6 @@ const LearnSection = ({ onBundleOpen={onOpenBundle} onCourseOpen={onOpenCourse} courses={courses} - receivedCourses={receivedCourses} getCourseCompletion={getCourseCompletion} /> ); diff --git a/newIDE/app/src/MainFrame/EditorContainers/HomePage/StoreSection/index.js b/newIDE/app/src/MainFrame/EditorContainers/HomePage/StoreSection/index.js index d779b872729f..28b27f9c6dab 100644 --- a/newIDE/app/src/MainFrame/EditorContainers/HomePage/StoreSection/index.js +++ b/newIDE/app/src/MainFrame/EditorContainers/HomePage/StoreSection/index.js @@ -27,7 +27,6 @@ type Props = {| onExtensionInstalled: (extensionNames: Array) => void, getSubscriptionPlansWithPricingSystems: () => Array | null, onCourseOpen: (courseId: string) => void, - receivedCourses?: ?Array, courses?: ?Array, getCourseCompletion: (courseId: string) => CourseCompletion | null, |}; @@ -40,7 +39,6 @@ const StoreSection = ({ onExtensionInstalled, onCourseOpen, courses, - receivedCourses, getSubscriptionPlansWithPricingSystems, getCourseCompletion, }: Props) => { @@ -98,7 +96,6 @@ const StoreSection = ({ displayPromotions onOpenProfile={onOpenProfile} courses={courses} - receivedCourses={receivedCourses} onCourseOpen={onCourseOpen} getSubscriptionPlansWithPricingSystems={ getSubscriptionPlansWithPricingSystems diff --git a/newIDE/app/src/MainFrame/EditorContainers/HomePage/index.js b/newIDE/app/src/MainFrame/EditorContainers/HomePage/index.js index 3357b8f97e99..27505d8d29ce 100644 --- a/newIDE/app/src/MainFrame/EditorContainers/HomePage/index.js +++ b/newIDE/app/src/MainFrame/EditorContainers/HomePage/index.js @@ -44,6 +44,7 @@ import PreferencesContext from '../../Preferences/PreferencesContext'; import useSubscriptionPlans from '../../../Utils/UseSubscriptionPlans'; import { BundleStoreContext } from '../../../AssetStore/Bundles/BundleStoreContext'; import { type CreateProjectResult } from '../../../Utils/UseCreateProject'; +import { CreditsPackageStoreContext } from '../../../AssetStore/CreditsPackages/CreditsPackageStoreContext'; const getRequestedTab = (routeArguments: RouteArguments): HomeTab | null => { if ( @@ -236,6 +237,9 @@ export const HomePage = React.memo( fetchGameTemplates, shop: { setInitialGameTemplateUserFriendlySlug }, } = React.useContext(PrivateGameTemplateStoreContext); + const { fetchCreditsPackages } = React.useContext( + CreditsPackageStoreContext + ); const [openedGameId, setOpenedGameId] = React.useState(null); const { games, @@ -413,12 +417,14 @@ export const HomePage = React.memo( fetchGameTemplates(); fetchTutorials(); fetchBundles(); + fetchCreditsPackages(); }, [ fetchExamplesAndFilters, fetchTutorials, fetchGameTemplates, fetchBundles, + fetchCreditsPackages, ] ); @@ -640,11 +646,6 @@ export const HomePage = React.memo( getSubscriptionPlansWithPricingSystems={ getSubscriptionPlansWithPricingSystems } - receivedCourses={ - courses - ? courses.filter(course => !course.isLocked) - : undefined - } clearInitialBundleValues={() => { setInitialBundleUserFriendlySlugForLearn(null); setInitialBundleCategoryForLearn(null); @@ -673,11 +674,6 @@ export const HomePage = React.memo( onSelectCourse(courseId); setActiveTab('learn'); }} - receivedCourses={ - courses - ? courses.filter(course => !course.isLocked) - : undefined - } courses={courses} getCourseCompletion={getCourseCompletion} getSubscriptionPlansWithPricingSystems={ diff --git a/newIDE/app/src/MainFrame/RouterContext.js b/newIDE/app/src/MainFrame/RouterContext.js index ab26411a59bd..aab5151d005a 100644 --- a/newIDE/app/src/MainFrame/RouterContext.js +++ b/newIDE/app/src/MainFrame/RouterContext.js @@ -13,7 +13,8 @@ export type Route = | 'create' // New way of opening the build section | 'education' | 'learn' - | 'play'; + | 'play' + | 'standalone'; type RouteKey = | 'initial-dialog' | 'game-id' diff --git a/newIDE/app/src/MainFrame/StandAloneDialog.js b/newIDE/app/src/MainFrame/StandAloneDialog.js new file mode 100644 index 000000000000..802fb8cfe365 --- /dev/null +++ b/newIDE/app/src/MainFrame/StandAloneDialog.js @@ -0,0 +1,120 @@ +// @flow +import { Trans } from '@lingui/macro'; + +import React from 'react'; +import Dialog from '../UI/Dialog'; +import PlaceholderLoader from '../UI/PlaceholderLoader'; +import ErrorBoundary from '../UI/ErrorBoundary'; +import RouterContext from './RouterContext'; +import { BundleStoreContext } from '../AssetStore/Bundles/BundleStoreContext'; +import { type BundleListingData } from '../Utils/GDevelopServices/Shop'; +import { getBundleListingDataFromCategory } from '../AssetStore/AssetStoreUtils'; +import { sendBundleInformationOpened } from '../Utils/Analytics/EventSender'; +import BundleInformationPage from '../AssetStore/Bundles/BundleInformationPage'; +import useCourses from './EditorContainers/HomePage/UseCourses'; +import useSubscriptionPlans from '../Utils/UseSubscriptionPlans'; +import AuthenticatedUserContext from '../Profile/AuthenticatedUserContext'; + +type Props = {| + // This dialog is not meant to be closed, but in case of an error, we provide a way to close it. + onClose: () => void, +|}; + +const StandaloneDialog = ({ onClose }: Props) => { + const authenticatedUser = React.useContext(AuthenticatedUserContext); + const { + routeArguments, + removeRouteArguments, + navigateToRoute, + } = React.useContext(RouterContext); + const { getSubscriptionPlansWithPricingSystems } = useSubscriptionPlans({ + authenticatedUser, + includeLegacy: false, + }); + const [ + selectedBundleListingData, + setSelectedBundleListingData, + ] = React.useState(null); + const { courses, getCourseCompletion } = useCourses(); + const { bundleListingDatas } = React.useContext(BundleStoreContext); + React.useEffect( + () => { + const bundleCategory = routeArguments['bundle-category']; + if (!bundleCategory || !bundleListingDatas) { + return; + } + + let bundleListingData: ?BundleListingData = null; + // Open the information page of a the bundle. + if (bundleCategory) { + bundleListingData = getBundleListingDataFromCategory({ + bundleListingDatas, + category: bundleCategory, + }); + } + + if (!bundleListingData) { + onClose(); + return; + } + + const priceForUsageType = bundleListingData.prices.find( + price => price.usageType === 'default' + ); + sendBundleInformationOpened({ + bundleName: bundleListingData.name, + bundleId: bundleListingData.id, + source: 'web-link', + priceValue: priceForUsageType && priceForUsageType.value, + priceCurrency: priceForUsageType && priceForUsageType.currency, + }); + setSelectedBundleListingData(bundleListingData); + removeRouteArguments(['bundle-category']); + }, + [bundleListingDatas, routeArguments, onClose, removeRouteArguments] + ); + + return ( + + {!selectedBundleListingData ? ( + + ) : ( + {}} + onGameTemplateOpen={() => {}} + onBundleOpen={() => {}} + onCourseOpen={() => {}} + courses={courses} + getCourseCompletion={getCourseCompletion} + noActions + simpleCheckout + onPurchaseDone={() => { + navigateToRoute('learn', { + bundle: selectedBundleListingData.id, + }); + onClose(); + }} + /> + )} + + ); +}; + +const StandaloneDialogWithErrorBoundary = (props: Props) => ( + About dialog} scope="about"> + + +); + +export default StandaloneDialogWithErrorBoundary; diff --git a/newIDE/app/src/MainFrame/index.js b/newIDE/app/src/MainFrame/index.js index b6642cbe7258..82fa9c9f95c9 100644 --- a/newIDE/app/src/MainFrame/index.js +++ b/newIDE/app/src/MainFrame/index.js @@ -194,12 +194,13 @@ import { QuickCustomizationDialog } from '../QuickCustomization/QuickCustomizati import { type ObjectWithContext } from '../ObjectsList/EnumerateObjects'; import useGamesList from '../GameDashboard/UseGamesList'; import useCapturesManager from './UseCapturesManager'; -import useHomepageWitchForRouting from './UseHomepageWitchForRouting'; +import useOpenPageForRouting from './useOpenPageForRouting'; import RobotIcon from '../ProjectCreation/RobotIcon'; import PublicProfileContext from '../Profile/PublicProfileContext'; import { useGamesPlatformFrame } from './EditorContainers/HomePage/PlaySection/UseGamesPlatformFrame'; import { useExtensionLoadErrorDialog } from '../Utils/UseExtensionLoadErrorDialog'; import { PanesContainer } from './PanesContainer'; +import StandaloneDialog from './StandAloneDialog'; const GD_STARTUP_TIMES = global.GD_STARTUP_TIMES || []; @@ -402,6 +403,10 @@ const MainFrame = (props: Props) => { shareDialogInitialTab, setShareDialogInitialTab, ] = React.useState(null); + const [ + standaloneDialogOpen, + setStandaloneDialogOpen, + ] = React.useState(false); const { showConfirmation, showAlert } = useAlertDialog(); const preferences = React.useContext(PreferencesContext); const { setHasProjectOpened } = preferences; @@ -2086,8 +2091,16 @@ const MainFrame = (props: Props) => { setShareDialogOpen(false); }, []); - const { navigateToRoute } = useHomepageWitchForRouting({ + const openStandaloneDialog = React.useCallback( + () => { + setStandaloneDialogOpen(true); + }, + [setStandaloneDialogOpen] + ); + + const { navigateToRoute } = useOpenPageForRouting({ openHomePage, + openStandaloneDialog, closeDialogs: closeDialogsToOpenHomePage, }); @@ -4244,7 +4257,9 @@ const MainFrame = (props: Props) => { onClose={() => setDiagnosticReportDialogOpen(false)} /> )} - + {standaloneDialogOpen && ( + setStandaloneDialogOpen(false)} /> + )} {quickCustomizationDialogOpenedFromGameId && currentProject && ( void, + openStandaloneDialog: () => void, closeDialogs: () => void, |}) => { - const { navigateToRoute, routeArguments } = React.useContext(RouterContext); + const { + navigateToRoute, + removeRouteArguments, + routeArguments, + } = React.useContext(RouterContext); // Open the homepage if not already open and close some dialogs. React.useEffect( @@ -37,12 +45,24 @@ const useHomepageWitchForRouting = ({ closeDialogs(); openHomePage(); } + + if (initialDialog === standaloneRoute) { + closeDialogs(); + openStandaloneDialog(); + removeRouteArguments(['initial-dialog']); + } }, - [routeArguments, openHomePage, closeDialogs] + [ + routeArguments, + openHomePage, + closeDialogs, + openStandaloneDialog, + removeRouteArguments, + ] ); return { navigateToRoute, }; }; -export default useHomepageWitchForRouting; +export default useOpenPageForRouting; diff --git a/newIDE/app/src/UI/Dialog.js b/newIDE/app/src/UI/Dialog.js index 9af4ae0137f8..81f08740835c 100644 --- a/newIDE/app/src/UI/Dialog.js +++ b/newIDE/app/src/UI/Dialog.js @@ -217,6 +217,7 @@ type DialogProps = {| minHeight?: 'sm' | 'lg', fullHeight?: boolean, fullscreen?: 'never-even-on-mobile' | 'always-even-on-desktop', + noPadding?: boolean, actionsFullWidthOnMobile?: boolean, // Useful when the content of the dialog can change and we want to avoid layout shifts. forceScrollVisible?: boolean, @@ -246,6 +247,7 @@ const DialogWithoutWindowSizeProvider = ({ cannotBeDismissed, exceptionallyStillAllowRenderingInstancesEditors, fullscreen, + noPadding, actionsFullWidthOnMobile, forceScrollVisible, topBackgroundSrc, @@ -302,7 +304,7 @@ const DialogWithoutWindowSizeProvider = ({ : {}; const additionalPaddingStyle = { paddingTop: 0, // Let the title container handle the padding, or no padding if there is no title. - paddingBottom: hasActions ? 0 : dialogActionPadding, // Ensure the padding is here if there are no actions. + paddingBottom: hasActions || noPadding ? 0 : dialogActionPadding, // Ensure the padding is here if there are no actions. }; const contentStyle = { ...styles.dialogContent, @@ -313,7 +315,9 @@ const DialogWithoutWindowSizeProvider = ({ const dialogContainerStyle = { ...styles.dialogContainer, // Ensure we don't spread an object here, to avoid a styling bug when resizing. - margin: isFullScreen + margin: noPadding + ? 0 + : isFullScreen ? dialogSmallPadding : `${dialogTitlePadding}px ${dialogPaddingX}px ${dialogActionPadding}px ${dialogPaddingX}px`, }; diff --git a/newIDE/app/src/Utils/GDevelopServices/Shop.js b/newIDE/app/src/Utils/GDevelopServices/Shop.js index 6c03217800ef..e937b637ef89 100644 --- a/newIDE/app/src/Utils/GDevelopServices/Shop.js +++ b/newIDE/app/src/Utils/GDevelopServices/Shop.js @@ -474,6 +474,32 @@ export const getPurchaseCheckoutUrl = ({ return url.toString(); }; +export const getStripeCheckoutUrl = ({ + userId, + productId, + priceName, + userEmail, + password, +}: {| + userId: string, + productId: string, + priceName: string, + userEmail: string, + password?: string, +|}) => { + const url = new URL( + `${GDevelopShopApi.baseUrl}/purchase/action/redirect-to-stripe-checkout` + ); + + url.searchParams.set('productId', productId); + url.searchParams.set('priceName', priceName); + url.searchParams.set('userId', userId); + url.searchParams.set('customerEmail', userEmail); + if (password) url.searchParams.set('password', password); + + return url.toString(); +}; + // Helper to fetch a token for private game templates if needed, when moving or fetching resources. export const fetchTokenForPrivateGameTemplateAuthorizationIfNeeded = async ({ authenticatedUser, diff --git a/newIDE/app/src/stories/componentStories/HomePage/LearnSection.stories.js b/newIDE/app/src/stories/componentStories/HomePage/LearnSection.stories.js index cf9fdc7fcd6b..853f24931248 100644 --- a/newIDE/app/src/stories/componentStories/HomePage/LearnSection.stories.js +++ b/newIDE/app/src/stories/componentStories/HomePage/LearnSection.stories.js @@ -76,7 +76,6 @@ export const Default = () => ( )} onSelectExampleShortHeader={action('onSelectExampleShortHeader')} getSubscriptionPlansWithPricingSystems={() => null} - receivedCourses={[]} initialBundleCategory={null} initialBundleUserFriendlySlug={null} clearInitialBundleValues={() => {}} @@ -127,7 +126,6 @@ export const NotAuthenticated = () => ( )} onSelectExampleShortHeader={action('onSelectExampleShortHeader')} getSubscriptionPlansWithPricingSystems={() => null} - receivedCourses={[]} initialBundleCategory={null} initialBundleUserFriendlySlug={null} clearInitialBundleValues={() => {}} @@ -180,7 +178,6 @@ export const EducationSubscriber = () => ( )} onSelectExampleShortHeader={action('onSelectExampleShortHeader')} getSubscriptionPlansWithPricingSystems={() => null} - receivedCourses={[]} initialBundleCategory={null} initialBundleUserFriendlySlug={null} clearInitialBundleValues={() => {}} @@ -233,7 +230,6 @@ export const EducationTeacher = () => ( )} onSelectExampleShortHeader={action('onSelectExampleShortHeader')} getSubscriptionPlansWithPricingSystems={() => null} - receivedCourses={[]} initialBundleCategory={null} initialBundleUserFriendlySlug={null} clearInitialBundleValues={() => {}} @@ -280,7 +276,6 @@ export const LoadingTutorials = () => ( )} onSelectExampleShortHeader={action('onSelectExampleShortHeader')} getSubscriptionPlansWithPricingSystems={() => null} - receivedCourses={[]} initialBundleCategory={null} initialBundleUserFriendlySlug={null} clearInitialBundleValues={() => {}} @@ -326,7 +321,6 @@ export const LoadingCourses = () => ( )} onSelectExampleShortHeader={action('onSelectExampleShortHeader')} getSubscriptionPlansWithPricingSystems={() => null} - receivedCourses={[]} initialBundleCategory={null} initialBundleUserFriendlySlug={null} clearInitialBundleValues={() => {}}