Skip to content

Wip earnings in gamedashboard. #7243

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion newIDE/app/src/GameDashboard/GameAnalyticsCharts.js
Original file line number Diff line number Diff line change
@@ -57,10 +57,12 @@ const CustomTooltip = ({
payload,
label,
customStyle,
decimals = 2,
}: {|
payload: ?Array<any>,
label: string,
customStyle: Object,
decimals?: number,
|}) =>
payload ? (
<Paper style={customStyle} background="light">
@@ -79,7 +81,9 @@ const CustomTooltip = ({
index
) => (
<Text noMargin key={index}>{`${name}: ${
Number.isInteger(value) ? value.toString() : value.toFixed(2)
Number.isInteger(value)
? value.toString()
: value.toFixed(decimals)
}${unit ? ` ${unit}` : ''}`}</Text>
)
)}
@@ -397,3 +401,53 @@ export const PlayersDurationPerDayChart = ({
</ResponsiveContainer>
);
};

export const GameAdEarningsChart = ({
i18n,
chartData,
height,
fontSize,
}: ChartProps) => {
const gdevelopTheme = React.useContext(GDevelopThemeContext);
const styles = getChartsStyleFromTheme(gdevelopTheme);

return (
<ResponsiveContainer width={chartWidth} height={height} debounce={1}>
<AreaChart data={chartData.adsEarnings} margin={chartMargins}>
<Area
name={i18n._(t`USD`)}
type="monotone"
dataKey="accumulatedEarningsInUSDs"
stroke={gdevelopTheme.chart.dataColor1}
fill={gdevelopTheme.chart.dataColor1}
fillOpacity={0.25}
yAxisId={0}
/>
<CartesianGrid
stroke={gdevelopTheme.chart.gridColor}
strokeDasharray="3 3"
/>
<XAxis
dataKey="date"
stroke={gdevelopTheme.chart.textColor}
style={styles.tickLabel}
tick={{ fontSize: fontSize === 'small' ? 12 : 16 }}
/>
<YAxis
dataKey="accumulatedEarningsInUSDs"
stroke={gdevelopTheme.chart.textColor}
style={styles.tickLabel}
tick={{ fontSize: fontSize === 'small' ? 12 : 16 }}
/>
<Tooltip
content={props =>
CustomTooltip({
...props,
customStyle: styles.tooltipContent,
})
}
/>
</AreaChart>
</ResponsiveContainer>
);
};
353 changes: 295 additions & 58 deletions newIDE/app/src/GameDashboard/GameAnalyticsEvaluator.js

Large diffs are not rendered by default.

109 changes: 76 additions & 33 deletions newIDE/app/src/GameDashboard/GameAnalyticsPanel.js
Original file line number Diff line number Diff line change
@@ -28,8 +28,13 @@ import {
PlayersRepartitionPerDurationChart,
PlayersDurationPerDayChart,
SessionsChart,
GameAdEarningsChart,
} from './GameAnalyticsCharts';
import MarketingPlanSingleDisplay from '../MarketingPlans/MarketingPlanSingleDisplay';
import {
getGameAdEarnings,
type GameAdEarning,
} from '../Utils/GDevelopServices/Usage';

const chartHeight = 300;

