summaryrefslogtreecommitdiff
path: root/node_modules/@jet-app/app-store/tmp/src/common/app-promotions
diff options
context:
space:
mode:
authorrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
committerrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
commitbce557cc2dc767628bed6aac87301a1be7c5431b (patch)
treeb51a051228d01fe3306cd7626d4a96768aadb944 /node_modules/@jet-app/app-store/tmp/src/common/app-promotions
init commit
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/app-promotions')
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-event-detail.js131
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-event.js243
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotion.js120
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotions-common.js385
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotions-shelf.js120
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/app-promotions/contingent-offer-detail.js106
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/app-promotions/contingent-offer.js189
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/app-promotions/offer-item-detail.js147
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/app-promotions/offer-item.js206
9 files changed, 1647 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-event-detail.js b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-event-detail.js
new file mode 100644
index 0000000..ea5b203
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-event-detail.js
@@ -0,0 +1,131 @@
+import * as models from "../../api/models";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as metricsHelpersPage from "../metrics/helpers/page";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as color from "../../foundation/util/color-util";
+import * as objects from "../../foundation/util/objects";
+import { addClickEventToAction } from "../metrics/helpers/clicks";
+import { MetricsReferralContext } from "../metrics/metrics-referral-context";
+import * as sharing from "../sharing";
+import * as appPromotionCommon from "./app-promotions-common";
+import { isNothing } from "@jet/environment";
+import { makeAppEventPageIntent } from "../../api/intents/app-event-page-intent";
+import { getLocale } from "../locale";
+import { getPlatform } from "../preview-platform";
+/**
+ * Create a flow action for navigating to the app event detail page.
+ * @param data The data blob
+ * @param parentAppData The associated parent app data
+ * @param appEvent The source app event
+ * @param baseMetricsOptions The base metrics options
+ * @param animationBehavior The animation behaviour for presenting the modal page
+ * @param includeLockupClickAction Whether to generate a click action for the lockup
+ * @param referrerData Referrer data from an incoming deep link
+ */
+export function appEventDetailPageFlowActionFromData(objectGraph, data, parentAppData, appEvent, baseMetricsOptions, animationBehavior, includeLockupClickAction, referrerData) {
+ const page = appEventDetailPageFromData(objectGraph, data, parentAppData, appEvent, baseMetricsOptions, includeLockupClickAction, referrerData, false);
+ const action = new models.FlowAction("appEventDetail");
+ action.title = appEvent.title;
+ action.pageData = page;
+ action.animationBehavior = animationBehavior;
+ if (baseMetricsOptions && baseMetricsOptions.pageInformation) {
+ action.referrerUrl = baseMetricsOptions.pageInformation.pageUrl;
+ }
+ if (objectGraph.client.isWeb) {
+ action.destination = makeAppEventPageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ id: appEvent.appEventId,
+ });
+ action.pageUrl = mediaAttributes.attributeAsString(data, "url");
+ }
+ return action;
+}
+/**
+ * Creates an app event detail page
+ * @param objectGraph The object graph
+ * @param data The data blob
+ * @param parentAppData The data blob for the related parent app
+ * @param appEvent The source app event
+ * @param baseMetricsOptions The base metrics options to use for the detail page
+ * @param includeLockupClickAction Whether to generate a click action for the lockup
+ * @param referrerData The referrer data
+ * @param isArcadePage Whether or not this is presented on the Arcade page
+ */
+export function appEventDetailPageFromData(objectGraph, data, parentAppData, appEvent, baseMetricsOptions, includeLockupClickAction, referrerData, isArcadePage) {
+ var _a, _b;
+ const artwork = appPromotionCommon.artworkFromData(objectGraph, data, "productArtwork");
+ const video = appPromotionCommon.videoFromData(objectGraph, data, "productVideo", true, true);
+ const copy = objects.shallowCopyOf(appEvent);
+ const selectedArtwork = (_a = video === null || video === void 0 ? void 0 : video.preview) !== null && _a !== void 0 ? _a : artwork;
+ let mediaOverlayStyle = "dark";
+ let isArtworkDark = true;
+ let includeBorderInDarkMode = false;
+ if (serverData.isDefinedNonNull(selectedArtwork)) {
+ isArtworkDark = color.isDarkColor(selectedArtwork.backgroundColor);
+ includeBorderInDarkMode = color.isDarkColor(selectedArtwork.backgroundColor, 10);
+ mediaOverlayStyle = isArtworkDark ? "dark" : "light";
+ }
+ const pageInformation = metricsHelpersPage.pageInformationForAppPromotionDetailPage(objectGraph, models.AppPromotionType.AppEvent, data.id, parentAppData.id, referrerData, (_b = baseMetricsOptions.recoMetricsData) !== null && _b !== void 0 ? _b : null);
+ const metricsOptions = {
+ ...baseMetricsOptions,
+ pageInformation: pageInformation,
+ locationTracker: metricsHelpersLocation.newLocationTracker(),
+ targetType: "EventDetails",
+ };
+ const clickOptions = {
+ ...metricsOptions,
+ id: parentAppData.id,
+ inAppEventId: data.id,
+ relatedSubjectIds: [parentAppData.id],
+ };
+ copy.notificationConfig = appPromotionCommon.notificationConfigFromData(objectGraph, data, appEvent, clickOptions, false);
+ const shareAction = shareActionFromData(objectGraph, data, appEvent, clickOptions);
+ const offerEnvironment = isArtworkDark ? "dark" : "light";
+ const lockup = appPromotionCommon.lockupFromData(objectGraph, data, parentAppData, copy.title, offerEnvironment, "transparent", false, metricsOptions, includeLockupClickAction, referrerData, isArcadePage, false);
+ if (serverData.isNull(lockup)) {
+ return null;
+ }
+ copy.lockup = lockup;
+ const page = new models.AppEventDetailPage(copy, artwork, video, shareAction, mediaOverlayStyle, includeBorderInDarkMode);
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, page, pageInformation, (fields) => {
+ if (serverData.isDefinedNonNullNonEmpty(referrerData)) {
+ MetricsReferralContext.shared.addReferralContextToMetricsFieldsIfNecessary(fields);
+ }
+ });
+ return page;
+}
+/**
+ * Creates an action for sharing an app event
+ * @param data: The data blob
+ * @param appEvent The source app event
+ * @param metricsClickOptions The click options used for the containing context.
+ */
+function shareActionFromData(objectGraph, data, appEvent, metricsClickOptions) {
+ var _a, _b;
+ const url = mediaAttributes.attributeAsString(data, "url");
+ if (isNothing(url) || url.length === 0) {
+ return null;
+ }
+ // Prefer the module artwork first, otherwise fall back to the video preview frame, if available.
+ const artwork = (_a = appEvent.moduleArtwork) !== null && _a !== void 0 ? _a : (_b = appEvent.moduleVideo) === null || _b === void 0 ? void 0 : _b.preview;
+ let subtitle = objectGraph.loc.string("SHARE_APP_EVENT_SUBTITLE");
+ if (subtitle === "SHARE_APP_EVENT_SUBTITLE") {
+ subtitle = appEvent.subtitle;
+ }
+ const shareData = sharing.shareSheetDataForAppEvent(objectGraph, appEvent.title, subtitle, url, undefined, artwork);
+ if (!serverData.isDefinedNonNull(shareData)) {
+ return null;
+ }
+ const activities = sharing.shareSheetActivitiesForAppEvent(objectGraph, appEvent, url);
+ const action = new models.ShareSheetAction(shareData, activities);
+ addClickEventToAction(objectGraph, action, {
+ ...metricsClickOptions,
+ targetType: "lockup",
+ actionType: "share",
+ idType: "its_id",
+ });
+ return action;
+}
+//# sourceMappingURL=app-event-detail.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-event.js b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-event.js
new file mode 100644
index 0000000..94eb7fe
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-event.js
@@ -0,0 +1,243 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import * as color from "../../foundation/util/color-util";
+import * as dateUtil from "../../foundation/util/date-util";
+import * as appPromotionCommon from "./app-promotions-common";
+/**
+ * Creates an app event object or a Date, if the event should not yet be shown, from the given data.
+ * @param data The data blob
+ * @param parentAppData The related parent app of this app event. If not provided will be derived from `data`.
+ * @param hideLockupWhenNotInstalled Whether to hide the lockup chin when the app is installed
+ * @param includeClickAction Whether to generate a click action for the app event
+ * @param offerEnvironment The preferred environment for the offer
+ * @param offerStyle The preferred style of the offer
+ * @param includeCrossLinkTitles Whether the cross link titles will be displayed when the app is installed
+ * @param allowMissingParentApp Whether to still create the app event if the parent app is missing
+ * @param baseMetricsOptions The base metrics options
+ * @param allowEndedEvents Whether events in the past are allowed
+ * @param includeLockupClickAction Whether to include the click action for the lockup
+ * @param isArcadePage Whether or not this is presented on the Arcade page
+ * @param allowUnpublishedAppEventPreviews Whether or not to allow app event previews
+ * @returns an AppEvent, or a Date if the event's `promotionStartDate` is in the future.
+ */
+export function appEventOrPromotionStartDateFromData(objectGraph, data, parentAppData, hideLockupWhenNotInstalled, includeClickAction, offerEnvironment, offerStyle, includeCrossLinkTitles, baseMetricsOptions, allowEndedEvents, includeLockupClickAction, editorialKind, isArcadePage, allowUnpublishedAppEventPreviews) {
+ var _a, _b, _c;
+ if (data.type !== "app-events") {
+ return null;
+ }
+ const promotionStartDateString = mediaAttributes.attributeAsString(data, "promotionStartDate");
+ if (isNothing(promotionStartDateString) || promotionStartDateString.length === 0) {
+ return null;
+ }
+ const promotionStartDate = new Date(promotionStartDateString);
+ if (isNothing(promotionStartDate)) {
+ return null;
+ }
+ const todayDate = new Date();
+ const isFutureDate = promotionStartDate.getTime() > todayDate.getTime();
+ if (isFutureDate && !allowUnpublishedAppEventPreviews) {
+ return promotionStartDate;
+ }
+ // Artwork / video
+ const moduleArtwork = appPromotionCommon.artworkFromData(objectGraph, data, "lockupArtwork");
+ const moduleVideo = appPromotionCommon.videoFromData(objectGraph, data, "lockupVideo", false, false);
+ if (isNothing(moduleArtwork) && serverData.isNullOrEmpty(moduleVideo)) {
+ return null;
+ }
+ const selectedArtwork = (_a = moduleVideo === null || moduleVideo === void 0 ? void 0 : moduleVideo.preview) !== null && _a !== void 0 ? _a : moduleArtwork;
+ const isArtworkDark = color.isDarkColor(selectedArtwork === null || selectedArtwork === void 0 ? void 0 : selectedArtwork.backgroundColor);
+ const mediaOverlayStyle = isArtworkDark ? "dark" : "light";
+ const includeBorderInDarkMode = color.isDarkColor(selectedArtwork === null || selectedArtwork === void 0 ? void 0 : selectedArtwork.backgroundColor, 10);
+ // Other required fields
+ const title = mediaAttributes.attributeAsString(data, "name");
+ let kind = mediaAttributes.attributeAsString(data, "kind");
+ if (serverData.isDefinedNonNullNonEmpty(editorialKind)) {
+ kind = editorialKind;
+ }
+ const startDateString = mediaAttributes.attributeAsString(data, "startDate");
+ if (isNothing(title) ||
+ title.length === 0 ||
+ isNothing(kind) ||
+ kind.length === 0 ||
+ isNothing(startDateString) ||
+ startDateString.length === 0) {
+ return null;
+ }
+ const startDate = new Date(startDateString);
+ if (isNothing(startDate)) {
+ return null;
+ }
+ // Description
+ const detail = (_b = mediaAttributes.attributeAsString(data, "description.standard")) !== null && _b !== void 0 ? _b : "";
+ // Lockup
+ const resolvedParentAppData = parentAppData !== null && parentAppData !== void 0 ? parentAppData : mediaRelationship.relationshipData(objectGraph, data, "app");
+ let lockup = null;
+ if (isSome(resolvedParentAppData)) {
+ lockup = appPromotionCommon.lockupFromData(objectGraph, data, resolvedParentAppData, title, offerEnvironment, offerStyle, includeCrossLinkTitles, baseMetricsOptions, includeLockupClickAction, null, isArcadePage, true);
+ }
+ if (isNothing(lockup)) {
+ objectGraph.console.warn(`Parent app for event ${data.id} is missing, returning null.`);
+ return null;
+ }
+ // Requirements
+ const requirements = mediaAttributes.attributeAsString(data, "requirement");
+ // Supplementary optional fields
+ const subtitle = mediaAttributes.attributeAsString(data, "subtitle");
+ const endDateString = mediaAttributes.attributeAsString(data, "endDate");
+ let endDate;
+ if (isSome(endDateString) && endDateString.length > 0) {
+ endDate = new Date(endDateString);
+ }
+ const hasEventEnded = endDate !== undefined && endDate.getTime() <= todayDate.getTime();
+ if (hasEventEnded && !allowEndedEvents) {
+ return null;
+ }
+ // Formatted dates
+ const badgeKindString = (_c = mediaAttributes.attributeAsString(data, "badgeKind")) !== null && _c !== void 0 ? _c : undefined;
+ const badgeKind = badgeKindFromString(badgeKindString, startDate, endDate);
+ const formattedDates = formattedDatesWithKind(objectGraph, badgeKind, startDate, endDate);
+ const appEvent = new models.AppEvent(data.id, moduleArtwork !== null && moduleArtwork !== void 0 ? moduleArtwork : undefined, moduleVideo !== null && moduleVideo !== void 0 ? moduleVideo : undefined, title, subtitle !== null && subtitle !== void 0 ? subtitle : undefined, detail, startDate, endDate, badgeKind, kind, requirements !== null && requirements !== void 0 ? requirements : undefined, lockup, hideLockupWhenNotInstalled, formattedDates, mediaOverlayStyle, includeBorderInDarkMode);
+ // Notifications
+ if (serverData.isDefinedNonNull(resolvedParentAppData)) {
+ const clickOptions = {
+ ...baseMetricsOptions,
+ id: resolvedParentAppData.id,
+ inAppEventId: data.id,
+ relatedSubjectIds: [resolvedParentAppData.id],
+ };
+ appEvent.notificationConfig = appPromotionCommon.notificationConfigFromData(objectGraph, data, appEvent, clickOptions, true);
+ }
+ // Click action
+ if (includeClickAction && serverData.isDefinedNonNull(resolvedParentAppData)) {
+ appEvent.clickAction = appPromotionCommon.detailPageClickActionFromData(objectGraph, data, resolvedParentAppData, appEvent, baseMetricsOptions, includeLockupClickAction);
+ }
+ return appEvent;
+}
+/**
+ * Determines the badge kind from the given string.
+ * @param badgeKindString The raw badge kind string
+ * @param startDate The start date of the app event
+ * @param endDate The end date of the app event, if any
+ * @returns An AppEventBadgeKind
+ */
+export function badgeKindFromString(badgeKindString, startDate, endDate) {
+ let badgeKind = (badgeKindString !== null && badgeKindString !== void 0 ? badgeKindString : models.AppEventBadgeKind.live);
+ if (badgeKind === models.AppEventBadgeKind.live && serverData.isDefinedNonNull(endDate)) {
+ // If the event is longer than 6 hours, override with "happening now" so that
+ // the blinking red live dot is less prevalent.
+ const difference = endDate.getTime() - startDate.getTime();
+ const sixHoursDifference = 1000 * 60 * 60 * 6;
+ if (difference > sixHoursDifference) {
+ badgeKind = models.AppEventBadgeKind.happening;
+ }
+ }
+ return badgeKind;
+}
+/**
+ * Generates a list of all the possible date variants for a given app event. The native
+ * client will look at this list and determine which variant to display, based
+ * on the current time. The reason for sending down all variants is that the client will
+ * continue to update the display as time progresses, so it's feasible that the client will
+ * cross over from date variant to the next (eg. TOMORROW 12:00 PM -> TODAY 12:00 PM).
+ * @param badgeKind The kind of badge used
+ * @param startDate The start date of the app event
+ * @param endDate The end date of the app event, if any
+ */
+export function formattedDatesWithKind(objectGraph, badgeKind, startDate, endDate) {
+ const formattedDates = [];
+ const startMidnight = dateUtil.convertLocalDateToLocalMidnight(startDate);
+ if (isNothing(startMidnight)) {
+ // This is only possible if `startDate` is "nothing", which means we can't proceed to create formatted dates anyway.
+ return [];
+ }
+ // Event starts 7+ days from now
+ // Example: JAN 15
+ const introductionDateFormat = objectGraph.loc.string("AppEvents.FormattedDate.SevenDaysOrMore.DateFormat");
+ const introductionText = objectGraph.loc.uppercased(objectGraph.loc.formatDate(introductionDateFormat, startDate));
+ const introductionFormattedDate = new models.AppEventFormattedDate(false, undefined, introductionText !== null && introductionText !== void 0 ? introductionText : undefined, undefined, undefined);
+ formattedDates.push(introductionFormattedDate);
+ // Event starts 2-6 days from now
+ // Example: FRI 12:00 PM
+ const sixDaysPrior = new Date(startMidnight);
+ sixDaysPrior.setDate(sixDaysPrior.getDate() - 6);
+ const sixDaysPriorDateFormat = objectGraph.loc.string("AppEvents.FormattedDate.SixDaysOrLess.DateFormat");
+ const sixDaysPriorText = objectGraph.loc.formatDate(sixDaysPriorDateFormat, startDate);
+ const sixDaysPriorTextUppercased = objectGraph.loc.uppercased(sixDaysPriorText);
+ const sixDaysPriorFormattedDate = new models.AppEventFormattedDate(false, sixDaysPrior, sixDaysPriorTextUppercased !== null && sixDaysPriorTextUppercased !== void 0 ? sixDaysPriorTextUppercased : undefined, undefined, undefined);
+ formattedDates.push(sixDaysPriorFormattedDate);
+ // Event starts tomorrow
+ // Example: TOMORROW 12:00 PM
+ const oneDayPrior = new Date(startMidnight);
+ oneDayPrior.setDate(oneDayPrior.getDate() - 1);
+ const tomorrowSentence = objectGraph.loc.string("AppEvents.FormattedDate.Tomorrow");
+ const tomorrowText = objectGraph.loc.formatDateInSentence(tomorrowSentence, "j:mm", startDate);
+ const tomorrowTextUppercased = objectGraph.loc.uppercased(tomorrowText);
+ const tomorrowFormattedDate = new models.AppEventFormattedDate(false, oneDayPrior, tomorrowTextUppercased !== null && tomorrowTextUppercased !== void 0 ? tomorrowTextUppercased : undefined, undefined, undefined);
+ formattedDates.push(tomorrowFormattedDate);
+ // Event starts today
+ // If the event starts within 1 hour of midnight, we don't need to show this case
+ // as it will be superceded by the 1 hour countdown
+ // Example: TODAY 12:00 PM, TONIGHT 7:00 PM
+ if (startDate.getHours() > 1 || (startDate.getHours() === 1 && startDate.getMinutes() > 0)) {
+ let todaySentence;
+ if (startDate.getHours() >= 19) {
+ todaySentence = objectGraph.loc.string("AppEvents.FormattedDate.Tonight");
+ }
+ else {
+ todaySentence = objectGraph.loc.string("AppEvents.FormattedDate.Today");
+ }
+ const todayText = objectGraph.loc.formatDateInSentence(todaySentence, "j:mm", startDate);
+ const todayTextUppercased = objectGraph.loc.uppercased(todayText);
+ const todayFormattedDate = new models.AppEventFormattedDate(false, startMidnight !== null && startMidnight !== void 0 ? startMidnight : undefined, todayTextUppercased !== null && todayTextUppercased !== void 0 ? todayTextUppercased : undefined, undefined, undefined);
+ formattedDates.push(todayFormattedDate);
+ }
+ // Event is starting in 1 hour or less
+ // Example: STARTS IN 59 MIN / AVAILABLE IN 59 MIN
+ const oneHourPrior = new Date(startDate);
+ oneHourPrior.setHours(oneHourPrior.getHours() - 1);
+ let countdownStringKey;
+ switch (badgeKind) {
+ case models.AppEventBadgeKind.available:
+ countdownStringKey = "AppEvents.FormattedDate.AvailableIn.MinutesCountdown";
+ break;
+ case models.AppEventBadgeKind.happening:
+ case models.AppEventBadgeKind.live:
+ default:
+ countdownStringKey = "AppEvents.FormattedDate.StartsIn.MinutesCountdown";
+ break;
+ }
+ const oneHourPriorFormattedDate = new models.AppEventFormattedDate(false, oneHourPrior, undefined, startDate, countdownStringKey);
+ formattedDates.push(oneHourPriorFormattedDate);
+ // Event is happening now
+ // Example: LIVE, HAPPENING NOW, NOW AVAILABLE
+ let liveText;
+ let showLiveIndicator;
+ switch (badgeKind) {
+ case models.AppEventBadgeKind.available:
+ liveText = objectGraph.loc.string("AppEvents.FormattedDate.NowAvailable");
+ showLiveIndicator = false;
+ break;
+ case models.AppEventBadgeKind.happening:
+ liveText = objectGraph.loc.string("AppEvents.FormattedDate.HappeningNow");
+ showLiveIndicator = false;
+ break;
+ case models.AppEventBadgeKind.live:
+ default:
+ liveText = objectGraph.loc.string("AppEvents.FormattedDate.Live");
+ showLiveIndicator = true;
+ break;
+ }
+ const liveFormattedDate = new models.AppEventFormattedDate(showLiveIndicator, startDate, liveText, undefined, undefined);
+ formattedDates.push(liveFormattedDate);
+ // Event has ended
+ // Example: EVENT ENDED
+ if (endDate !== null) {
+ const eventEndedFormattedDate = new models.AppEventFormattedDate(false, endDate, objectGraph.loc.string("AppEvents.FormattedDate.EventEnded"), undefined, undefined);
+ formattedDates.push(eventEndedFormattedDate);
+ }
+ return formattedDates;
+}
+//# sourceMappingURL=app-event.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotion.js b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotion.js
new file mode 100644
index 0000000..84afb2e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotion.js
@@ -0,0 +1,120 @@
+import * as models from "../../api/models";
+import * as appEventDetail from "./app-event-detail";
+import * as contingentOfferDetail from "./contingent-offer-detail";
+import * as offerItemDetail from "./offer-item-detail";
+import * as appEventModel from "./app-event";
+import * as contingentOfferModel from "./contingent-offer";
+import * as offerItemModel from "./offer-item";
+/**
+ * @param data The MAPI data to determine the promotion type
+ * @returns The App Promotion type, or null if the type is unknown
+ */
+export function promotionTypeFromData(data) {
+ switch (data.type) {
+ case "app-events":
+ return models.AppPromotionType.AppEvent;
+ case "contingent-items":
+ return models.AppPromotionType.ContingentOffer;
+ case "offer-items":
+ return models.AppPromotionType.OfferItem;
+ default:
+ return null;
+ }
+}
+/**
+ * @param data The MAPI data to determine the promotion type
+ * @returns The target type for metrics events for the given app promotion
+ */
+export function metricsTargetTypeFromData(data) {
+ switch (data.type) {
+ case "app-events":
+ return "eventModule";
+ case "contingent-items":
+ return "module";
+ case "offer-items":
+ return "module";
+ default:
+ return null;
+ }
+}
+/**
+ * @param data The MAPI data to determine the MetricsKind
+ * @returns The metrics kind for metrics events for the given app promotion
+ */
+export function metricsKindFromData(data) {
+ switch (data.type) {
+ case "app-events":
+ return "inAppEvent";
+ case "contingent-items":
+ return "contingentPriceOffer";
+ case "offer-items":
+ return "winbackPriceOffer";
+ default:
+ return null;
+ }
+}
+/**
+ * Creates an app promotion (App Event or Contingent Offer) object or a Date, if the event should not yet be shown, from the given data.
+ * @param data The data blob
+ * @param parentAppData The related parent app of this app promotion. If not provided will be derived from `data`.
+ * @param hideLockupWhenNotInstalled Whether to hide the lockup chin when the app is installed
+ * @param includeClickAction Whether to generate a click action for the app promotion
+ * @param offerEnvironment The preferred environment for the offer
+ * @param offerStyle The preferred style of the offer
+ * @param includeCrossLinkTitles Whether the cross link titles will be displayed when the app is installed
+ * @param allowMissingParentApp Whether to still create the app event if the parent app is missing
+ * @param baseMetricsOptions The base metrics options
+ * @param allowEndedEvents Whether events in the past are allowed
+ * @param includeLockupClickAction Whether to include the click action for the lockup
+ * @param isArcadePage Whether or not this is presented on the Arcade page
+ * @param allowUnpublishedAppEventPreviews Whether or not to allow event previews
+ * @returns an AppPromotion, or a Date if the event's `promotionStartDate` is in the future.
+ */
+export function appPromotionOrDateFromData(objectGraph, data, parentAppData, hideLockupWhenNotInstalled, includeClickAction, offerEnvironment, offerStyle, includeCrossLinkTitles, baseMetricsOptions, allowEndedEvents, includeLockupClickAction, editorialKind, isArcadePage, allowUnpublishedAppEventPreviews) {
+ const promotionType = promotionTypeFromData(data);
+ const promotionBaseMetricsOptions = {
+ ...baseMetricsOptions,
+ targetType: metricsTargetTypeFromData(data),
+ };
+ switch (promotionType) {
+ case models.AppPromotionType.AppEvent:
+ return appEventModel.appEventOrPromotionStartDateFromData(objectGraph, data, parentAppData, hideLockupWhenNotInstalled, includeClickAction, offerEnvironment, offerStyle, includeCrossLinkTitles, promotionBaseMetricsOptions, allowEndedEvents, includeLockupClickAction, editorialKind, isArcadePage, allowUnpublishedAppEventPreviews);
+ case models.AppPromotionType.ContingentOffer:
+ return contingentOfferModel.contingentOfferFromData(objectGraph, data, parentAppData, offerEnvironment, offerStyle, promotionBaseMetricsOptions, includeLockupClickAction, isArcadePage);
+ case models.AppPromotionType.OfferItem:
+ return offerItemModel.offerItemFromData(objectGraph, data, parentAppData, offerEnvironment, offerStyle, promotionBaseMetricsOptions, includeLockupClickAction, isArcadePage);
+ default:
+ return null;
+ }
+}
+/**
+ * Create a flow action for navigating to the app promotion detail page.
+ * @param data The data blob
+ * @param parentAppData The associated parent app data
+ * @param appPromotion The source app promotion
+ * @param baseMetricsOptions The base metrics options
+ * @param animationBehavior The animation behaviour for presenting the modal page
+ * @param includeLockupClickAction Whether to generate a click action for the lockup
+ * @param referrerData Referrer data from an incoming deep link
+ */
+export function detailPageFlowActionFromData(objectGraph, data, parentAppData, appPromotion, baseMetricsOptions, animationBehavior, includeLockupClickAction, referrerData) {
+ const promotionType = promotionTypeFromData(data);
+ const promotionBaseMetricsOptions = {
+ ...baseMetricsOptions,
+ targetType: metricsTargetTypeFromData(data),
+ };
+ switch (promotionType) {
+ case models.AppPromotionType.AppEvent:
+ const appEvent = appPromotion;
+ return appEventDetail.appEventDetailPageFlowActionFromData(objectGraph, data, parentAppData, appEvent, promotionBaseMetricsOptions, animationBehavior, includeLockupClickAction, referrerData);
+ case models.AppPromotionType.ContingentOffer:
+ const contingentOffer = appPromotion;
+ return contingentOfferDetail.contingentOfferDetailPageFlowActionFromData(objectGraph, data, parentAppData, contingentOffer, promotionBaseMetricsOptions, animationBehavior, includeLockupClickAction, referrerData);
+ case models.AppPromotionType.OfferItem:
+ const offerItem = appPromotion;
+ return offerItemDetail.offerItemDetailPageFlowActionFromData(objectGraph, data, parentAppData, offerItem, promotionBaseMetricsOptions, animationBehavior, includeLockupClickAction, referrerData);
+ default:
+ return null;
+ }
+}
+//# sourceMappingURL=app-promotion.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotions-common.js b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotions-common.js
new file mode 100644
index 0000000..ffeac86
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotions-common.js
@@ -0,0 +1,385 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as platformAttributes from "../../foundation/media/platform-attributes";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import { Host, Parameters, Protocol } from "../../foundation/network/url-constants";
+import * as urls from "../../foundation/network/urls";
+import * as objects from "../../foundation/util/objects";
+import * as videoDefaults from "../constants/video-constants";
+import * as contentArtwork from "../content/artwork/artwork";
+import * as contentAttributes from "../content/attributes";
+import * as content from "../content/content";
+import * as lockups from "../lockups/lockups";
+import * as metricsBuilder from "../metrics/builder";
+import * as metricsHelpersClicks from "../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as metricsHelpersMisc from "../metrics/helpers/misc";
+import * as metricsHelpersModels from "../metrics/helpers/models";
+import * as appPromotionModel from "./app-promotion";
+import { formattedTextFromData } from "./contingent-offer";
+/**
+ * Convenience function for determining if app events are enabled.
+ */
+export function appEventsAreEnabled(objectGraph) {
+ return objectGraph.bag.enableAppEvents && (objectGraph.client.isiOS || objectGraph.client.isWeb);
+}
+/**
+ * Convenience function for determining if contingent items are enabled.
+ */
+export function appContingentItemsAreEnabled(objectGraph) {
+ const isContingentEnabledInBag = objectGraph.bag.enableContingentOffers;
+ return isContingentEnabledInBag && objectGraph.client.isiOS;
+}
+/**
+ * Convenience function for determining if offer items (Winback) items are enabled.
+ */
+export function appOfferItemsAreEnabled(objectGraph) {
+ return objectGraph.bag.enableOfferItems && objectGraph.client.isiOS;
+}
+/**
+ * Creates the artwork suitable for an app promotion
+ * @param data The data blob
+ * @param artworkKey The key used to derive the artwork from the data blob
+ */
+export function artworkFromData(objectGraph, data, artworkKey) {
+ const artworkData = mediaAttributes.attributeAsDictionary(data, artworkKey);
+ if (isNothing(artworkData)) {
+ return null;
+ }
+ const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, {
+ useCase: 0 /* content.ArtworkUseCase.Default */,
+ withJoeColorPlaceholder: true,
+ cropCode: "sr",
+ });
+ return artwork;
+}
+/**
+ * Creates the artwork suitable for an app promotion from the platform attributes
+ * @param data The data blob
+ * @param artworkKey The key used to derive the artwork from the data blob
+ */
+export function artworkFromPlatformData(objectGraph, data, artworkKey) {
+ const attributePlatform = contentAttributes.bestAttributePlatformFromData(objectGraph, data);
+ if (isNothing(attributePlatform)) {
+ return null;
+ }
+ const artworkData = platformAttributes.platformAttributeAsDictionary(data, attributePlatform, artworkKey);
+ if (isNothing(artworkData)) {
+ return null;
+ }
+ const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, {
+ useCase: 0 /* content.ArtworkUseCase.Default */,
+ withJoeColorPlaceholder: true,
+ cropCode: "sr",
+ });
+ return artwork;
+}
+/**
+ * Creates the video suitable for an app promotion
+ * @param objectGraph
+ * @param data The data blob
+ * @param videoKey The key used to derive the video from the data blob
+ * @param canPlayFullScreen Whether the video should support full-screen playback
+ * @param isFullPage whether this video is being used on the full page
+ */
+export function videoFromData(objectGraph, data, videoKey, canPlayFullScreen, isFullPage) {
+ // Preview artwork
+ const previewArtwork = artworkFromData(objectGraph, data, `${videoKey}.previewFrame`);
+ if (serverData.isNull(previewArtwork)) {
+ return null;
+ }
+ // Video URL
+ const videoUrl = mediaAttributes.attributeAsString(data, `${videoKey}.video`);
+ if (serverData.isNull(videoUrl)) {
+ return null;
+ }
+ const autoplayPlaybackControls = {
+ muteUnmute: true,
+ };
+ const configuration = {
+ allowsAutoPlay: true,
+ looping: true,
+ canPlayFullScreen: canPlayFullScreen,
+ playbackControls: isFullPage ? videoDefaults.standardControls(objectGraph) : {},
+ autoPlayPlaybackControls: isFullPage ? autoplayPlaybackControls : {},
+ };
+ const video = new models.Video(videoUrl, previewArtwork, configuration);
+ video.canPlayFullScreen = canPlayFullScreen;
+ video.allowsAutoPlay = true;
+ video.looping = true;
+ return video;
+}
+/**
+ * Creates the lockup for an app event or contingent offer
+ * @param objectGraph The object graph.
+ * @param promotionData The data blob
+ * @param parentAppData The related parent app of this app promotion
+ * @param title The title of the app promotion
+ * @param offerEnvironment The preferred environment for the offer
+ * @param offerStyle The preferred style of the offer
+ * @param includeCrossLinkTitles Whether the cross link titles will be displayed when the app is installed
+ * @param baseMetricsOptions The base metrics options for the lockup
+ * @param includeLockupClickAction Whether to generate a click action for the lockup
+ * @param referrerData Referrer data from an incoming deep link
+ * @param isArcadePage Whether or not this is presented on the Arcade page.
+ * @param includeModuleClickLocation Whether or not this to push the module location to the location tracker.
+
+ */
+export function lockupFromData(objectGraph, promotionData, parentAppData, title, offerEnvironment, offerStyle, includeCrossLinkTitles, baseMetricsOptions, includeLockupClickAction, referrerData, isArcadePage, includeModuleClickLocation) {
+ var _a, _b, _c;
+ if (isNothing(promotionData) || isNothing(parentAppData)) {
+ // if (serverData.isNullOrEmpty(promotionData) || serverData.isNullOrEmpty(parentAppData)) {
+ return null;
+ }
+ const promotionType = appPromotionModel.promotionTypeFromData(promotionData);
+ // Push a content location, so that the lockup action has both the containing card (eg. event module)
+ // lockup location included.
+ const contentMetricsOptions = {
+ ...baseMetricsOptions,
+ id: promotionData.id,
+ relatedSubjectIds: [parentAppData.id],
+ idType: "its_id",
+ };
+ const lockupMetrics = {
+ ...baseMetricsOptions,
+ id: parentAppData.id,
+ relatedSubjectIds: [parentAppData.id],
+ targetType: "lockup",
+ idType: "its_id",
+ kind: null,
+ softwareType: null,
+ title: (_a = mediaAttributes.attributeAsString(parentAppData, "name")) !== null && _a !== void 0 ? _a : "",
+ excludeAttribution: serverData.isNullOrEmpty(referrerData),
+ };
+ if (promotionType === models.AppPromotionType.AppEvent) {
+ contentMetricsOptions["inAppEventId"] = promotionData.id;
+ lockupMetrics["inAppEventId"] = promotionData.id;
+ }
+ // If our base metrics options are in fact content metrics options, we want to carry across
+ // the ID and ID type. This specifically caters for heros / editorial cards.
+ if (metricsHelpersModels.isContentMetricsOptions(baseMetricsOptions)) {
+ contentMetricsOptions.id = baseMetricsOptions.id;
+ contentMetricsOptions.idType = baseMetricsOptions.idType;
+ }
+ if (includeModuleClickLocation) {
+ const locationTitle = promotionType === models.AppPromotionType.ContingentOffer
+ ? (_b = formattedTextFromData(objectGraph, promotionData)) === null || _b === void 0 ? void 0 : _b.rawTitle
+ : mediaAttributes.attributeAsString(promotionData, "name");
+ metricsHelpersLocation.pushContentLocation(objectGraph, contentMetricsOptions, locationTitle !== null && locationTitle !== void 0 ? locationTitle : "");
+ }
+ const externalDeepLinkUrl = mediaAttributes.attributeAsString(promotionData, "deepLink");
+ const lockupOptions = {
+ metricsOptions: lockupMetrics,
+ artworkUseCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
+ externalDeepLinkUrl: externalDeepLinkUrl !== null && externalDeepLinkUrl !== void 0 ? externalDeepLinkUrl : undefined,
+ crossLinkSubtitle: includeCrossLinkTitles ? title : undefined,
+ offerEnvironment: offerEnvironment,
+ offerStyle: offerStyle,
+ skipDefaultClickAction: !includeLockupClickAction,
+ includeBetaApps: true,
+ referrerData: referrerData !== null && referrerData !== void 0 ? referrerData : undefined,
+ shouldHideArcadeHeader: objectGraph.featureFlags.isEnabled("hide_arcade_header_on_arcade_tab") && isArcadePage,
+ parentAppData: parentAppData,
+ useJoeColorIconPlaceholder: true,
+ overrideArtworkTextColorKey: "textColor4",
+ };
+ const resolvedData = promotionType === models.AppPromotionType.AppEvent ? parentAppData : promotionData;
+ const lockup = lockups.lockupFromData(objectGraph, resolvedData, lockupOptions);
+ if (includeModuleClickLocation) {
+ metricsHelpersLocation.popLocation(baseMetricsOptions.locationTracker);
+ }
+ if (serverData.isNull(lockup)) {
+ return null;
+ }
+ if (includeCrossLinkTitles) {
+ lockup.crossLinkTitle = (_c = objectGraph.loc.uppercased(lockup.title)) !== null && _c !== void 0 ? _c : undefined;
+ }
+ return lockup;
+}
+export function notificationConfigFromData(objectGraph, data, appEvent, baseMetricsOptions, includeScheduledAction) {
+ // If the event has already started, we cannot set a notification reminder
+ if (appEvent.startDate.getTime() <= Date.now()) {
+ return null;
+ }
+ if (isNothing(appEvent.lockup)) {
+ return null;
+ }
+ const title = objectGraph.loc.string("APP_EVENTS_NOTIFICATION_TITLE").replace("{appTitle}", appEvent.lockup.title);
+ const detail = objectGraph.loc.string("APP_EVENTS_NOTIFICATION_DETAIL").replace("{eventTitle}", appEvent.title);
+ const displayTime = appEvent.startDate;
+ const icon = appEvent.lockup.icon;
+ const artworkUrl = appEvent.lockup.icon.template
+ .replace("{w}", `${icon.width}`)
+ .replace("{h}", `${icon.height}`)
+ .replace("{c}", "wd") // iOS rounded corners
+ .replace("{f}", "png");
+ // Notification scheduled action
+ let scheduledAction;
+ if (includeScheduledAction) {
+ scheduledAction = new models.AlertAction("toast");
+ scheduledAction.title = objectGraph.loc.string("APP_EVENTS_NOTIFICATION_TOAST_TITLE");
+ scheduledAction.message = objectGraph.loc.string("APP_EVENTS_NOTIFICATION_TOAST_DETAIL");
+ scheduledAction.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://bell.fill");
+ }
+ // The below if statement can be removed in Sydro timeframe
+ // Notifications not authorized action
+ let notAuthorizedAction;
+ if (objectGraph.bag.newEventsForODJAreEnabled) {
+ // When we have ODJs active we send a metrics click event to signal the Alert button was tapped. ODJ picks this up as a signal to
+ // show a half/full sheet notifications upsell to the user
+ const notAuthorizedMetricsAction = new models.BlankAction();
+ // Schedule click data
+ const scheduleClickFieldsNotAuthed = metricsHelpersMisc.fieldsFromPageInformation(baseMetricsOptions.pageInformation);
+ scheduleClickFieldsNotAuthed["actionType"] = "notifyActivateNotificationsDisabled";
+ scheduleClickFieldsNotAuthed["location"] = metricsHelpersLocation.createContentLocation(objectGraph, {
+ ...baseMetricsOptions,
+ id: data.id,
+ }, "");
+ // We want to actively remove the topic from this click event so it doesn't leave the device and is only consumed by ODJ
+ scheduleClickFieldsNotAuthed["topic"] = "";
+ const scheduleClickDataNotAuthed = metricsBuilder.createMetricsClickData(objectGraph, appEvent.lockup.adamId, "lockup", scheduleClickFieldsNotAuthed);
+ notAuthorizedMetricsAction.actionMetrics.addMetricsData(scheduleClickDataNotAuthed);
+ notAuthorizedAction = notAuthorizedMetricsAction;
+ }
+ else {
+ const notAuthorizedAlertAction = new models.AlertAction("default");
+ notAuthorizedAlertAction.title = objectGraph.loc.string("APP_EVENTS_NOTIFICATION_NOT_AUTHORIZED_TITLE");
+ notAuthorizedAlertAction.message = objectGraph.loc.string("APP_EVENTS_NOTIFICATION_NOT_AUTHORIZED_DETAIL");
+ notAuthorizedAlertAction.isCancelable = true;
+ notAuthorizedAlertAction.buttonTitles = [objectGraph.loc.string("ACTION_SETTINGS")];
+ // NOTE: This URL only works on iOS. If this feature is expanded beyond iOS, this code will need to be split per-platform.
+ notAuthorizedAlertAction.buttonActions = [
+ new models.ExternalUrlAction("prefs:root=NOTIFICATIONS_ID&path=com.apple.AppStore", true),
+ ];
+ notAuthorizedAction = notAuthorizedAlertAction;
+ }
+ const failureAction = new models.AlertAction("default");
+ failureAction.title = objectGraph.loc.string("APP_EVENTS_NOTIFICATION_FAILURE_TITLE");
+ failureAction.message = objectGraph.loc.string("APP_EVENTS_NOTIFICATION_FAILURE_DETAIL");
+ failureAction.isCancelable = true;
+ // App launch trampoline URL
+ const appLaunchTrampolineUrl = new urls.URL()
+ .set("protocol", Protocol.storeKitUIServiceAppStore)
+ .param(Parameters.appId, appEvent.lockup.adamId)
+ .param(Parameters.bundleId, appEvent.lockup.bundleId)
+ .param(Parameters.appEventId, appEvent.appEventId);
+ appLaunchTrampolineUrl.host = Host.launchApp;
+ const externalDeepLinkUrl = mediaAttributes.attributeAsString(data, "deepLink");
+ if (isSome(externalDeepLinkUrl) && (externalDeepLinkUrl === null || externalDeepLinkUrl === void 0 ? void 0 : externalDeepLinkUrl.length) > 0) {
+ appLaunchTrampolineUrl.param(Parameters.appEventDeepLink, encodeURIComponent(externalDeepLinkUrl));
+ }
+ // Schedule click data
+ const scheduleClickFields = metricsHelpersMisc.fieldsFromPageInformation(baseMetricsOptions.pageInformation);
+ scheduleClickFields["actionType"] = "notifyActivate";
+ scheduleClickFields["location"] = metricsHelpersLocation.createContentLocation(objectGraph, {
+ ...baseMetricsOptions,
+ id: data.id,
+ }, "");
+ const scheduleClickData = metricsBuilder.createMetricsClickData(objectGraph, appEvent.lockup.adamId, "lockup", scheduleClickFields);
+ // Cancel schedule click data
+ const cancelScheduleClickFields = objects.shallowCopyOf(scheduleClickFields);
+ cancelScheduleClickFields["actionType"] = "notifyDeactivate";
+ const cancelScheduleClickData = metricsBuilder.createMetricsClickData(objectGraph, appEvent.lockup.adamId, "lockup", cancelScheduleClickFields);
+ return new models.AppEventNotificationConfig(data.id, title, detail, artworkUrl, displayTime, scheduledAction, notAuthorizedAction, failureAction, appLaunchTrampolineUrl.build(), scheduleClickData, cancelScheduleClickData);
+}
+/**
+ * Create a click action for navigating to the contingent offer detail page.
+ * @param data The data blob
+ * @param parentAppData The associated parent app data
+ * @param appPromotion The source app promotion
+ * @param baseMetricsOptions The base metrics options
+ * @param includeLockupClickAction Whether to generate a click action for the lockup
+ */
+export function detailPageClickActionFromData(objectGraph, data, parentAppData, appPromotion, baseMetricsOptions, includeLockupClickAction) {
+ const action = appPromotionModel.detailPageFlowActionFromData(objectGraph, data, parentAppData, appPromotion, baseMetricsOptions, "infer", includeLockupClickAction, null);
+ if (isNothing(action)) {
+ return undefined;
+ }
+ const clickOptions = {
+ id: data.id,
+ actionDetails: {
+ action: "Open",
+ contentType: appPromotionModel.metricsTargetTypeFromData(data),
+ },
+ relatedSubjectIds: [parentAppData.id],
+ ...baseMetricsOptions,
+ };
+ const promotionType = appPromotionModel.promotionTypeFromData(data);
+ if (promotionType === models.AppPromotionType.AppEvent) {
+ clickOptions["inAppEventId"] = data.id;
+ }
+ metricsHelpersClicks.addClickEventToAction(objectGraph, action, clickOptions);
+ return action;
+}
+/**
+ * Creates the app events or contingent offers objects from the given data
+ * @param objectGraph The object graph
+ * @param appPromotionDataItems The array of app event / contingent offer data blobs.
+ * @param parentAppData The data for the parent app, if any.
+ * @param hideLockupWhenNotInstalled If true, the lockup will be hidden when the app is not locally installed
+ * @param includeCrossLinkTitles Whether the cross link titles will be displayed when the app is installed
+ * @param baseMetricsOptions The base metrics options for the app promotions
+ * @param allowEndedEvents Whether or not ended events should be returned
+ * @param includeLockupClickAction Whether to generate a click action for the lockup
+ * @param isArcadePage Whether or not this is presented on the Arcade page
+ * @param allowUnpublishedAppEventPreviews Whether or not to allow app event previews
+ * @returns an DisplayableAppPromotions object including the relevant App Promotions, as well as an optional Date for when the next App Event should be visible.
+ */
+export function appPromotionsFromData(objectGraph, appPromotionDataItems, parentAppData = null, hideLockupWhenNotInstalled, includeCrossLinkTitles, baseMetricsOptions, allowEndedEvents, includeLockupClickAction, isArcadePage, allowUnpublishedAppEventPreviews) {
+ var _a;
+ const appPromotions = [];
+ let nextAppEventPromotionStartDate;
+ for (const data of appPromotionDataItems) {
+ const appPromotionOrDate = appPromotionModel.appPromotionOrDateFromData(objectGraph, data, parentAppData, hideLockupWhenNotInstalled, true, "light", "infer", includeCrossLinkTitles, baseMetricsOptions, allowEndedEvents, includeLockupClickAction, null, isArcadePage, allowUnpublishedAppEventPreviews);
+ if (serverData.isNull(appPromotionOrDate)) {
+ continue;
+ }
+ if (appPromotionOrDate instanceof Date) {
+ // Set the next event promotion start date if we don't yet have one, or it's sooner than the current one.
+ if (isNothing(nextAppEventPromotionStartDate) ||
+ appPromotionOrDate.getTime() < nextAppEventPromotionStartDate.getTime()) {
+ nextAppEventPromotionStartDate = appPromotionOrDate;
+ }
+ continue;
+ }
+ const appPromotionItem = appPromotionOrDate;
+ // Metrics
+ const impressionOptions = {
+ ...baseMetricsOptions,
+ id: data.id,
+ kind: appPromotionModel.metricsKindFromData(data),
+ targetType: appPromotionModel.metricsTargetTypeFromData(data),
+ title: (_a = appPromotionItem.title) !== null && _a !== void 0 ? _a : "",
+ softwareType: null,
+ };
+ const resolvedParentAppData = parentAppData !== null && parentAppData !== void 0 ? parentAppData : mediaRelationship.relationshipData(objectGraph, data, "app");
+ if (serverData.isDefinedNonNull(resolvedParentAppData)) {
+ impressionOptions.relatedSubjectIds = [resolvedParentAppData.id];
+ }
+ metricsHelpersImpressions.addImpressionFields(objectGraph, appPromotionItem, impressionOptions);
+ metricsHelpersLocation.nextPosition(impressionOptions.locationTracker);
+ appPromotions.push(appPromotionItem);
+ }
+ return {
+ appPromotions: appPromotions,
+ nextAppEventPromotionStartDate: nextAppEventPromotionStartDate,
+ };
+}
+/**
+ * Replaces keys inside a templated string with their computed values.
+ * @param templateString A templated string with keys that need to be replaced
+ * @param templateKeys A map of string keys to replacement strings
+ * @returns A filled out string with no keys
+ */
+export function replacingTemplatedKeys(templateString, templateKeys) {
+ let returnString = templateString !== null && templateString !== void 0 ? templateString : "";
+ Object.keys(templateKeys).forEach((element) => {
+ returnString = returnString.replace(element, templateKeys[element]);
+ });
+ return returnString;
+}
+// endregion
+//# sourceMappingURL=app-promotions-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotions-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotions-shelf.js
new file mode 100644
index 0000000..4949afa
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotions-shelf.js
@@ -0,0 +1,120 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaDataStructure from "../../foundation/media/data-structure";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import * as metricsHelpersImpressions from "../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as refresh from "../refresh/page-refresh-controller";
+import * as appPromotionsCommon from "./app-promotions-common";
+/**
+ * Builder for the app events shelf, on the product page.
+ * @param data The container data
+ * @param shelfMetrics The product page shelf metrics.
+ * @return The built shelf or null
+ */
+export function appPromotionsShelfForProductPage(objectGraph, data, shelfMetrics, refreshController) {
+ var _a;
+ if (!appPromotionsCommon.appEventsAreEnabled(objectGraph)) {
+ return null;
+ }
+ let eventAndOffersData = mediaRelationship.relationshipViewsCollection(data, "events-and-offers");
+ if (isNothing(eventAndOffersData) || eventAndOffersData.length === 0) {
+ eventAndOffersData = mediaRelationship.relationshipCollection(data, "app-events");
+ }
+ if (isNothing(eventAndOffersData) || eventAndOffersData.length === 0) {
+ return null;
+ }
+ const recoMetricsData = mediaRelationship.relationshipViewsContainer(data, "events-and-offers");
+ const metricsOptions = {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ recoMetricsData: (_a = mediaDataStructure.metricsFromMediaApiObject(recoMetricsData)) !== null && _a !== void 0 ? _a : undefined,
+ };
+ const hasOffers = eventAndOffersData.some((dataItem) => dataItem.type !== "app-events");
+ const titleKey = hasOffers ? "ProductPage.Section.AppEventsAndOffers.Title" : "ProductPage.Section.AppEvents.Title";
+ const shelfTitle = objectGraph.loc.string(titleKey);
+ const shelfId = hasOffers ? "EventsAndOffers" : "Events";
+ metricsHelpersLocation.pushContentLocation(objectGraph, {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ recoMetricsData: metricsOptions.recoMetricsData,
+ targetType: "swoosh",
+ id: shelfId,
+ idType: "none",
+ }, shelfTitle);
+ const displayableAppEvents = appPromotionsCommon.appPromotionsFromData(objectGraph, eventAndOffersData, data, !hasOffers, false, metricsOptions, false, false, false, false);
+ if (isSome(displayableAppEvents.nextAppEventPromotionStartDate)) {
+ refresh.addNextPreferredContentRefreshDate(displayableAppEvents.nextAppEventPromotionStartDate, refreshController);
+ }
+ const appEvents = displayableAppEvents.appPromotions;
+ if (appEvents.length === 0) {
+ metricsHelpersLocation.popLocation(metricsOptions.locationTracker);
+ return null;
+ }
+ const shelf = appEventsShelf(objectGraph, appEvents, shelfTitle);
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "swoosh", shelfId, "none", metricsOptions.recoMetricsData);
+ metricsHelpersLocation.popLocation(metricsOptions.locationTracker);
+ metricsHelpersLocation.nextPosition(metricsOptions.locationTracker);
+ return shelf;
+}
+/**
+ * Builder for the app events shelf, in a today article.
+ * @param objectGraph
+ * @param dataItems The array of data blobs
+ * @param metricsPageInformation The metrics page information.
+ * @param metricsLocationTracker The metrics location tracker.
+ * @param context
+ * @return The built shelf or null
+ */
+export function appEventsShelfForArticle(objectGraph, dataItems, metricsPageInformation, metricsLocationTracker, context) {
+ if (!appPromotionsCommon.appEventsAreEnabled(objectGraph)) {
+ return null;
+ }
+ if (serverData.isNullOrEmpty(dataItems)) {
+ return null;
+ }
+ const metricsOptions = {
+ pageInformation: metricsPageInformation,
+ locationTracker: metricsLocationTracker,
+ };
+ const displayableAppEvents = appPromotionsCommon.appPromotionsFromData(objectGraph, dataItems, null, false, false, metricsOptions, true, true, false, context.allowUnpublishedAppEventPreviews);
+ if (isSome(displayableAppEvents.nextAppEventPromotionStartDate) && isSome(context === null || context === void 0 ? void 0 : context.refreshController)) {
+ refresh.addNextPreferredContentRefreshDate(displayableAppEvents.nextAppEventPromotionStartDate, context === null || context === void 0 ? void 0 : context.refreshController);
+ }
+ const appEvents = displayableAppEvents.appPromotions;
+ if (appEvents.length === 0) {
+ return null;
+ }
+ const shelf = appEventsShelf(objectGraph, appEvents, undefined);
+ // Items should be centered in the article context.
+ shelf.isHorizontal = false;
+ // We don't really need this shelf impression, but without it the contained items
+ // won't be impressed as it is a horizontal shelf
+ const shelfMetricsOptions = {
+ ...metricsOptions,
+ id: "",
+ kind: null,
+ softwareType: null,
+ targetType: "swoosh",
+ title: "",
+ };
+ metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, shelfMetricsOptions);
+ return shelf;
+}
+/**
+ * Convenience function for creating a shelf from a list of appEvents
+ * @param objectGraph
+ * @param appEvents The array of app events
+ * @param title The title for the shelf
+ */
+function appEventsShelf(objectGraph, appEvents, title) {
+ const shelfType = "appPromotion";
+ const shelf = new models.Shelf(shelfType);
+ shelf.isHorizontal = true;
+ shelf.title = title;
+ shelf.items = appEvents;
+ return shelf;
+}
+// endregion
+//# sourceMappingURL=app-promotions-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/contingent-offer-detail.js b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/contingent-offer-detail.js
new file mode 100644
index 0000000..64e75db
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/contingent-offer-detail.js
@@ -0,0 +1,106 @@
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as color from "../../foundation/util/color-util";
+import * as objects from "../../foundation/util/objects";
+import * as metricsBuilder from "../metrics/builder";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as metricsHelpersPage from "../metrics/helpers/page";
+import { MetricsReferralContext } from "../metrics/metrics-referral-context";
+import * as appPromotionCommon from "./app-promotions-common";
+/**
+ * Create a flow action for navigating to the contingent offer detail page.
+ * @param data The data blob
+ * @param parentAppData The associated parent app data
+ * @param contingentOffer The source contingent offer
+ * @param baseMetricsOptions The base metrics options
+ * @param animationBehavior The animation behavior for presenting the modal page
+ * @param includeLockupClickAction Whether to generate a click action for the lockup
+ * @param referrerData Referrer data from an incoming deep link
+ */
+export function contingentOfferDetailPageFlowActionFromData(objectGraph, data, parentAppData, contingentOffer, baseMetricsOptions, animationBehavior, includeLockupClickAction, referrerData) {
+ const page = contingentOfferDetailPageFromData(objectGraph, data, parentAppData, contingentOffer, baseMetricsOptions, includeLockupClickAction, referrerData, false);
+ const action = new models.FlowAction("contingentOfferDetail");
+ action.title = contingentOffer.title;
+ action.pageData = page;
+ action.animationBehavior = animationBehavior;
+ if (baseMetricsOptions && baseMetricsOptions.pageInformation) {
+ action.referrerUrl = baseMetricsOptions.pageInformation.pageUrl;
+ }
+ return action;
+}
+/**
+ * Creates an contingent offer detail page
+ * @param objectGraph The object graph
+ * @param data The data blob
+ * @param parentAppData The data blob for the related parent app
+ * @param contingentOffer The source contingent offer
+ * @param baseMetricsOptions The base metrics options to use for the detail page
+ * @param includeLockupClickAction Whether to generate a click action for the lockup
+ * @param referrerData The referrer data
+ * @param isArcadePage Whether or not this is presented on the Arcade page
+ */
+export function contingentOfferDetailPageFromData(objectGraph, data, parentAppData, contingentOffer, baseMetricsOptions, includeLockupClickAction, referrerData, isArcadePage) {
+ var _a, _b;
+ const artwork = appPromotionCommon.artworkFromPlatformData(objectGraph, data, "productArtwork");
+ const copy = objects.shallowCopyOf(contingentOffer);
+ let mediaOverlayStyle = "dark";
+ let isArtworkDark = true;
+ if (serverData.isDefinedNonNull(artwork)) {
+ isArtworkDark = color.isDarkColor(artwork.backgroundColor);
+ mediaOverlayStyle = isArtworkDark ? "dark" : "light";
+ }
+ const pageInformation = metricsHelpersPage.pageInformationForAppPromotionDetailPage(objectGraph, models.AppPromotionType.ContingentOffer, data.id, parentAppData.id, referrerData, (_a = baseMetricsOptions.recoMetricsData) !== null && _a !== void 0 ? _a : null);
+ const metricsOptions = {
+ ...baseMetricsOptions,
+ pageInformation: pageInformation,
+ locationTracker: metricsHelpersLocation.newLocationTracker(),
+ };
+ const offerEnvironment = isArtworkDark ? "dark" : "light";
+ const lockup = appPromotionCommon.lockupFromData(objectGraph, data, parentAppData, (_b = copy.title) !== null && _b !== void 0 ? _b : undefined, offerEnvironment, "transparent", false, metricsOptions, includeLockupClickAction, referrerData, isArcadePage, false);
+ if (serverData.isNull(lockup)) {
+ return null;
+ }
+ copy.offerLockup = lockup;
+ copy.trunkAppIcon = contingentOffer.trunkAppIcon;
+ const page = new models.ContingentOfferDetailPage(copy, artwork !== null && artwork !== void 0 ? artwork : undefined, mediaOverlayStyle);
+ page.backButtonActionMetrics.addMetricsData(createButtonActionMetricsForTarget(objectGraph, "back", pageInformation, metricsOptions.locationTracker));
+ page.learnMoreActionMetrics.addMetricsData(createButtonActionMetricsForTarget(objectGraph, "LearnMore", pageInformation, metricsOptions.locationTracker, contingentOffer.learnMoreTitle));
+ page.closeButtonActionMetrics.addMetricsData(createButtonActionMetricsForTarget(objectGraph, "close", pageInformation, metricsOptions.locationTracker));
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, page, pageInformation, (fields) => {
+ if (serverData.isDefinedNonNullNonEmpty(referrerData)) {
+ MetricsReferralContext.shared.addReferralContextToMetricsFieldsIfNecessary(fields);
+ }
+ });
+ return page;
+}
+function createButtonActionMetricsForTarget(objectGraph, targetId, pageInformation, locationTracker, title) {
+ let actionType;
+ switch (targetId) {
+ case "LearnMore":
+ actionType = "navigate";
+ break;
+ case "back":
+ actionType = "back";
+ break;
+ case "close":
+ actionType = "dismiss";
+ break;
+ default:
+ break;
+ }
+ const eventFields = {
+ targetType: "button",
+ actionType,
+ targetId,
+ idType: undefined,
+ location: metricsHelpersLocation.createContentLocation(objectGraph, {
+ pageInformation: pageInformation,
+ locationTracker: locationTracker,
+ targetType: "button",
+ id: targetId,
+ }, title !== null && title !== void 0 ? title : targetId),
+ };
+ const event = metricsBuilder.createMetricsClickData(objectGraph, targetId, "button", eventFields);
+ return event;
+}
+//# sourceMappingURL=contingent-offer-detail.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/contingent-offer.js b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/contingent-offer.js
new file mode 100644
index 0000000..8cc3cd7
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/contingent-offer.js
@@ -0,0 +1,189 @@
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import * as appPromotionCommon from "./app-promotions-common";
+import * as offerFormatting from "../offers/offer-formatting";
+import * as offers from "../offers/offers";
+import * as color from "../../foundation/util/color-util";
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import { contentAttributeAsDictionary } from "../content/attributes";
+import * as content from "../content/content";
+/**
+ * Creates a contingent offer object.
+ * @param data The data blob
+ * @param parentAppData The related parent app of this app event. If not provided will be derived from `data`.
+ * @param offerEnvironment The preferred environment for the offer
+ * @param offerStyle The preferred style of the offer
+ * @param baseMetricsOptions The base metrics options
+ * @param allowEndedEvents Whether events in the past are allowed
+ * @param includeLockupClickAction Whether to include the click action for the lockup
+ * @param isArcadePage Whether or not this is presented on the Arcade page
+ * @returns an ContingentOffer or null
+ */
+export function contingentOfferFromData(objectGraph, data, parentAppData, offerEnvironment, offerStyle, baseMetricsOptions, includeLockupClickAction, isArcadePage) {
+ var _a, _b, _c;
+ if (data.type !== "contingent-items") {
+ return null;
+ }
+ // Artwork
+ const moduleArtwork = appPromotionCommon.artworkFromPlatformData(objectGraph, data, "lockupArtwork");
+ if (serverData.isNull(moduleArtwork)) {
+ return null;
+ }
+ const isArtworkDark = color.isDarkColor(moduleArtwork.backgroundColor);
+ const mediaOverlayStyle = isArtworkDark ? "dark" : "light";
+ // Labels
+ const badge = mediaAttributes.attributeAsString(data, "badge");
+ const subtitle = mediaAttributes.attributeAsString(data, "subtitle");
+ const label = mediaAttributes.attributeAsString(data, "label");
+ const additionalInfoLabel = objectGraph.loc.string("ContingentOffer.AdditionalInfoButton.Title");
+ if (isNothing(badge) || isNothing(label)) {
+ return null;
+ }
+ // The discounted offer
+ const branch = mediaRelationship.relationshipData(objectGraph, data, "branch");
+ if (isNothing(branch) || serverData.isNullOrEmpty(serverData.asDictionary(branch, "meta.contingentItemOffer"))) {
+ return null;
+ }
+ const formattedText = formattedTextFromData(objectGraph, data);
+ if (isNothing(formattedText) || isNothing(formattedText === null || formattedText === void 0 ? void 0 : formattedText.title)) {
+ return null;
+ }
+ // The Apps
+ const resolvedBranchAppData = mediaRelationship.relationshipData(objectGraph, data, "branch-app");
+ const supportsStreamlinedBuy = mediaAttributes.attributeAsBooleanOrFalse(resolvedBranchAppData, "supportsStreamlinedBuy");
+ const offerLockup = appPromotionCommon.lockupFromData(objectGraph, data, resolvedBranchAppData, "", offerEnvironment, offerStyle, false, baseMetricsOptions, includeLockupClickAction, null, isArcadePage, true);
+ if (isNothing(offerLockup)) {
+ return null;
+ }
+ // Trunk Lockup. The app you need to be subscribed to in order to get the discounted offer
+ const resolvedTrunkAppData = mediaRelationship.relationshipData(objectGraph, data, "trunk-app");
+ let trunkAppIcon;
+ if (isSome(resolvedTrunkAppData) && isSome(subtitle) && (subtitle === null || subtitle === void 0 ? void 0 : subtitle.length) > 0) {
+ trunkAppIcon =
+ (_a = appPromotionCommon.artworkFromPlatformData(objectGraph, resolvedTrunkAppData, "artwork")) !== null && _a !== void 0 ? _a : appPromotionCommon.artworkFromData(objectGraph, resolvedTrunkAppData, "artwork");
+ }
+ // When displaying a first party offer the trunk artwork is in a different location
+ if (isSome(subtitle) && (subtitle === null || subtitle === void 0 ? void 0 : subtitle.length) > 0) {
+ trunkAppIcon = (_b = firstPartyTrunkLockupFromData(objectGraph, data)) !== null && _b !== void 0 ? _b : trunkAppIcon;
+ }
+ // Offer Strings
+ const additionalInfo = formattedAdditionalInfoFromData(objectGraph, data, branch);
+ const contingentOffer = new models.ContingentOffer(moduleArtwork, mediaOverlayStyle, supportsStreamlinedBuy, additionalInfoLabel, formattedText.title, subtitle !== null && subtitle !== void 0 ? subtitle : undefined, (_c = formattedText.description) !== null && _c !== void 0 ? _c : undefined, label, badge, additionalInfo, trunkAppIcon !== null && trunkAppIcon !== void 0 ? trunkAppIcon : undefined, offerLockup);
+ /// The raw title is used for metrics purposes
+ contingentOffer.title = formattedText.rawTitle;
+ // Detail page click action
+ if (serverData.isDefinedNonNull(resolvedBranchAppData)) {
+ contingentOffer.clickAction = appPromotionCommon.detailPageClickActionFromData(objectGraph, data, resolvedBranchAppData, contingentOffer, baseMetricsOptions, includeLockupClickAction);
+ }
+ return contingentOffer;
+}
+/**
+ * Creates the Title and description of the contingent offer out of a templated string
+ * @param data A data blob for the contingent-item
+ * @returns Creates a formatted string of the tile including the subscription price and period
+ */
+export function formattedTextFromData(objectGraph, data) {
+ var _a, _b, _c;
+ const branch = mediaRelationship.relationshipData(objectGraph, data, "branch");
+ if (isNothing(branch)) {
+ return undefined;
+ }
+ const regularOffer = offers.offerDataFromData(objectGraph, branch);
+ const discountedOffer = serverData.asDictionary(branch, "meta.contingentItemOffer");
+ if (serverData.isNullOrEmpty(regularOffer) || serverData.isNullOrEmpty(discountedOffer)) {
+ return undefined;
+ }
+ // Price Strings
+ // Adding \u{2060} (Word Joiner) prevents the string from breaking on the slash character
+ const regularPrice = (_a = formattedPrice(objectGraph, serverData.asString(regularOffer, "recurringSubscriptionPeriod"), serverData.asNumber(regularOffer, "numOfPeriods"), serverData.asString(regularOffer, "priceFormatted"))) === null || _a === void 0 ? void 0 : _a.replace("/", "/\u{2060}");
+ const discountedPrice = (_b = formattedPrice(objectGraph, serverData.asString(discountedOffer, "recurringSubscriptionPeriod"), serverData.asNumber(discountedOffer, "numOfPeriods"), serverData.asString(discountedOffer, "priceFormatted"))) === null || _b === void 0 ? void 0 : _b.replace("/", "/\u{2060}");
+ if (serverData.isNullOrEmpty(regularPrice) || serverData.isNullOrEmpty(discountedPrice)) {
+ return null;
+ }
+ const title = mediaAttributes.attributeAsString(data, "name");
+ const titleKeys = {
+ "@@discountedPrice@@/@@recurringSubscriptionPeriod@@": discountedPrice,
+ "@@regularPrice@@/@@recurringSubscriptionPeriod@@": regularPrice,
+ "@@discountedPricePerRecurringSubscriptionPeriod@@": discountedPrice,
+ "@@regularPricePerRecurringSubscriptionPeriod@@": regularPrice,
+ };
+ let titleTemplate = title !== null && title !== void 0 ? title : "";
+ Object.keys(titleKeys).forEach((element) => {
+ titleTemplate = titleTemplate.replace(element, titleKeys[element]);
+ });
+ const htmlRegex = /<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>/g;
+ const rawTitle = titleTemplate.replace(htmlRegex, "");
+ const formattedTitle = new models.Paragraph(titleTemplate, "text/x-apple-as3-nqml", "appPromotionTitle");
+ const developerDetailText = (_c = mediaAttributes.attributeAsString(data, "description.standard")) !== null && _c !== void 0 ? _c : "";
+ const detailTemplate = objectGraph.loc.string("ContingentOffer.Description.Format");
+ const descriptionKeys = {
+ "@@BranchName@@": mediaAttributes.attributeAsString(branch, "name"),
+ "@@RegularPrice@@": regularPrice,
+ "@@DiscountedPrice@@": discountedPrice,
+ };
+ let description = detailTemplate;
+ Object.keys(descriptionKeys).forEach((element) => {
+ description = description.replace(element, descriptionKeys[element]);
+ });
+ const formattedDescription = [description, developerDetailText].join(" ");
+ return {
+ title: formattedTitle,
+ rawTitle,
+ description: formattedDescription,
+ };
+}
+/**
+ * Creates a readable subscription price
+ * @param recurringSubscriptionPeriod The subscription recurrence in the server format. (eg P1M)
+ * @param price A price string in a readable format like $3.99.
+ * @returns A formatted string eg $3.99/month
+ */
+function formattedPrice(objectGraph, recurringSubscriptionPeriod, numberOfPeriods, price) {
+ const subscriptionRecurrence = offerFormatting.subscriptionRecurrenceForServerRecurrence(objectGraph, recurringSubscriptionPeriod, numberOfPeriods);
+ if (isNothing(subscriptionRecurrence) || isNothing(price)) {
+ return undefined;
+ }
+ return offerFormatting.priceDurationString(objectGraph, subscriptionRecurrence.type, subscriptionRecurrence.periodDuration, price);
+}
+/**
+ * Creates the additional info text of the contingent offer out of a templated string
+ * @param data A data blob
+ * @param branch A data blob containing the offer also known as the branch
+ * @param trunk A data blob containing the required subscription app also known as the trunk
+ * @returns Creates a formatted paragraph containing the contents of the additional info page.
+ */
+function formattedAdditionalInfoFromData(objectGraph, data, branch) {
+ const skuNameTitle = "<b>" + mediaAttributes.attributeAsString(branch, "name") + "</b>";
+ const skuDescription = mediaAttributes.attributeAsString(branch, "description.standard") + "<br>";
+ const termsTitle = "<b>" + objectGraph.loc.string("ContingentOffer.Terms.Title") + "</b>";
+ const terms = mediaAttributes.attributeAsString(data, "additionalTerms");
+ return new models.Paragraph([skuNameTitle, skuDescription, termsTitle, terms].join("<br>"), "text/x-apple-as3-nqml");
+}
+/**
+ * If the contingent offer has a an associated trunk use that instead of the trunk-app
+ * @param data The data blob
+ * @returns Creates a lockup to use as the TrunkLockup
+ */
+function firstPartyTrunkLockupFromData(objectGraph, data) {
+ var _a, _b;
+ const firstPartyTrunkData = serverData.asInterface(data, "meta.associations.trunks");
+ if (isNothing(firstPartyTrunkData)) {
+ return null;
+ }
+ const firstPartyApp = firstPartyTrunkData.data[0];
+ const shouldUseTrunkArtwork = (_b = (_a = firstPartyApp === null || firstPartyApp === void 0 ? void 0 : firstPartyApp.meta) === null || _a === void 0 ? void 0 : _a["useTrunkArtwork"]) !== null && _b !== void 0 ? _b : false;
+ if (shouldUseTrunkArtwork) {
+ const editorialArtwork = contentAttributeAsDictionary(objectGraph, firstPartyApp, "editorialArtwork.brandLogo");
+ if (isNothing(editorialArtwork)) {
+ return null;
+ }
+ return content.artworkFromApiArtwork(objectGraph, editorialArtwork, {
+ useCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
+ style: "roundedRect",
+ });
+ }
+ return null;
+}
+//# sourceMappingURL=contingent-offer.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/offer-item-detail.js b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/offer-item-detail.js
new file mode 100644
index 0000000..ef400cb
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/offer-item-detail.js
@@ -0,0 +1,147 @@
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as objects from "../../foundation/util/objects";
+import * as metricsBuilder from "../metrics/builder";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as metricsHelpersPage from "../metrics/helpers/page";
+import { MetricsReferralContext } from "../metrics/metrics-referral-context";
+import * as appPromotionCommon from "./app-promotions-common";
+/**
+ * Create a flow action for navigating to the offer detail page.
+ * @param data The data blob
+ * @param parentAppData The associated parent app data
+ * @param offerItem The source offer object
+ * @param baseMetricsOptions The base metrics options
+ * @param animationBehavior The animation behavior for presenting the modal page
+ * @param includeLockupClickAction Whether to generate a click action for the lockup
+ * @param referrerData Referrer data from an incoming deep link
+ */
+export function offerItemDetailPageFlowActionFromData(objectGraph, data, parentAppData, offerItem, baseMetricsOptions, animationBehavior, includeLockupClickAction, referrerData) {
+ const page = offerItemDetailPageFromData(objectGraph, data, parentAppData, offerItem, baseMetricsOptions, includeLockupClickAction, referrerData, false);
+ const action = new models.FlowAction("offerItemDetail");
+ action.title = offerItem.title;
+ action.pageData = page;
+ action.animationBehavior = animationBehavior;
+ if (baseMetricsOptions && baseMetricsOptions.pageInformation) {
+ action.referrerUrl = baseMetricsOptions.pageInformation.pageUrl;
+ }
+ return action;
+}
+/**
+ * Creates an offer item detail page
+ * @param objectGraph The object graph
+ * @param data The data blob
+ * @param parentAppData The data blob for the related parent app
+ * @param offerItem The source offer
+ * @param baseMetricsOptions The base metrics options to use for the detail page
+ * @param includeLockupClickAction Whether to generate a click action for the lockup
+ * @param referrerData The referrer data
+ * @param isArcadePage Whether or not this is presented on the Arcade page
+ */
+export function offerItemDetailPageFromData(objectGraph, offerItemData, parentAppData, offerItem, baseMetricsOptions, includeLockupClickAction, referrerData, isArcadePage) {
+ var _a, _b;
+ let artwork = appPromotionCommon.artworkFromPlatformData(objectGraph, offerItemData, "productArtwork");
+ // rdar://126775681 (Remove force moduleArtwork to be null as MAPI still sends down artwork when it should not)
+ // The below `if` check should be uncommented when this is removed - it was commented as strict null checking is smart enough to know that `artwork` will always be null.
+ artwork = null;
+ const mediaOverlayStyle = "dark";
+ const isArtworkDark = true;
+ const includeBorderInDarkMode = false;
+ // if (serverData.isDefinedNonNullNonEmpty(artwork)) {
+ // isArtworkDark = color.isDarkColor(artwork?.backgroundColor);
+ // mediaOverlayStyle = isArtworkDark ? "dark" : "light";
+ // includeBorderInDarkMode = color.isDarkColor(artwork?.backgroundColor, 10);
+ // }
+ const copy = objects.shallowCopyOf(offerItem);
+ const pageInformation = metricsHelpersPage.pageInformationForAppPromotionDetailPage(objectGraph, models.AppPromotionType.OfferItem, offerItemData.id, parentAppData.id, referrerData, (_a = baseMetricsOptions.recoMetricsData) !== null && _a !== void 0 ? _a : null);
+ const metricsOptions = {
+ ...baseMetricsOptions,
+ pageInformation: pageInformation,
+ locationTracker: metricsHelpersLocation.newLocationTracker(),
+ };
+ const offerEnvironment = isArtworkDark ? "dark" : "light";
+ const lockup = appPromotionCommon.lockupFromData(objectGraph, offerItemData, parentAppData, (_b = copy.title) !== null && _b !== void 0 ? _b : undefined, offerEnvironment, "transparent", false, metricsOptions, includeLockupClickAction, referrerData, isArcadePage, false);
+ if (serverData.isNull(lockup)) {
+ return null;
+ }
+ copy.offerLockup = lockup;
+ const additionalInfoLabel = objectGraph.loc.string("Winback.AdditionalInfoButton.Title");
+ const iapData = mediaRelationship.relationshipData(objectGraph, offerItemData, "salables");
+ const redemptionExpirationDate = mediaAttributes.attributeAsString(offerItemData, "redemptionExpirationDate");
+ let additionalInfo;
+ if (isSome(redemptionExpirationDate) && isSome(iapData)) {
+ const endDate = new Date(redemptionExpirationDate);
+ additionalInfo = formattedAdditionalInfoFromData(objectGraph, offerItemData, iapData, endDate);
+ }
+ const page = new models.OfferItemDetailPage(copy, artwork !== null && artwork !== void 0 ? artwork : undefined, undefined, mediaOverlayStyle, includeBorderInDarkMode, additionalInfoLabel, additionalInfo);
+ page.backButtonActionMetrics.addMetricsData(createButtonActionMetricsForTarget(objectGraph, "back", pageInformation, metricsOptions.locationTracker));
+ page.learnMoreActionMetrics.addMetricsData(createButtonActionMetricsForTarget(objectGraph, "LearnMore", pageInformation, metricsOptions.locationTracker, additionalInfoLabel));
+ page.closeButtonActionMetrics.addMetricsData(createButtonActionMetricsForTarget(objectGraph, "close", pageInformation, metricsOptions.locationTracker));
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, page, pageInformation, (fields) => {
+ if (serverData.isDefinedNonNullNonEmpty(referrerData)) {
+ MetricsReferralContext.shared.addReferralContextToMetricsFieldsIfNecessary(fields);
+ }
+ });
+ return page;
+}
+function createButtonActionMetricsForTarget(objectGraph, targetId, pageInformation, locationTracker, title) {
+ let actionType;
+ switch (targetId) {
+ case "LearnMore":
+ actionType = "navigate";
+ break;
+ case "back":
+ actionType = "back";
+ break;
+ case "close":
+ actionType = "dismiss";
+ break;
+ default:
+ break;
+ }
+ const eventFields = {
+ targetType: "button",
+ actionType,
+ targetId,
+ idType: undefined,
+ location: metricsHelpersLocation.createContentLocation(objectGraph, {
+ pageInformation: pageInformation,
+ locationTracker: locationTracker,
+ targetType: "button",
+ id: targetId,
+ }, title !== null && title !== void 0 ? title : targetId),
+ };
+ const event = metricsBuilder.createMetricsClickData(objectGraph, targetId, "button", eventFields);
+ return event;
+}
+/**
+ * Creates the additional info text of the promotion out of a templated string
+ * @param data A data blob
+ * @param iapData A data blob containing the iap offer
+ * @returns Creates a formatted paragraph containing the contents of the additional info page.
+ */
+function formattedAdditionalInfoFromData(objectGraph, data, iapData, endDate) {
+ if (isNothing(iapData)) {
+ return undefined;
+ }
+ const skuName = mediaAttributes.attributeAsString(iapData, "name");
+ const skuNameFormatted = isSome(skuName) ? "<b>" + skuName + "</b>" : undefined;
+ const skuDescription = mediaAttributes.attributeAsString(iapData, "description.standard");
+ const skuDescriptionFormatted = isSome(skuDescription) ? skuDescription + "<br>" : undefined;
+ const termsTitle = "<b>" + objectGraph.loc.string("Promotion.Terms.Title") + "</b>";
+ let terms;
+ const redemptionDateFormat = objectGraph.loc.string("OfferItems.FormattedDate.RedemptionDate.DateFormat");
+ if (isSome(skuName) && isSome(endDate)) {
+ const templateKeyMap = {
+ "@@redemptionDate@@": objectGraph.loc.formatDate(redemptionDateFormat, endDate),
+ "@@skuName@@": skuName,
+ };
+ terms = appPromotionCommon.replacingTemplatedKeys(mediaAttributes.attributeAsString(data, "additionalTerms"), templateKeyMap);
+ }
+ const combinedStrings = [skuNameFormatted, skuDescriptionFormatted, termsTitle, terms].filter(isSome).join("<br>");
+ return new models.Paragraph(combinedStrings, "text/x-apple-as3-nqml");
+}
+//# sourceMappingURL=offer-item-detail.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/offer-item.js b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/offer-item.js
new file mode 100644
index 0000000..989ab6d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/offer-item.js
@@ -0,0 +1,206 @@
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import * as appPromotionCommon from "./app-promotions-common";
+import * as dateUtil from "../../foundation/util/date-util";
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as offerFormatting from "../offers/offer-formatting";
+import * as offers from "../offers/offers";
+import * as content from "../content/content";
+import * as contentArtwork from "../content/artwork/artwork";
+/**
+ * Creates an offer item object.
+ * @param offerItemData The data blob
+ * @param parentAppData The related parent app of this app event. If not provided will be derived from `data`.
+ * @param offerEnvironment The preferred environment for the offer
+ * @param offerStyle The preferred style of the offer
+ * @param baseMetricsOptions The base metrics options
+ * @param allowEndedEvents Whether events in the past are allowed
+ * @param includeLockupClickAction Whether to include the click action for the lockup
+ * @param isArcadePage Whether or not this is presented on the Arcade page
+ * @returns an Offer Item or null
+ */
+export function offerItemFromData(objectGraph, offerItemData, parentAppData, offerEnvironment, offerStyle, baseMetricsOptions, includeLockupClickAction, isArcadePage) {
+ var _a, _b, _c, _d, _e, _f;
+ if (offerItemData.type !== "offer-items") {
+ return null;
+ }
+ const kind = mediaAttributes.attributeAsString(offerItemData, "kind");
+ if (kind !== "resubscription" && kind !== "winback") {
+ // Only resubscriptions are supported
+ return null;
+ }
+ const isArtworkDark = false;
+ const mediaOverlayStyle = isArtworkDark ? "dark" : "light";
+ // The Offers
+ const discountedIAP = mediaRelationship.relationshipData(objectGraph, offerItemData, "salables");
+ if (isNothing(discountedIAP)) {
+ return null;
+ }
+ const discountedOffer = serverData.asDictionary(discountedIAP, "meta.discountOffer");
+ const regularOffer = offers.offerDataFromData(objectGraph, discountedIAP);
+ if (isNothing(discountedOffer)) {
+ return null;
+ }
+ const resolvedParentApp = (_a = parentAppData !== null && parentAppData !== void 0 ? parentAppData : mediaRelationship.relationshipData(objectGraph, offerItemData, "app")) !== null && _a !== void 0 ? _a : mediaRelationship.relationshipData(objectGraph, discountedIAP, "app");
+ const supportsStreamlinedBuy = mediaAttributes.attributeAsBooleanOrFalse(resolvedParentApp, "supportsStreamlinedBuy");
+ // Offer Validity
+ const endDateString = mediaAttributes.attributeAsString(offerItemData, "redemptionExpirationDate");
+ if (isNothing(endDateString) || !isOfferValid(endDateString)) {
+ return null;
+ }
+ const endDate = new Date(endDateString);
+ let formattedExpiryDateShortString = formattedExpiryDate(objectGraph, endDate);
+ if (isNothing(formattedExpiryDateShortString)) {
+ formattedExpiryDateShortString = objectGraph.loc.string("OfferItems.Available.Now");
+ }
+ // IAP Artwork
+ let iapArtwork = content.iconFromData(objectGraph, discountedIAP, {
+ useCase: 3 /* content.ArtworkUseCase.LockupIconLarge */,
+ withJoeColorPlaceholder: true,
+ overrideTextColorKey: "textColor4",
+ });
+ /// A fallback artwork for rare cases where no artwork is found.
+ /// This should not happen in production
+ if (serverData.isNullOrEmpty(iapArtwork)) {
+ const blackColor = {
+ type: "rgb",
+ red: 0.0 / 255,
+ green: 0.0 / 255,
+ blue: 0.0 / 255,
+ alpha: 1,
+ };
+ iapArtwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://questionmark.circle", 200, 200, blackColor);
+ iapArtwork.style = "iap";
+ }
+ const subscriptionRecurrence = offerFormatting.subscriptionRecurrenceForServerRecurrence(objectGraph, serverData.asString(discountedOffer, "recurringSubscriptionPeriod"), serverData.asNumber(discountedOffer, "numOfPeriods"));
+ const discountDuration = isSome(subscriptionRecurrence)
+ ? offerFormatting.durationCountString(objectGraph, subscriptionRecurrence.type, subscriptionRecurrence.periodDuration * subscriptionRecurrence.periodCount)
+ : undefined;
+ // Price Strings
+ // Adding \u{2060} (Word Joiner) prevents the string from breaking on the slash character
+ const regularPrice = (_b = formattedPrice(objectGraph, serverData.asString(regularOffer, "recurringSubscriptionPeriod"), serverData.asNumber(regularOffer, "numOfPeriods"), serverData.asString(regularOffer, "priceFormatted"))) === null || _b === void 0 ? void 0 : _b.replace("/", "/\u{2060}");
+ const discountedPrice = (_c = formattedPrice(objectGraph, serverData.asString(discountedOffer, "recurringSubscriptionPeriod"), serverData.asNumber(discountedOffer, "numOfPeriods"), serverData.asString(discountedOffer, "priceFormatted"))) === null || _c === void 0 ? void 0 : _c.replace("/", "/\u{2060}");
+ /// A string map of the templated string in an offer
+ /// Fallback to the templated string if the string does not exist
+ const redemptionDateFormat = objectGraph.loc.string("OfferItems.FormattedDate.RedemptionDate.DateFormat");
+ const templateKeyMap = {
+ "@@redemptionDate@@": objectGraph.loc.formatDate(redemptionDateFormat, endDate),
+ "@@skuName@@": (_d = mediaAttributes.attributeAsString(discountedIAP, "name")) !== null && _d !== void 0 ? _d : "@@skuName@@",
+ "@@discountedPrice@@": discountedPrice !== null && discountedPrice !== void 0 ? discountedPrice : "@@discountedPrice@@",
+ "@@regularPricePerDuration@@": regularPrice !== null && regularPrice !== void 0 ? regularPrice : "@@regularPricePerDuration@@",
+ "@@discountDuration@@": discountDuration !== null && discountDuration !== void 0 ? discountDuration : "@@discountDuration@@",
+ "@@payUpfrontPrice@@": (_e = serverData.asString(discountedOffer, "priceFormatted")) !== null && _e !== void 0 ? _e : "@@payUpfrontPrice@@",
+ };
+ // Labels
+ const badge = mediaAttributes.attributeAsString(offerItemData, "badge");
+ if (isNothing(badge)) {
+ return null;
+ }
+ const title = appPromotionCommon.replacingTemplatedKeys(mediaAttributes.attributeAsString(offerItemData, "title"), templateKeyMap);
+ const formattedTitle = new models.Paragraph(title, "text/x-apple-as3-nqml", "appPromotionTitle");
+ const subtitle = appPromotionCommon.replacingTemplatedKeys(mediaAttributes.attributeAsString(offerItemData, "subtitle"), templateKeyMap);
+ const description = appPromotionCommon.replacingTemplatedKeys((_f = mediaAttributes.attributeAsString(offerItemData, "details")) !== null && _f !== void 0 ? _f : "", templateKeyMap);
+ // Offer Lockup
+ let offerLockup;
+ if (serverData.isDefinedNonNull(resolvedParentApp)) {
+ offerLockup = appPromotionCommon.lockupFromData(objectGraph, offerItemData, resolvedParentApp, "", offerEnvironment, offerStyle, false, baseMetricsOptions, includeLockupClickAction, null, isArcadePage, true);
+ }
+ if (isNothing(offerLockup)) {
+ return null;
+ }
+ const offerItem = new models.OfferItem(null, null, mediaOverlayStyle, supportsStreamlinedBuy, formattedTitle, formattedExpiryDateShortString, subtitle, description, badge, endDate, iapArtwork, offerLockup);
+ offerItem.title = title;
+ // Detail page click action
+ if (serverData.isDefinedNonNull(resolvedParentApp)) {
+ offerItem.clickAction = appPromotionCommon.detailPageClickActionFromData(objectGraph, offerItemData, resolvedParentApp, offerItem, baseMetricsOptions, includeLockupClickAction);
+ }
+ return offerItem;
+}
+/**
+ * Creates a readable subscription price
+ * @param recurringSubscriptionPeriod The subscription recurrence in the server format. (eg P1M)
+ * @param numberOfPeriods The number of periods the subscription recurs for.
+ * @param price A price string in a readable format like $3.99.
+ * @returns A formatted string eg $3.99/month
+ */
+function formattedPrice(objectGraph, recurringSubscriptionPeriod, numberOfPeriods, price) {
+ const subscriptionRecurrence = offerFormatting.subscriptionRecurrenceForServerRecurrence(objectGraph, recurringSubscriptionPeriod, numberOfPeriods);
+ if (isNothing(subscriptionRecurrence) || isNothing(price)) {
+ return null;
+ }
+ return offerFormatting.priceDurationString(objectGraph, subscriptionRecurrence === null || subscriptionRecurrence === void 0 ? void 0 : subscriptionRecurrence.type, subscriptionRecurrence === null || subscriptionRecurrence === void 0 ? void 0 : subscriptionRecurrence.periodDuration, price);
+}
+/**
+ * Checks the validity of a app promotion based on end date
+ * @param endDateString A string representation of the end date
+ * @returns bool of if the offer is valid or not
+ */
+function isOfferValid(endDateString) {
+ if (isNothing(endDateString) || serverData.isNullOrEmpty(endDateString)) {
+ return false;
+ }
+ const endDate = new Date(endDateString);
+ if (serverData.isNull(endDate)) {
+ return false;
+ }
+ const todayDate = new Date();
+ const hasEventEnded = endDate.getTime() <= todayDate.getTime();
+ if (hasEventEnded) {
+ return false;
+ }
+ return true;
+}
+/**
+ * Creates the availability label for a app promotion
+ * @param startDate The start date
+ * @param endDate The end date
+ * @returns string of the format the date should be shown
+ */
+function formattedExpiryDate(objectGraph, endDate) {
+ if (isNothing(endDate)) {
+ return null;
+ }
+ const endMidnight = dateUtil.convertLocalDateToLocalMidnight(endDate);
+ const currentDate = new Date();
+ const daysLeft = dateUtil.numberOfDaysBetween(currentDate, endMidnight);
+ if (isNothing(daysLeft)) {
+ return null;
+ }
+ const isSameDate = dateUtil.areLocalDatesSameDay(currentDate, endDate);
+ if (daysLeft > 90) {
+ return objectGraph.loc.string("OfferItems.Available.Now");
+ }
+ // Event ends 5+ days from now
+ // Example: EXPIRES DEC 31
+ if (daysLeft > 5) {
+ const expiryDateFormat = currentDate.getFullYear() !== endDate.getFullYear()
+ ? objectGraph.loc.string("OfferItems.FormattedDate.NextYear.DateFormat")
+ : objectGraph.loc.string("OfferItems.FormattedDate.FiveDaysOrMore.DateFormat");
+ const expiryDateString = objectGraph.loc.uppercased(objectGraph.loc.formatDate(expiryDateFormat, endDate));
+ if (isNothing(expiryDateString)) {
+ return null;
+ }
+ return objectGraph.loc
+ .string("OfferItems.FormattedDate.FiveDaysOrMore.Title")
+ .replace("@@date@@", expiryDateString);
+ }
+ // Event ends 2-5 days from now
+ // Example: EXPIRES IN 3 Days
+ if (daysLeft > 1) {
+ const fiveDaysPriorDateFormat = objectGraph.loc
+ .string("OfferItems.FormattedDate.FiveDaysOrLess.Title")
+ .replace("@@count@@", objectGraph.loc.formattedCount(daysLeft));
+ return fiveDaysPriorDateFormat;
+ }
+ // Event ends 1 day from now
+ // Example: EXPIRES TOMORROW
+ if (daysLeft === 1 && !isSameDate) {
+ return objectGraph.loc.string("OfferItems.FormattedDate.Tomorrow.Title");
+ }
+ // Event ends 1 day from now
+ // Example: EXPIRES TONIGHT
+ return objectGraph.loc.string("OfferItems.FormattedDate.Today.Title");
+}
+//# sourceMappingURL=offer-item.js.map \ No newline at end of file