mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
1 Commits
40c576bc2d
...
feat/impro
Author | SHA1 | Date | |
---|---|---|---|
![]() |
80a167e300 |
@@ -268,8 +268,9 @@ const BundlePageHeader = ({
|
||||
bundleId: bundle.id,
|
||||
bundleName: bundle.name,
|
||||
bundleTag: bundle.tag,
|
||||
currency: price ? price.currency : undefined,
|
||||
usageType: 'default',
|
||||
priceValue: price && price.value,
|
||||
priceCurrency: price && price.currency,
|
||||
});
|
||||
|
||||
setPurchasingBundleListingData(bundleListingData);
|
||||
|
@@ -157,10 +157,15 @@ export const BundleStoreStateProvider = ({
|
||||
});
|
||||
|
||||
if (bundleListingData) {
|
||||
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,
|
||||
});
|
||||
shopNavigationState.openBundleInformationPage({
|
||||
bundleListingData,
|
||||
|
@@ -186,10 +186,15 @@ const ExampleStore = ({
|
||||
)
|
||||
: [],
|
||||
onSelectPrivateGameTemplateListingData: privateGameTemplateListingData => {
|
||||
const priceForUsageType = privateGameTemplateListingData.prices.find(
|
||||
price => price.usageType === 'default'
|
||||
);
|
||||
sendGameTemplateInformationOpened({
|
||||
gameTemplateName: privateGameTemplateListingData.name,
|
||||
gameTemplateId: privateGameTemplateListingData.id,
|
||||
source: 'examples-list',
|
||||
priceValue: priceForUsageType && priceForUsageType.value,
|
||||
priceCurrency: priceForUsageType && priceForUsageType.currency,
|
||||
});
|
||||
onSelectPrivateGameTemplateListingData(
|
||||
privateGameTemplateListingData
|
||||
|
@@ -455,7 +455,8 @@ const PrivateAssetPackInformationPage = ({
|
||||
assetPackTag: assetPack.tag,
|
||||
assetPackKind: 'private',
|
||||
usageType: selectedUsageType,
|
||||
currency: price ? price.currency : undefined,
|
||||
priceValue: price && price.value,
|
||||
priceCurrency: price && price.currency,
|
||||
});
|
||||
|
||||
setPurchasingPrivateAssetPackListingData(privateAssetPackListingData);
|
||||
@@ -487,15 +488,6 @@ const PrivateAssetPackInformationPage = ({
|
||||
return;
|
||||
}
|
||||
|
||||
sendAssetPackBuyClicked({
|
||||
assetPackId: assetPack.id,
|
||||
assetPackName: assetPack.name,
|
||||
assetPackTag: assetPack.tag,
|
||||
assetPackKind: 'private',
|
||||
currency: 'CREDITS',
|
||||
usageType: selectedUsageType,
|
||||
});
|
||||
|
||||
const currentCreditsAmount = limits.credits.userBalance.amount;
|
||||
const assetPackPriceForUsageType = privateAssetPackListingData.creditPrices.find(
|
||||
price => price.usageType === selectedUsageType
|
||||
@@ -508,6 +500,17 @@ const PrivateAssetPackInformationPage = ({
|
||||
return;
|
||||
}
|
||||
const assetPackCreditsAmount = assetPackPriceForUsageType.amount;
|
||||
|
||||
sendAssetPackBuyClicked({
|
||||
assetPackId: assetPack.id,
|
||||
assetPackName: assetPack.name,
|
||||
assetPackTag: assetPack.tag,
|
||||
assetPackKind: 'private',
|
||||
priceValue: assetPackCreditsAmount,
|
||||
priceCurrency: 'CREDITS',
|
||||
usageType: selectedUsageType,
|
||||
});
|
||||
|
||||
if (currentCreditsAmount < assetPackCreditsAmount) {
|
||||
openCreditsPackageDialog({
|
||||
missingCredits: assetPackCreditsAmount - currentCreditsAmount,
|
||||
|
@@ -355,8 +355,9 @@ const PrivateGameTemplateInformationPage = ({
|
||||
gameTemplateId: gameTemplate.id,
|
||||
gameTemplateName: gameTemplate.name,
|
||||
gameTemplateTag: gameTemplate.tag,
|
||||
currency: price ? price.currency : undefined,
|
||||
usageType: selectedUsageType,
|
||||
priceValue: price && price.value,
|
||||
priceCurrency: price && price.currency,
|
||||
});
|
||||
|
||||
setPurchasingPrivateGameTemplateListingData(
|
||||
@@ -392,14 +393,6 @@ const PrivateGameTemplateInformationPage = ({
|
||||
return;
|
||||
}
|
||||
|
||||
sendGameTemplateBuyClicked({
|
||||
gameTemplateId: gameTemplate.id,
|
||||
gameTemplateName: gameTemplate.name,
|
||||
gameTemplateTag: gameTemplate.tag,
|
||||
usageType: selectedUsageType,
|
||||
currency: 'CREDITS',
|
||||
});
|
||||
|
||||
const currentCreditsAmount = limits.credits.userBalance.amount;
|
||||
const gameTemplatePriceForUsageType = privateGameTemplateListingData.creditPrices.find(
|
||||
price => price.usageType === selectedUsageType
|
||||
@@ -412,6 +405,16 @@ const PrivateGameTemplateInformationPage = ({
|
||||
return;
|
||||
}
|
||||
const gameTemplateCreditsAmount = gameTemplatePriceForUsageType.amount;
|
||||
|
||||
sendGameTemplateBuyClicked({
|
||||
gameTemplateId: gameTemplate.id,
|
||||
gameTemplateName: gameTemplate.name,
|
||||
gameTemplateTag: gameTemplate.tag,
|
||||
usageType: selectedUsageType,
|
||||
priceValue: gameTemplateCreditsAmount,
|
||||
priceCurrency: 'CREDITS',
|
||||
});
|
||||
|
||||
if (currentCreditsAmount < gameTemplateCreditsAmount) {
|
||||
openCreditsPackageDialog({
|
||||
missingCredits: gameTemplateCreditsAmount - currentCreditsAmount,
|
||||
|
@@ -214,10 +214,15 @@ export const PrivateGameTemplateStoreStateProvider = ({
|
||||
);
|
||||
|
||||
if (privateGameTemplateListingData) {
|
||||
const priceForUsageType = privateGameTemplateListingData.prices.find(
|
||||
price => price.usageType === 'default'
|
||||
);
|
||||
sendGameTemplateInformationOpened({
|
||||
gameTemplateName: privateGameTemplateListingData.name,
|
||||
gameTemplateId: privateGameTemplateListingData.id,
|
||||
source: 'web-link',
|
||||
priceValue: priceForUsageType && priceForUsageType.value,
|
||||
priceCurrency: priceForUsageType && priceForUsageType.currency,
|
||||
});
|
||||
shopNavigationState.openPrivateGameTemplateInformationPage({
|
||||
privateGameTemplateListingData,
|
||||
|
@@ -435,10 +435,16 @@ export const AssetStore = React.forwardRef<Props, AssetStoreInterface>(
|
||||
|
||||
if (!receivedAssetPack || (options && options.forceProductPage)) {
|
||||
// The user has not received the pack, open the page to buy it.
|
||||
const priceForUsageType = privateAssetPackListingData.prices.find(
|
||||
price => price.usageType === 'default'
|
||||
);
|
||||
|
||||
sendAssetPackInformationOpened({
|
||||
assetPackName: privateAssetPackListingData.name,
|
||||
assetPackId: privateAssetPackListingData.id,
|
||||
assetPackKind: 'private',
|
||||
priceValue: priceForUsageType && priceForUsageType.value,
|
||||
priceCurrency: priceForUsageType && priceForUsageType.currency,
|
||||
});
|
||||
saveScrollPosition();
|
||||
shopNavigationState.openPrivateAssetPackInformationPage({
|
||||
@@ -489,10 +495,15 @@ export const AssetStore = React.forwardRef<Props, AssetStoreInterface>(
|
||||
|
||||
const selectPrivateGameTemplate = React.useCallback(
|
||||
(privateGameTemplateListingData: PrivateGameTemplateListingData) => {
|
||||
const priceForUsageType = privateGameTemplateListingData.prices.find(
|
||||
price => price.usageType === 'default'
|
||||
);
|
||||
sendGameTemplateInformationOpened({
|
||||
gameTemplateName: privateGameTemplateListingData.name,
|
||||
gameTemplateId: privateGameTemplateListingData.id,
|
||||
source: 'store',
|
||||
priceValue: priceForUsageType && priceForUsageType.value,
|
||||
priceCurrency: priceForUsageType && priceForUsageType.currency,
|
||||
});
|
||||
saveScrollPosition();
|
||||
shopNavigationState.openPrivateGameTemplateInformationPage({
|
||||
@@ -506,10 +517,15 @@ export const AssetStore = React.forwardRef<Props, AssetStoreInterface>(
|
||||
|
||||
const selectBundle = React.useCallback(
|
||||
(bundleListingData: BundleListingData) => {
|
||||
const priceForUsageType = bundleListingData.prices.find(
|
||||
price => price.usageType === 'default'
|
||||
);
|
||||
sendBundleInformationOpened({
|
||||
bundleName: bundleListingData.name,
|
||||
bundleId: bundleListingData.id,
|
||||
source: 'store',
|
||||
priceValue: priceForUsageType && priceForUsageType.value,
|
||||
priceCurrency: priceForUsageType && priceForUsageType.currency,
|
||||
});
|
||||
saveScrollPosition();
|
||||
shopNavigationState.openBundleInformationPage({
|
||||
@@ -523,10 +539,15 @@ export const AssetStore = React.forwardRef<Props, AssetStoreInterface>(
|
||||
|
||||
const selectCourse = React.useCallback(
|
||||
(courseListingData: CourseListingData) => {
|
||||
const priceForUsageType = courseListingData.prices.find(
|
||||
price => price.usageType === 'default'
|
||||
);
|
||||
sendCourseInformationOpened({
|
||||
courseName: courseListingData.name,
|
||||
courseId: courseListingData.id,
|
||||
source: 'store',
|
||||
priceValue: priceForUsageType && priceForUsageType.value,
|
||||
priceCurrency: priceForUsageType && priceForUsageType.currency,
|
||||
});
|
||||
if (onCourseOpen) onCourseOpen(courseListingData.id);
|
||||
},
|
||||
|
@@ -7,7 +7,10 @@ import {
|
||||
type Course,
|
||||
type CourseChapter,
|
||||
} from '../../../../Utils/GDevelopServices/Asset';
|
||||
import type { BundleListingData } from '../../../../Utils/GDevelopServices/Shop';
|
||||
import {
|
||||
type BundleListingData,
|
||||
type CourseListingData,
|
||||
} from '../../../../Utils/GDevelopServices/Shop';
|
||||
import CoursePreviewBanner from '../../../../Course/CoursePreviewBanner';
|
||||
import type { CourseCompletion, CourseChapterCompletion } from '../UseCourses';
|
||||
import { Line } from '../../../../UI/Grid';
|
||||
@@ -36,7 +39,7 @@ const styles = {
|
||||
type Props = {|
|
||||
onBack: () => void,
|
||||
courses: ?Array<Course>,
|
||||
onSelectCourse: (courseId: string) => void,
|
||||
onSelectCourse: (courseListingData: CourseListingData) => void,
|
||||
onSelectBundle: (bundleListingData: BundleListingData) => void,
|
||||
previewedCourse: ?Course,
|
||||
getCourseChapters: (courseId: string) => ?Array<CourseChapter>,
|
||||
@@ -83,8 +86,12 @@ const CoursesPage = ({
|
||||
getCourseCompletion={getCourseCompletion}
|
||||
getCourseChapterCompletion={getCourseChapterCompletion}
|
||||
onDisplayCourse={() => {
|
||||
if (!previewedCourse) return;
|
||||
onSelectCourse(previewedCourse.id);
|
||||
if (!previewedCourse || !listedCourses) return;
|
||||
const courseListingData = listedCourses.find(
|
||||
listedCourse => listedCourse.id === previewedCourse.id
|
||||
);
|
||||
if (!courseListingData) return;
|
||||
onSelectCourse(courseListingData);
|
||||
}}
|
||||
/>
|
||||
</SectionRow>
|
||||
@@ -109,7 +116,8 @@ const CoursesPage = ({
|
||||
courseListingData={courseListingData}
|
||||
completion={completion}
|
||||
onClick={() => {
|
||||
onSelectCourse(course.id);
|
||||
if (!courseListingData) return;
|
||||
onSelectCourse(courseListingData);
|
||||
}}
|
||||
/>
|
||||
</GridListTile>
|
||||
@@ -159,7 +167,8 @@ const CoursesPage = ({
|
||||
courseListingData={courseListingData}
|
||||
completion={completion}
|
||||
onClick={() => {
|
||||
onSelectCourse(course.id);
|
||||
if (!courseListingData) return;
|
||||
onSelectCourse(courseListingData);
|
||||
}}
|
||||
/>
|
||||
</GridListTile>
|
||||
@@ -201,7 +210,8 @@ const CoursesPage = ({
|
||||
courseListingData={courseListingData}
|
||||
completion={completion}
|
||||
onClick={() => {
|
||||
onSelectCourse(course.id);
|
||||
if (!courseListingData) return;
|
||||
onSelectCourse(courseListingData);
|
||||
}}
|
||||
/>
|
||||
</GridListTile>
|
||||
|
@@ -34,6 +34,7 @@ import ExampleStore from '../../../../AssetStore/ExampleStore';
|
||||
import {
|
||||
type PrivateGameTemplateListingData,
|
||||
type BundleListingData,
|
||||
type CourseListingData,
|
||||
} from '../../../../Utils/GDevelopServices/Shop';
|
||||
import { type ExampleShortHeader } from '../../../../Utils/GDevelopServices/Example';
|
||||
import Carousel from '../../../../UI/Carousel';
|
||||
@@ -62,7 +63,7 @@ type Props = {|
|
||||
onSelectCategory: (category: LearnCategory) => void,
|
||||
selectInAppTutorial: (tutorialId: string) => void,
|
||||
courses: ?(Course[]),
|
||||
onSelectCourse: (courseId: string) => void,
|
||||
onSelectCourse: (courseListingData: CourseListingData) => void,
|
||||
onSelectBundle: (bundleListingData: BundleListingData) => void,
|
||||
getCourseCompletion: (courseId: string) => CourseCompletion | null,
|
||||
getCourseChapterCompletion: (
|
||||
@@ -216,7 +217,8 @@ const MainPage = ({
|
||||
courseListingData={courseListingData}
|
||||
completion={completion}
|
||||
onClick={() => {
|
||||
onSelectCourse(course.id);
|
||||
if (!courseListingData) return;
|
||||
onSelectCourse(courseListingData);
|
||||
}}
|
||||
/>
|
||||
</GridListTile>
|
||||
|
@@ -21,6 +21,8 @@ import { selectMessageByLocale } from '../../../../Utils/i18n/MessageByLocale';
|
||||
import Text from '../../../../UI/Text';
|
||||
import { TutorialContext } from '../../../../Tutorial/TutorialContext';
|
||||
import PlaceholderLoader from '../../../../UI/PlaceholderLoader';
|
||||
import CourseStoreContext from '../../../../Course/CourseStoreContext';
|
||||
import { type CourseListingData } from '../../../../Utils/GDevelopServices/Shop';
|
||||
|
||||
const styles = {
|
||||
educationCurriculumTutorialContainer: {
|
||||
@@ -143,7 +145,7 @@ type Props = {|
|
||||
onBack: () => void,
|
||||
category: TutorialCategory,
|
||||
onOpenTemplateFromTutorial: string => Promise<void>,
|
||||
onSelectCourse: (courseId: string) => void,
|
||||
onSelectCourse: (courseListingData: CourseListingData) => void,
|
||||
|};
|
||||
|
||||
const TutorialsCategoryPage = ({
|
||||
@@ -152,6 +154,7 @@ const TutorialsCategoryPage = ({
|
||||
onOpenTemplateFromTutorial,
|
||||
onSelectCourse,
|
||||
}: Props) => {
|
||||
const { listedCourses } = React.useContext(CourseStoreContext);
|
||||
const { limits } = React.useContext(AuthenticatedUserContext);
|
||||
const { tutorials } = React.useContext(TutorialContext);
|
||||
const texts = TUTORIAL_CATEGORY_TEXTS[category];
|
||||
@@ -187,7 +190,14 @@ const TutorialsCategoryPage = ({
|
||||
<EducationCurriculum
|
||||
tutorials={filteredTutorials}
|
||||
onSelectTutorial={setSelectedTutorial}
|
||||
onSelectCourse={onSelectCourse}
|
||||
onSelectCourse={courseId => {
|
||||
if (!listedCourses) return;
|
||||
const courseListingData = listedCourses.find(
|
||||
course => course.id === courseId
|
||||
);
|
||||
if (!courseListingData) return;
|
||||
onSelectCourse(courseListingData);
|
||||
}}
|
||||
i18n={i18n}
|
||||
limits={limits}
|
||||
onOpenTemplateFromTutorial={onOpenTemplateFromTutorial}
|
||||
|
@@ -119,10 +119,15 @@ const LearnSection = ({
|
||||
|
||||
const onOpenBundle = React.useCallback(
|
||||
(bundleListingData: BundleListingData) => {
|
||||
const priceForUsageType = bundleListingData.prices.find(
|
||||
price => price.usageType === 'default'
|
||||
);
|
||||
sendBundleInformationOpened({
|
||||
bundleName: bundleListingData.name,
|
||||
bundleId: bundleListingData.id,
|
||||
source: 'learn',
|
||||
priceValue: priceForUsageType && priceForUsageType.value,
|
||||
priceCurrency: priceForUsageType && priceForUsageType.currency,
|
||||
});
|
||||
setSelectedBundleListingData(bundleListingData);
|
||||
},
|
||||
@@ -130,18 +135,27 @@ const LearnSection = ({
|
||||
);
|
||||
|
||||
const onOpenCourse = React.useCallback(
|
||||
(courseId: string | null) => {
|
||||
if (courseId && courses) {
|
||||
const course = courses.find(c => c.id === courseId);
|
||||
if (course && course.isLocked) {
|
||||
// Only send the event if the course is not owned.
|
||||
sendCourseInformationOpened({
|
||||
courseName: course.titleByLocale['en'],
|
||||
courseId: courseId,
|
||||
source: 'learn',
|
||||
});
|
||||
}
|
||||
(courseListingData: CourseListingData) => {
|
||||
const courseId = courseListingData.id;
|
||||
if (!courses) {
|
||||
return;
|
||||
}
|
||||
|
||||
const course = courses.find(c => c.id === courseId);
|
||||
if (course && course.isLocked) {
|
||||
const priceForUsageType = courseListingData.prices.find(
|
||||
price => price.usageType === 'default'
|
||||
);
|
||||
// Only send the event if the course is not owned.
|
||||
sendCourseInformationOpened({
|
||||
courseName: courseListingData.name,
|
||||
courseId: courseListingData.id,
|
||||
source: 'learn',
|
||||
priceValue: priceForUsageType && priceForUsageType.value,
|
||||
priceCurrency: priceForUsageType && priceForUsageType.currency,
|
||||
});
|
||||
}
|
||||
|
||||
onSelectCourse(courseId);
|
||||
},
|
||||
[onSelectCourse, courses]
|
||||
@@ -210,9 +224,7 @@ const LearnSection = ({
|
||||
}}
|
||||
onGameTemplateOpen={onSelectPrivateGameTemplateListingData}
|
||||
onBundleOpen={onOpenBundle}
|
||||
onCourseOpen={courseListingData => {
|
||||
onOpenCourse(courseListingData.id);
|
||||
}}
|
||||
onCourseOpen={onOpenCourse}
|
||||
courses={courses}
|
||||
receivedCourses={receivedCourses}
|
||||
getCourseCompletion={getCourseCompletion}
|
||||
|
@@ -405,8 +405,9 @@ const useCourses = () => {
|
||||
sendCourseBuyClicked({
|
||||
courseId: course.id,
|
||||
courseName: course.titleByLocale.en,
|
||||
currency: priceForUsageType ? priceForUsageType.currency : undefined,
|
||||
usageType: 'default',
|
||||
priceValue: priceForUsageType.value,
|
||||
priceCurrency: priceForUsageType.currency,
|
||||
});
|
||||
|
||||
setPurchasingCourseListingData(listedCourse);
|
||||
@@ -433,13 +434,6 @@ const useCourses = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
sendCourseBuyClicked({
|
||||
courseId: course.id,
|
||||
courseName: course.titleByLocale.en,
|
||||
currency: 'CREDITS',
|
||||
usageType: 'default',
|
||||
});
|
||||
|
||||
const currentCreditsAmount = limits.credits.userBalance.amount;
|
||||
const listedCourse = listedCourses.find(
|
||||
listedCourse => listedCourse.id === course.id
|
||||
@@ -458,6 +452,15 @@ const useCourses = () => {
|
||||
return;
|
||||
}
|
||||
const creditsAmount = priceForUsageType.amount;
|
||||
|
||||
sendCourseBuyClicked({
|
||||
courseId: course.id,
|
||||
courseName: course.titleByLocale.en,
|
||||
usageType: 'default',
|
||||
priceValue: creditsAmount,
|
||||
priceCurrency: 'CREDITS',
|
||||
});
|
||||
|
||||
if (currentCreditsAmount < creditsAmount) {
|
||||
openCreditsPackageDialog({
|
||||
missingCredits: creditsAmount - currentCreditsAmount,
|
||||
|
@@ -13,6 +13,8 @@ import { getBrowserLanguageOrLocale } from '../Language';
|
||||
import { type SubscriptionAnalyticsMetadata } from '../../Profile/Subscription/SubscriptionSuggestionContext';
|
||||
import optionalRequire from '../OptionalRequire';
|
||||
import Window from '../Window';
|
||||
import { isMobile, isNativeMobileApp } from '../Platform';
|
||||
import { retryIfFailed } from '../RetryIfFailed';
|
||||
const electron = optionalRequire('electron');
|
||||
|
||||
const isElectronApp = !!electron;
|
||||
@@ -26,6 +28,59 @@ let userIdentified = false;
|
||||
let posthogLastPropertiesSent = '';
|
||||
let currentlyRunningInAppTutorial = null;
|
||||
|
||||
let gdevelopEditorAnalytics: {|
|
||||
initialize: (rootElement: HTMLElement) => Promise<void>,
|
||||
identify: (
|
||||
userId: string,
|
||||
userProperties: { [string]: any }
|
||||
) => Promise<void>,
|
||||
trackEvent: (eventName: string, metadata: { [string]: any }) => Promise<void>,
|
||||
|} | null = null;
|
||||
let gdevelopEditorAnalyticsPromise: Promise<void> | null = null;
|
||||
|
||||
const ensureGDevelopEditorAnalyticsReady = async () => {
|
||||
if (gdevelopEditorAnalytics) {
|
||||
// Already loaded.
|
||||
return;
|
||||
}
|
||||
|
||||
if (gdevelopEditorAnalyticsPromise) {
|
||||
// Being loaded.
|
||||
return gdevelopEditorAnalyticsPromise;
|
||||
}
|
||||
|
||||
gdevelopEditorAnalyticsPromise = (async () => {
|
||||
try {
|
||||
// Load the library. If it fails, retry or throw so we can retry later.
|
||||
const module = await retryIfFailed(
|
||||
{ times: 2 },
|
||||
async () =>
|
||||
// $FlowExpectedError - Remote script cannot be found.
|
||||
(await import(/* webpackIgnore: true */ 'https://resources.gdevelop.io/a/gea.js'))
|
||||
.default
|
||||
);
|
||||
if (module) {
|
||||
await module.initialize({
|
||||
documentBody: document.body,
|
||||
isNativeMobileApp: isNativeMobileApp(),
|
||||
isElectronApp,
|
||||
isDev,
|
||||
isMobile: isMobile(),
|
||||
ideVersionWithHash: getIDEVersionWithHash(),
|
||||
});
|
||||
gdevelopEditorAnalytics = module;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error while loading GDevelop Editor Analytics:', error);
|
||||
} finally {
|
||||
// If loading fails, retry later.
|
||||
gdevelopEditorAnalyticsPromise = null;
|
||||
}
|
||||
})();
|
||||
|
||||
return gdevelopEditorAnalyticsPromise;
|
||||
};
|
||||
|
||||
export const setCurrentlyRunningInAppTutorial = (tutorial: string | null) =>
|
||||
(currentlyRunningInAppTutorial = tutorial);
|
||||
|
||||
@@ -55,24 +110,33 @@ const recordEvent = (name: string, metadata?: { [string]: any }) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!posthogLoaded || !userIdentified) {
|
||||
console.info(`App analytics not ready for an event - retrying in 2s.`);
|
||||
setTimeout(() => {
|
||||
console.info(
|
||||
`Retrying to send the app analytics event with name ${name}`
|
||||
);
|
||||
recordEvent(name, metadata);
|
||||
}, 2000);
|
||||
(() => {
|
||||
if (!posthogLoaded || !userIdentified) {
|
||||
console.info(`App analytics not ready for an event - retrying in 2s.`);
|
||||
setTimeout(() => {
|
||||
console.info(
|
||||
`Retrying to send the app analytics event with name ${name}`
|
||||
);
|
||||
recordEvent(name, metadata);
|
||||
}, 2000);
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
posthog.capture(name, {
|
||||
...metadata,
|
||||
isInAppTutorialRunning: currentlyRunningInAppTutorial,
|
||||
isInDesktopApp: isElectronApp,
|
||||
isInWebApp: !isElectronApp,
|
||||
});
|
||||
posthog.capture(name, {
|
||||
...metadata,
|
||||
isInAppTutorialRunning: currentlyRunningInAppTutorial,
|
||||
isInDesktopApp: isElectronApp,
|
||||
isInWebApp: !isElectronApp,
|
||||
});
|
||||
})();
|
||||
|
||||
(async () => {
|
||||
await ensureGDevelopEditorAnalyticsReady();
|
||||
if (gdevelopEditorAnalytics) {
|
||||
await gdevelopEditorAnalytics.trackEvent(name, metadata || {});
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -84,6 +148,10 @@ export const installAnalyticsEvents = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
ensureGDevelopEditorAnalyticsReady().catch(() => {
|
||||
// Will be retried when an event is sent.
|
||||
});
|
||||
|
||||
posthog.init('phc_yjTVz4BMHUOhCLBhVImjk3Jn1AjMCg808bxENY228qu', {
|
||||
api_host: 'https://app.posthog.com',
|
||||
loaded: () => {
|
||||
@@ -107,16 +175,6 @@ export const identifyUserForAnalytics = (
|
||||
return;
|
||||
}
|
||||
|
||||
if (!posthogLoaded) {
|
||||
console.info(`App analytics not ready - retrying in 2s.`);
|
||||
setTimeout(() => {
|
||||
console.info(`Retrying to update the user for app analytics.`);
|
||||
identifyUserForAnalytics(authenticatedUser);
|
||||
}, 2000);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const firebaseUser = authenticatedUser.firebaseUser;
|
||||
const profile = authenticatedUser.profile;
|
||||
const userPreferences = loadPreferencesFromLocalStorage();
|
||||
@@ -145,19 +203,39 @@ export const identifyUserForAnalytics = (
|
||||
hearFrom: profile ? profile.hearFrom : undefined,
|
||||
};
|
||||
|
||||
// Identify which user is using the app, after de-duplicating the call to
|
||||
// avoid useless calls.
|
||||
// This is so we can build stats on the used version, languages and usage
|
||||
// of GDevelop features.
|
||||
const stringifiedUserProperties = JSON.stringify(userProperties);
|
||||
if (stringifiedUserProperties !== posthogLastPropertiesSent) {
|
||||
// If the user is not logged in, identify the user by its anonymous UUID.
|
||||
// If the user is logged in, identify the user by its Firebase ID.
|
||||
const userId = firebaseUser ? firebaseUser.uid : getUserUUID();
|
||||
posthog.identify(userId, userProperties);
|
||||
posthogLastPropertiesSent = stringifiedUserProperties;
|
||||
userIdentified = true;
|
||||
}
|
||||
// If the user is not logged in, identify the user by its anonymous UUID.
|
||||
// If the user is logged in, identify the user by its Firebase ID.
|
||||
const userId = firebaseUser ? firebaseUser.uid : getUserUUID();
|
||||
|
||||
(() => {
|
||||
if (!posthogLoaded) {
|
||||
console.info(`App analytics not ready - retrying in 2s.`);
|
||||
setTimeout(() => {
|
||||
console.info(`Retrying to update the user for app analytics.`);
|
||||
identifyUserForAnalytics(authenticatedUser);
|
||||
}, 2000);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Identify which user is using the app, after de-duplicating the call to
|
||||
// avoid useless calls.
|
||||
// This is so we can build stats on the used version, languages and usage
|
||||
// of GDevelop features.
|
||||
const stringifiedUserProperties = JSON.stringify(userProperties);
|
||||
if (stringifiedUserProperties !== posthogLastPropertiesSent) {
|
||||
posthog.identify(userId, userProperties);
|
||||
posthogLastPropertiesSent = stringifiedUserProperties;
|
||||
userIdentified = true;
|
||||
}
|
||||
})();
|
||||
|
||||
(async () => {
|
||||
await ensureGDevelopEditorAnalyticsReady();
|
||||
if (gdevelopEditorAnalytics) {
|
||||
await gdevelopEditorAnalytics.identify(userId, userProperties);
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -261,6 +339,13 @@ export const sendInAppTutorialExited = (metadata: {|
|
||||
recordEvent('in-app-tutorial-exited', metadata);
|
||||
};
|
||||
|
||||
const patchWithCurrencyField = (options: { [string]: any }) => {
|
||||
return {
|
||||
...options,
|
||||
currency: options.priceCurrency,
|
||||
};
|
||||
};
|
||||
|
||||
export const sendAssetPackOpened = (options: {|
|
||||
assetPackId: string | null,
|
||||
assetPackName: string,
|
||||
@@ -276,70 +361,85 @@ export const sendAssetPackBuyClicked = (options: {|
|
||||
assetPackName: string,
|
||||
assetPackTag: string,
|
||||
assetPackKind: 'public' | 'private' | 'unknown',
|
||||
currency?: string,
|
||||
priceValue: number | void,
|
||||
priceCurrency: string | void,
|
||||
usageType: string,
|
||||
|}) => {
|
||||
recordEvent('asset_pack_buy_clicked', options);
|
||||
recordEvent('asset_pack_buy_clicked', patchWithCurrencyField(options));
|
||||
};
|
||||
|
||||
export const sendAssetPackInformationOpened = (options: {|
|
||||
assetPackId: string,
|
||||
assetPackName: string,
|
||||
assetPackKind: 'public' | 'private' | 'unknown',
|
||||
priceValue: number | void,
|
||||
priceCurrency: string | void,
|
||||
|}) => {
|
||||
recordEvent('asset_pack_information_opened', options);
|
||||
recordEvent('asset_pack_information_opened', patchWithCurrencyField(options));
|
||||
};
|
||||
|
||||
export const sendGameTemplateBuyClicked = (options: {|
|
||||
gameTemplateId: string,
|
||||
gameTemplateName: string,
|
||||
gameTemplateTag: string,
|
||||
currency?: string,
|
||||
usageType: string,
|
||||
priceValue: number | void,
|
||||
priceCurrency: string | void,
|
||||
|}) => {
|
||||
recordEvent('game_template_buy_clicked', options);
|
||||
recordEvent('game_template_buy_clicked', patchWithCurrencyField(options));
|
||||
};
|
||||
|
||||
export const sendGameTemplateInformationOpened = (options: {|
|
||||
gameTemplateId: string,
|
||||
gameTemplateName: string,
|
||||
source: 'store' | 'examples-list' | 'homepage' | 'web-link',
|
||||
priceValue: number | void,
|
||||
priceCurrency: string | void,
|
||||
|}) => {
|
||||
recordEvent('game_template_information_opened', options);
|
||||
recordEvent(
|
||||
'game_template_information_opened',
|
||||
patchWithCurrencyField(options)
|
||||
);
|
||||
};
|
||||
|
||||
export const sendBundleBuyClicked = (options: {|
|
||||
bundleId: string,
|
||||
bundleName: string,
|
||||
bundleTag: string,
|
||||
currency?: string,
|
||||
usageType: string,
|
||||
priceValue: number | void,
|
||||
priceCurrency: string | void,
|
||||
|}) => {
|
||||
recordEvent('bundle_buy_clicked', options);
|
||||
recordEvent('bundle_buy_clicked', patchWithCurrencyField(options));
|
||||
};
|
||||
export const sendBundleInformationOpened = (options: {|
|
||||
bundleId: string,
|
||||
bundleName: string,
|
||||
source: 'store' | 'learn' | 'web-link',
|
||||
priceValue: number | void,
|
||||
priceCurrency: string | void,
|
||||
|}) => {
|
||||
recordEvent('bundle_information_opened', options);
|
||||
recordEvent('bundle_information_opened', patchWithCurrencyField(options));
|
||||
};
|
||||
|
||||
export const sendCourseInformationOpened = (options: {|
|
||||
courseId: string,
|
||||
courseName: string,
|
||||
source: 'store' | 'learn',
|
||||
priceValue: number | void,
|
||||
priceCurrency: string | void,
|
||||
|}) => {
|
||||
recordEvent('course_information_opened', options);
|
||||
recordEvent('course_information_opened', patchWithCurrencyField(options));
|
||||
};
|
||||
|
||||
export const sendCourseBuyClicked = (options: {|
|
||||
courseId: string,
|
||||
courseName: string,
|
||||
currency?: string,
|
||||
usageType: string,
|
||||
priceValue: number | void,
|
||||
priceCurrency: string | void,
|
||||
|}) => {
|
||||
recordEvent('course_buy_clicked', options);
|
||||
recordEvent('course_buy_clicked', patchWithCurrencyField(options));
|
||||
};
|
||||
|
||||
export const sendUserSurveyStarted = () => {
|
||||
|
Reference in New Issue
Block a user