@@ -46,73 +51,90 @@ export const GameAnalyticsPanel = ({
gameFeaturings,
fetchGameFeaturings,
}: Props) => {
const { getAuthorizationHeader, profile } = React.useContext(
const { getAuthorizationHeader, profile, usages } = React.useContext(
AuthenticatedUserContext
);

const [gameRollingMetrics, setGameMetrics] = React.useState<?(GameMetrics[])>(
null
);
const [gameMetrics, setGameMetrics] = React.useState<?(GameMetrics[])>(null);
const [
gameAdEarnings,
setGameAdEarnings,
] = React.useState<?(GameAdEarning[])>(null);
const { yearChartData, monthChartData } = React.useMemo(
() => buildChartData(gameRollingMetrics),
[gameRollingMetrics]
() =>
buildChartData({ gameMetrics, gameAdEarnings, usages, gameId: game.id }),
[gameMetrics, gameAdEarnings, usages, game.id]
);
const [dataPeriod, setDataPeriod] = React.useState('month');
const chartData = dataPeriod === 'year' ? yearChartData : monthChartData;

const [gameRollingMetricsError, setGameMetricsError] = React.useState<?Error>(
null
);
const [isGameMetricsLoading, setIsGameMetricsLoading] = React.useState(false);
const [error, setError] = React.useState<?Error>(null);
const [isLoading, setIsLoading] = React.useState(false);

// TODO In some timezones, it might ask one less or extra day.
const lastYearIsoDate = formatISO(subDays(new Date(), daysShownForYear), {
representation: 'date',
});
const loadGameMetrics = React.useCallback(
const loadGameAnalytics = React.useCallback(
async () => {
if (!profile) return;

const { id } = profile;

setIsGameMetricsLoading(true);
setGameMetricsError(null);
setIsLoading(true);
setError(null);

const lastYearIsoDate = formatISO(subDays(new Date(), daysShownForYear), {
representation: 'date',
});
const gameCreatioDateIsoDate = formatISO(new Date(game.createdAt), {
representation: 'date',
});
const todayIsoDate = formatISO(new Date(), {
representation: 'date',
});

try {
const gameRollingMetrics = await getGameMetricsFrom(
getAuthorizationHeader,
id,
game.id,
lastYearIsoDate
);
const [gameRollingMetrics, gameAdEarnings] = await Promise.all([
getGameMetricsFrom(
getAuthorizationHeader,
id,
game.id,
lastYearIsoDate
),
getGameAdEarnings(getAuthorizationHeader, id, {
gameId: game.id,
startIsoDate: gameCreatioDateIsoDate,
endIsoDate: todayIsoDate,
}),
]);
setGameMetrics(gameRollingMetrics);
setGameAdEarnings(gameAdEarnings);
} catch (err) {
console.error(`Unable to load game rolling metrics:`, err);
setGameMetricsError(err);
setError(err);
}
setIsGameMetricsLoading(false);
setIsLoading(false);
},
[getAuthorizationHeader, profile, game, lastYearIsoDate]
[getAuthorizationHeader, profile, game]
);

React.useEffect(
() => {
loadGameMetrics();
loadGameAnalytics();
},
[loadGameMetrics]
[loadGameAnalytics]
);

if (isGameMetricsLoading) return <PlaceholderLoader />;
if (isLoading) return <PlaceholderLoader />;

const displaySuggestedMarketingPlan =
recommendedMarketingPlan && gameFeaturings && fetchGameFeaturings;

return (
<I18n>
{({ i18n }) =>
gameRollingMetricsError ? (
error ? (
<PlaceholderError
onRetry={() => {
loadGameMetrics();
loadGameAnalytics();
}}
>
<Trans>There was an issue getting the game analytics.</Trans>{' '}
@@ -136,8 +158,28 @@ export const GameAnalyticsPanel = ({
<Grid
item
xs={12}
sm={displaySuggestedMarketingPlan ? 7 : 12}
md={displaySuggestedMarketingPlan ? 8 : 12}
sm={displaySuggestedMarketingPlan ? 4 : 12}
md={displaySuggestedMarketingPlan ? 4 : 12}
>
<Column noMargin alignItems="center" expand>
<Text size="block-title" align="center">
<Trans>
USD {chartData.overview.totalEarningsInUSDs} in Ads
earnings
</Trans>
</Text>
<GameAdEarningsChart
chartData={chartData}
height={chartHeight}
i18n={i18n}
/>
</Column>
</Grid>
<Grid
item
xs={12}
sm={displaySuggestedMarketingPlan ? 4 : 12}
md={displaySuggestedMarketingPlan ? 4 : 12}
>
<Column noMargin alignItems="center" expand>
<Text size="block-title" align="center">
@@ -150,10 +192,11 @@ export const GameAnalyticsPanel = ({
/>
</Column>
</Grid>

{recommendedMarketingPlan &&
gameFeaturings &&
fetchGameFeaturings && (
<Grid item xs={12} sm={5} md={4}>
<Grid item xs={12} sm={4} md={4}>
<MarketingPlanSingleDisplay
fetchGameFeaturings={fetchGameFeaturings}
gameFeaturings={gameFeaturings}
85 changes: 37 additions & 48 deletions newIDE/app/src/GameDashboard/Monetization/UserEarningsWidget.js
Original file line number Diff line number Diff line change
@@ -11,7 +11,6 @@ import Window from '../../Utils/Window';
import RaisedButton from '../../UI/RaisedButton';
import Coin from '../../Credits/Icons/Coin';
import AuthenticatedUserContext from '../../Profile/AuthenticatedUserContext';
import PlaceholderError from '../../UI/PlaceholderError';
import Tooltip from '@material-ui/core/Tooltip';
import CreditOutDialog from './CashOutDialog';
import GDevelopThemeContext from '../../UI/Theme/GDevelopThemeContext';
@@ -49,7 +48,6 @@ const UserEarningsWidget = ({ size }: Props) => {

const [earningsInMilliUsd, setEarningsInMilliUsd] = React.useState(0);
const [earningsInCredits, setEarningsInCredits] = React.useState(0);
const [error, setError] = React.useState(null);
const intervalValuesUpdate = React.useRef(null);

const [selectedCashOutType, setSelectedCashOutType] = React.useState<
@@ -58,43 +56,43 @@ const UserEarningsWidget = ({ size }: Props) => {

const animateEarnings = React.useCallback(
async () => {
if (!userEarningsBalance) return;

try {
// Create an animation to show the earnings increasing.
const targetMilliUsd = userEarningsBalance.amountInMilliUSDs;
const targetCredits = userEarningsBalance.amountInCredits;

const duration = 500;
const steps = 30;
const intervalTime = duration / steps;

const milliUsdIncrement = targetMilliUsd / steps;
const creditsIncrement = targetCredits / steps;

let currentMilliUsd = 0;
let currentCredits = 0;
let step = 0;

intervalValuesUpdate.current = setInterval(() => {
step++;
currentMilliUsd += milliUsdIncrement;
currentCredits += creditsIncrement;

setEarningsInMilliUsd(currentMilliUsd);
setEarningsInCredits(currentCredits);

if (step >= steps) {
clearInterval(intervalValuesUpdate.current);
// Ensure final values are exactly the target values
setEarningsInMilliUsd(targetMilliUsd);
setEarningsInCredits(targetCredits);
}
}, intervalTime);
} catch (error) {
console.error('Unable to get user earnings balance:', error);
setError(error);
if (!userEarningsBalance) {
// In case the user logs out, reset the earnings.
setEarningsInMilliUsd(0);
setEarningsInCredits(0);
return;
}

// Create an animation to show the earnings increasing.
const targetMilliUsd = userEarningsBalance.amountInMilliUSDs;
const targetCredits = userEarningsBalance.amountInCredits;

const duration = 500;
const steps = 30;
const intervalTime = duration / steps;

const milliUsdIncrement = targetMilliUsd / steps;
const creditsIncrement = targetCredits / steps;

let currentMilliUsd = 0;
let currentCredits = 0;
let step = 0;

intervalValuesUpdate.current = setInterval(() => {
step++;
currentMilliUsd += milliUsdIncrement;
currentCredits += creditsIncrement;

setEarningsInMilliUsd(currentMilliUsd);
setEarningsInCredits(currentCredits);

if (step >= steps) {
clearInterval(intervalValuesUpdate.current);
// Ensure final values are exactly the target values
setEarningsInMilliUsd(targetMilliUsd);
setEarningsInCredits(targetCredits);
}
}, intervalTime);
},
[userEarningsBalance]
);
@@ -127,16 +125,7 @@ const UserEarningsWidget = ({ size }: Props) => {
userEarningsBalance &&
earningsInMilliUsd >= userEarningsBalance.minAmountToCashoutInMilliUSDs;

const content = error ? (
<LineStackLayout noMargin alignItems="center">
<PlaceholderError onRetry={onRefreshEarningsBalance}>
<Trans>
Can't load your game earnings. Verify your internet connection or try
again later.
</Trans>
</PlaceholderError>
</LineStackLayout>
) : (
const content = (
<ResponsiveLineStackLayout
noMargin
alignItems="center"
164 changes: 93 additions & 71 deletions newIDE/app/src/GameDashboard/Widgets/AnalyticsWidget.js
Original file line number Diff line number Diff line change
@@ -10,17 +10,19 @@ import { ColumnStackLayout, ResponsiveLineStackLayout } from '../../UI/Layout';
import { Column, Line, Spacer } from '../../UI/Grid';
import Text from '../../UI/Text';
import { type Game } from '../../Utils/GDevelopServices/Game';
import { SessionsChart } from '../GameAnalyticsCharts';
import { GameAdEarningsChart, SessionsChart } from '../GameAnalyticsCharts';
import { type GameMetrics } from '../../Utils/GDevelopServices/Analytics';
import { buildLastWeekChartData } from '../GameAnalyticsEvaluator';
import { buildChartData } from '../GameAnalyticsEvaluator';
import RaisedButton from '../../UI/RaisedButton';
import Coin from '../../Credits/Icons/Coin';
import MarketingPlansDialog from '../../MarketingPlans/MarketingPlansDialog';
import GameLinkAndShareIcons from '../GameLinkAndShareIcons';
import { useResponsiveWindowSize } from '../../UI/Responsive/ResponsiveWindowMeasurer';
import { type GameAdEarning } from '../../Utils/GDevelopServices/Usage';
import { getHelpLink } from '../../Utils/HelpLink';
import Window from '../../Utils/Window';
import Link from '../../UI/Link';
import AuthenticatedUserContext from '../../Profile/AuthenticatedUserContext';

const publishingHelpLink = getHelpLink('/publishing', 'publish-your-game');

@@ -30,29 +32,29 @@ type Props = {|
game: Game,
onSeeAll: () => void,
gameMetrics: ?Array<GameMetrics>,
gameAdEarnings: ?(GameAdEarning[]),
gameUrl: ?string,
|};

const AnalyticsWidget = ({ game, onSeeAll, gameMetrics, gameUrl }: Props) => {
const AnalyticsWidget = ({
game,
onSeeAll,
gameMetrics,
gameAdEarnings,
gameUrl,
}: Props) => {
const hasNoSession = gameMetrics && gameMetrics.length === 0;
const { isMobile } = useResponsiveWindowSize();
const oneWeekAgoIsoDate = new Date(
new Date().setHours(0, 0, 0, 0) - 7 * 24 * 3600 * 1000
).toISOString();
const [
marketingPlansDialogOpen,
setMarketingPlansDialogOpen,
] = React.useState<boolean>(false);
const { usages } = React.useContext(AuthenticatedUserContext);

const chartData = React.useMemo(
() => {
const lastWeekGameMetrics = gameMetrics
? gameMetrics.filter(metrics => metrics.date > oneWeekAgoIsoDate)
: null;

return buildLastWeekChartData(lastWeekGameMetrics);
},
[gameMetrics, oneWeekAgoIsoDate]
const { weekChartData } = React.useMemo(
() =>
buildChartData({ gameMetrics, gameAdEarnings, usages, gameId: game.id }),
[gameMetrics, gameAdEarnings, usages, game.id]
);

return (
@@ -74,69 +76,89 @@ const AnalyticsWidget = ({ game, onSeeAll, gameMetrics, gameUrl }: Props) => {
widgetName="analytics"
>
<ResponsiveLineStackLayout expand noColumnMargin noMargin>
{!gameMetrics ? (
<div style={styles.loadingSpace} />
) : hasNoSession ? (
gameUrl ? (
<ColumnStackLayout
noMargin
alignItems={isMobile ? 'stretch' : 'flex-start'}
noOverflowParent
>
<Spacer />
<Text noMargin color="secondary">
<Trans>
No data to show yet. Share your game creator profile
with more people to get more players!
</Trans>
</Text>
<GameLinkAndShareIcons display="column" url={gameUrl} />
</ColumnStackLayout>
<Column expand noMargin>
<Line alignItems="center">
<Text size="sub-title">
<Trans>Ads earnings</Trans>
</Text>
</Line>
{!gameAdEarnings ? (
<div style={styles.loadingSpace} />
) : (
<ColumnStackLayout
noMargin
expand
justifyContent="center"
alignItems="center"
>
<Spacer />
<Text color="secondary" noMargin>
<Trans>
<Link
href={publishingHelpLink}
onClick={() =>
Window.openExternalURL(publishingHelpLink)
}
>
Share your game
</Link>{' '}
and start collecting data from your players to better
understand them.
</Trans>
</Text>
</ColumnStackLayout>
)
) : (
<Column expand noMargin>
<Line alignItems="center" justifyContent="space-between">
<Text size="block-title" noMargin>
<Trans>Sessions</Trans>
</Text>
<RaisedButton
primary
icon={<Coin fontSize="small" />}
label={<Trans>Get more players</Trans>}
onClick={() => setMarketingPlansDialogOpen(true)}
<Column noMargin>
<GameAdEarningsChart
i18n={i18n}
height={200}
chartData={weekChartData}
fontSize="small"
/>
</Line>
</Column>
)}
</Column>
<Column expand noMargin>
<Line alignItems="center" justifyContent="space-between">
<Text size="sub-title">
<Trans>Sessions</Trans>
</Text>
<RaisedButton
primary
icon={<Coin fontSize="small" />}
label={<Trans>Get more players</Trans>}
onClick={() => setMarketingPlansDialogOpen(true)}
disabled={!gameMetrics}
/>
</Line>
{!gameMetrics ? (
<div style={styles.loadingSpace} />
) : hasNoSession ? (
gameUrl ? (
<ColumnStackLayout
noMargin
alignItems={isMobile ? 'stretch' : 'flex-start'}
noOverflowParent
>
<Spacer />
<Text noMargin color="secondary">
<Trans>
No data to show yet. Share your game creator profile
with more people to get more players!
</Trans>
</Text>
<GameLinkAndShareIcons display="column" url={gameUrl} />
</ColumnStackLayout>
) : (
<ColumnStackLayout
noMargin
expand
justifyContent="center"
alignItems="center"
>
<Spacer />
<Text color="secondary" noMargin>
<Trans>
<Link
href={publishingHelpLink}
onClick={() =>
Window.openExternalURL(publishingHelpLink)
}
>
Share your game
</Link>{' '}
and start collecting data from your players to better
understand them.
</Trans>
</Text>
</ColumnStackLayout>
)
) : (
<SessionsChart
i18n={i18n}
height={200}
chartData={chartData}
chartData={weekChartData}
fontSize="small"
/>
</Column>
)}
)}
</Column>
</ResponsiveLineStackLayout>
</DashboardWidget>
)}
2 changes: 1 addition & 1 deletion newIDE/app/src/GameDashboard/Widgets/ServicesWidget.js
Original file line number Diff line number Diff line change
@@ -39,7 +39,7 @@ const ServicesWidget = ({
);
return (
<DashboardWidget
widgetSize={'full'}
widgetSize="full"
title={<Trans>Player services</Trans>}
widgetName="services"
>
42 changes: 32 additions & 10 deletions newIDE/app/src/GameDashboard/index.js
Original file line number Diff line number Diff line change
@@ -60,6 +60,10 @@ import ProjectsWidget from './Widgets/ProjectsWidget';
import { useResponsiveWindowSize } from '../UI/Responsive/ResponsiveWindowMeasurer';
import { formatISO, subDays } from 'date-fns';
import { daysShownForYear } from './GameAnalyticsEvaluator';
import {
getGameAdEarnings,
type GameAdEarning,
} from '../Utils/GDevelopServices/Usage';

const styles = {
mobileFooter: {
@@ -140,9 +144,7 @@ const GameDashboard = ({
const [feedbacks, setFeedbacks] = React.useState<?Array<Comment>>(null);
const [builds, setBuilds] = React.useState<?Array<Build>>(null);
const [publicGame, setPublicGame] = React.useState<?PublicGame>(null);
const [gameRollingMetrics, setGameMetrics] = React.useState<?(GameMetrics[])>(
null
);
const [gameMetrics, setGameMetrics] = React.useState<?(GameMetrics[])>(null);
const [
recommendedMarketingPlan,
setRecommendedMarketingPlan,
@@ -154,9 +156,10 @@ const GameDashboard = ({
const [leaderboards, setLeaderboards] = React.useState<?Array<Leaderboard>>(
null
);
const lastYearIsoDate = formatISO(subDays(new Date(), daysShownForYear), {
representation: 'date',
});
const [
gameAdEarnings,
setGameAdEarnings,
] = React.useState<?Array<GameAdEarning>>(null);

const webBuilds = builds
? builds.filter(build => build.type === 'web-build')
@@ -457,13 +460,25 @@ const GameDashboard = ({
setLeaderboards(null);
setGameFeaturings(null);
setRecommendedMarketingPlan(null);
setGameAdEarnings(null);
return;
}

const lastYearIsoDate = formatISO(subDays(new Date(), daysShownForYear), {
representation: 'date',
});
const gameCreatioDateIsoDate = formatISO(new Date(game.createdAt), {
representation: 'date',
});
const todayIsoDate = formatISO(new Date(), {
representation: 'date',
});

const [
feedbacks,
builds,
gameRollingMetrics,
gameMetrics,
gameAdEarnings,
leaderboards,
recommendedMarketingPlan,
] = await Promise.all([
@@ -478,6 +493,11 @@ const GameDashboard = ({
game.id,
lastYearIsoDate
),
getGameAdEarnings(getAuthorizationHeader, profile.id, {
gameId: game.id,
startIsoDate: gameCreatioDateIsoDate,
endIsoDate: todayIsoDate,
}),
listGameActiveLeaderboards(getAuthorizationHeader, profile.id, game.id),
getRecommendedMarketingPlan(getAuthorizationHeader, {
gameId: game.id,
@@ -487,16 +507,17 @@ const GameDashboard = ({
]);
setFeedbacks(feedbacks);
setBuilds(builds);
setGameMetrics(gameRollingMetrics);
setGameMetrics(gameMetrics);
setLeaderboards(leaderboards);
setRecommendedMarketingPlan(recommendedMarketingPlan);
setGameAdEarnings(gameAdEarnings);
},
[
fetchGameFeaturings,
game.id,
getAuthorizationHeader,
profile,
lastYearIsoDate,
game.createdAt,
]
);

@@ -582,7 +603,8 @@ const GameDashboard = ({
<Grid container spacing={2} ref={grid}>
<AnalyticsWidget
onSeeAll={() => setCurrentView('analytics')}
gameMetrics={gameRollingMetrics}
gameMetrics={gameMetrics}
gameAdEarnings={gameAdEarnings}
game={game}
gameUrl={gameUrl}
/>
4 changes: 2 additions & 2 deletions newIDE/app/src/Utils/GDevelopServices/Analytics.js
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
import axios from 'axios';
import { GDevelopAnalyticsApi } from './ApiConfigs';

export type GameMetrics = {
export type GameMetrics = {|
date: string,

sessions: ?{
@@ -43,7 +43,7 @@ export type GameMetrics = {
/** Day 7 retained players (number of players who played this day, and were new players 7 days earlier). */
d7RetainedPlayers: number,
},
};
|};

export const client = axios.create({
baseURL: GDevelopAnalyticsApi.baseUrl,
2 changes: 1 addition & 1 deletion newIDE/app/src/Utils/GDevelopServices/ApiConfigs.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @flow
import Window from '../Window';

const isDev = Window.isDev();
const isDev = false;

export const GDevelopGamePreviews = {
baseUrl: `https://game-previews.gdevelop.io/`,
44 changes: 44 additions & 0 deletions newIDE/app/src/Utils/GDevelopServices/Usage.js
Original file line number Diff line number Diff line change
@@ -11,6 +11,8 @@ export type Usage = {
userId: string,
type: string,
createdAt: number,
description?: string,
creditsPaid?: number,
};
export type Usages = Array<Usage>;

@@ -192,6 +194,14 @@ export type SubscriptionPlanWithPricingSystems = {|
pricingSystems: SubscriptionPlanPricingSystem[],
|};

export type GameAdEarning = {|
gameId: string,
date: string,
adEarningsInMilliUSDs: number,
adEarningsInCredits: number,
updatedAt: number,
|};

export interface UserEarningsBalance {
userId: string;
amountInMilliUSDs: number;
@@ -313,6 +323,40 @@ export const getUserUsages = async (
return response.data;
};

export const getGameAdEarnings = async (
getAuthorizationHeader: () => Promise<string>,
userId: string,
{
gameId,
startIsoDate,
endIsoDate,
}: {|
gameId: string,
startIsoDate: string,
endIsoDate: string,
|}
): Promise<GameAdEarning[]> => {
const authorizationHeader = await getAuthorizationHeader();

const response = await apiClient.get(`/game-ad-earning`, {
params: {
userId,
gameId,
startIsoDate,
endIsoDate,
},
headers: {
Authorization: authorizationHeader,
},
});
const gameAdEarnings = response.data;
if (!Array.isArray(gameAdEarnings)) {
throw new Error('Invalid response from the game ad earnings API');
}

return gameAdEarnings;
};

export const getUserEarningsBalance = async (
getAuthorizationHeader: () => Promise<string>,
userId: string