summaryrefslogtreecommitdiff
path: root/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/offer-item.js
blob: 989ab6d78635f8b5efda8b96bd36caf215ba98f5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
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