Skip to content
Open
130 changes: 104 additions & 26 deletions libs/blocks/ost/ost.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import ctaTextOption from './ctaTextOption.js';
import { getConfig, getLocale, getMetadata, loadScript, loadStyle } from '../../utils/utils.js';
import {
getConfig, getLocale, getMetadata, loadScript, loadStyle, createTag,
} from '../../utils/utils.js';
import { initService, loadMasComponent, getMasLibs, getMiloLocaleSettings, MAS_COMMERCE_SERVICE } from '../merch/merch.js';

export const AOS_API_KEY = 'wcms-commerce-ims-user-prod';
export const CHECKOUT_CLIENT_ID = 'creative';
Expand All @@ -8,12 +11,24 @@ const IMS_COMMERCE_CLIENT_ID = 'aos_milo_commerce';
const IMS_SCOPE = 'AdobeID,openid';
const IMS_ENV = 'prod';
const IMS_PROD_URL = 'https://auth.services.adobe.com/imslib/imslib.min.js';
const OST_SCRIPT_URL = 'https://mas.adobe.com/studio/ost/index.js';
const OST_STYLE_URL = 'https://mas.adobe.com/studio/ost/index.css';
const OST_SCRIPT_URL = '/studio/ost/index.js';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this change do?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nvrm, i see it enables maslibs on ost, cool

const OST_STYLE_URL = '/studio/ost/index.css';
/** @see https://git.corp.adobe.com/PandoraUI/core/blob/master/packages/react-env-provider/src/component.tsx#L49 */
export const WCS_ENV = 'PROD';
export const WCS_API_KEY = 'wcms-commerce-ims-ro-user-cc';
export const WCS_LANDSCAPE = 'PUBLISHED';
export const WCS_LANDSCAPE_DRAFT = 'DRAFT';
export const LANDSCAPE_URL_PARAM = 'commerce.landscape';
export const DEFAULTS_URL_PARAM = 'commerce.defaults';

function isMasDefaultsEnabled() {
const searchParameters = new URLSearchParams(window.location.search);
const defaults = searchParameters.get(DEFAULTS_URL_PARAM) || 'on';
return defaults === 'on';
}

let masCommerceService;
const masDefaultsEnabled = isMasDefaultsEnabled();

/**
* Maps Franklin page metadata to OST properties.
Expand All @@ -31,26 +46,45 @@ const priceDefaultOptions = {
exclusive: false,
};

const updateParams = (params, key, value) => {
if (value !== priceDefaultOptions[key]) {
const updateParams = (params, key, value, opts) => {
if (value !== opts[key]) {
params.set(key, value);
}
};

document.body.classList.add('tool', 'tool-ost');

/**
* Gets the base URL for loading Tacocat OST build file based on maslibs parameter
* @returns {string} Base URL for OST index.js
*/
export function getMasLibsBase(windowObj) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feels like the body of this method could be replaced with
getMasLibs().replace('/web-components/dist', '')

Please try it out, I'd like to avoid logic duplication if possible. getMasLibs() can be our single the source of truth

const urlParams = new URLSearchParams(windowObj.location.search);
const masLibs = urlParams.get('maslibs');

if (!masLibs || masLibs.trim() === '' || masLibs.trim() === 'main') return 'https://mas.adobe.com';

return getMasLibs().replace('/web-components/dist', '');
}

/**
* @param {Commerce.Defaults} defaults
*/
export const createLinkMarkup = (
export const createLinkMarkup = async (
defaults,
offerSelectorId,
type,
offer,
options,
promo,
country,
) => {
const isCta = !!type?.startsWith('checkout');
const cs = offer.customer_segment;
const ms = offer.market_segments[0];
const taxFlags = masDefaultsEnabled && masCommerceService
? await masCommerceService.resolvePriceTaxFlags(country, null, cs, ms)
: {};

const createHref = () => {
const params = new URLSearchParams([
Expand All @@ -77,11 +111,17 @@ export const createLinkMarkup = (
displayOldPrice,
forceTaxExclusive,
} = options;
updateParams(params, 'term', displayRecurrence);
updateParams(params, 'seat', displayPerUnit);
updateParams(params, 'tax', displayTax);
updateParams(params, 'old', displayOldPrice);
updateParams(params, 'exclusive', forceTaxExclusive);
const optsMasDefaults = masDefaultsEnabled ? {
...priceDefaultOptions,
seat: offer.customer_segment !== 'INDIVIDUAL',
tax: taxFlags.displayTax || priceDefaultOptions.tax,
exclusive: taxFlags.forceTaxExclusive || priceDefaultOptions.exclusive,
} : priceDefaultOptions;
updateParams(params, 'term', displayRecurrence, optsMasDefaults);
updateParams(params, 'seat', displayPerUnit, optsMasDefaults);
updateParams(params, 'tax', displayTax, optsMasDefaults);
updateParams(params, 'old', displayOldPrice, optsMasDefaults);
updateParams(params, 'exclusive', forceTaxExclusive, optsMasDefaults);
}
return `https://milo.adobe.com/tools/ost?${params.toString()}`;
};
Expand All @@ -102,36 +142,38 @@ export async function loadOstEnv() {
const commerceEnv = searchParameters.get('commerce.env');
if (wcsLandscape || commerceEnv) {
if (wcsLandscape) {
searchParameters.set('commerce.landscape', wcsLandscape);
searchParameters.set(LANDSCAPE_URL_PARAM, wcsLandscape);
searchParameters.delete('wcsLandscape');
}
if (commerceEnv?.toLowerCase() === 'stage') {
searchParameters.set('commerce.landscape', 'DRAFT');
searchParameters.set(LANDSCAPE_URL_PARAM, WCS_LANDSCAPE_DRAFT);
searchParameters.delete('commerce.env');
}
window.history.replaceState({}, null, `${window.location.origin}${window.location.pathname}?${searchParameters.toString()}`);
}
/* c8 ignore next */
const { initService, loadMasComponent, getMasLibs, getMiloLocaleSettings, MAS_COMMERCE_SERVICE } = await import('../merch/merch.js');
await initService(true, { 'allow-override': 'true' });
const attributes = { 'allow-override': 'true' };
if (masDefaultsEnabled) {
attributes['data-mas-ff-defaults'] = 'on';
}
await initService(true, attributes);
// Load commerce.js based on masLibs parameter
await loadMasComponent(MAS_COMMERCE_SERVICE);
masCommerceService = await loadMasComponent(MAS_COMMERCE_SERVICE);

// Get the exports - they might be in different places depending on how it was loaded
let Log;
let Defaults;
let resolvePriceTaxFlags;
if (getMasLibs() && window.mas?.commerce) {
// Loaded from external URL - check global scope
({ Log, Defaults, resolvePriceTaxFlags } = window.mas.commerce);
({ Log, Defaults } = window.mas.commerce);
} else {
// Loaded as module
({ Log, Defaults, resolvePriceTaxFlags } = await import('../../deps/mas/commerce.js'));
({ Log, Defaults } = await import('../../deps/mas/commerce.js'));
}

const defaultPlaceholderOptions = Object.fromEntries([
['term', 'displayRecurrence', 'true'],
['seat', 'displayPerUnit', 'true'],
['seat', 'displayPerUnit', masDefaultsEnabled ? null : 'true'],
['tax', 'displayTax'],
['old', 'displayOldPrice'],
].map(([key, targetKey, defaultValue = false]) => {
Expand Down Expand Up @@ -178,7 +220,7 @@ export async function loadOstEnv() {
window.history.replaceState({}, null, newURL);

const environment = searchParameters.get('env') ?? WCS_ENV;
const landscape = searchParameters.get('commerce.landscape') ?? WCS_LANDSCAPE;
const landscape = searchParameters.get(LANDSCAPE_URL_PARAM) ?? WCS_LANDSCAPE;
const owner = searchParameters.get('owner');
const referrer = searchParameters.get('referrer');
const repo = searchParameters.get('repo');
Expand Down Expand Up @@ -220,21 +262,23 @@ export async function loadOstEnv() {
}
}

const onSelect = (
const onSelect = async (
offerSelectorId,
type,
offer,
options,
promoOverride,
co,
) => {
log.debug(offerSelectorId, type, offer, options, promoOverride);
const link = createLinkMarkup(
const link = await createLinkMarkup(
Defaults,
offerSelectorId,
type,
offer,
options,
promoOverride,
co,
);

log.debug(`Use Link: ${link.outerHTML}`);
Expand All @@ -260,27 +304,61 @@ export async function loadOstEnv() {
defaultPlaceholderOptions,
wcsApiKey: WCS_API_KEY,
ctaTextOption,
resolvePriceTaxFlags,
resolvePriceTaxFlags: masCommerceService?.resolvePriceTaxFlags,
modalsAndEntitlements: true,
};
}

function addToggleSwitch(container, label, checked, onChange) {
const switchDiv = createTag('div', { class: 'spectrum-Switch' }, null, { parent: container });
const input = createTag('input', { type: 'checkbox', class: 'spectrum-Switch-input', id: 'gb-overlay-toggle' }, null, { parent: switchDiv });
createTag('span', { class: 'spectrum-Switch-switch' }, null, { parent: switchDiv });
createTag('label', { class: 'spectrum-Switch-label', for: 'gb-overlay-toggle' }, label, { parent: switchDiv });
input.checked = checked;
input.addEventListener('change', onChange);
}

function addToggleSwitches(el, ostEnv) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In https://github.com/adobecom/milo/pull/5193/files this function accepts more params, let's unify to avoid conflicts

const { base } = getConfig();
loadStyle(`${base}/blocks/graybox/switch.css`);
const toggleContainer = createTag('span', { class: 'toggle-switch' }, null, { parent: el });
addToggleSwitch(toggleContainer, 'Draft landscape offer', ostEnv.landscape === WCS_LANDSCAPE_DRAFT, (e) => {
const url = new URL(window.location.href);
if (e.target.checked) {
url.searchParams.set(LANDSCAPE_URL_PARAM, WCS_LANDSCAPE_DRAFT);
} else {
url.searchParams.delete(LANDSCAPE_URL_PARAM);
}
window.location.href = url.toString();
});
addToggleSwitch(toggleContainer, 'MAS defaults', masDefaultsEnabled, (e) => {
const url = new URL(window.location.href);
if (!e.target.checked) {
url.searchParams.set(DEFAULTS_URL_PARAM, 'off');
} else {
url.searchParams.delete(DEFAULTS_URL_PARAM);
}
window.location.href = url.toString();
});
}

export default async function init(el) {
el.innerHTML = '<div />';

loadStyle(OST_STYLE_URL);
loadStyle(`${getMasLibsBase(window)}${OST_STYLE_URL}`);
loadStyle('https://use.typekit.net/pps7abe.css');

const [ostEnv] = await Promise.all([
loadOstEnv(),
loadScript(OST_SCRIPT_URL),
loadScript(`${getMasLibsBase(window)}${OST_SCRIPT_URL}`),
]);

function openOst() {
window.ost.openOfferSelectorTool({
...ostEnv,
rootElement: el.firstElementChild,
});
addToggleSwitches(el, ostEnv);
}

if (ostEnv.aosAccessToken) {
Expand Down
17 changes: 10 additions & 7 deletions test/blocks/ost/ost.test.html.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const perpM2M = {
commitment: 'PERPETUAL',
name: 'Stock',
planType: 'M2M',
customer_segment: 'INDIVIDUAL',
market_segments: ['COM'],
};
const defaults = {
checkoutWorkflow: 'UCv3',
Expand Down Expand Up @@ -46,6 +48,7 @@ function createLink(params = {}) {
perpM2M,
params,
params.promo,
'US',
);
}

Expand Down Expand Up @@ -244,14 +247,14 @@ describe('OST: merch link creation', () => {
const type = types.checkoutUrl;

it('with default params', async () => {
const link = createLink({ type });
const link = await createLink({ type });
assertLink(link, perpM2M, { osi, type });
expect({ ...link.dataset }).to.eql({});
});

it('with promo and custom text', async () => {
const ctaText = texts.try;
const link = createLink({ ctaText, promo, type });
const link = await createLink({ ctaText, promo, type });
assertLink(link, perpM2M, { osi, promo, type }, ctaText);
});

Expand All @@ -260,7 +263,7 @@ describe('OST: merch link creation', () => {
const modal = 'd2p';
const entitlement = 'true';
const upgrade = 'false';
const link = createLink({ ctaText, modal, entitlement, upgrade, type });
const link = await createLink({ ctaText, modal, entitlement, upgrade, type });
assertLink(link, perpM2M, { osi, modal, entitlement, upgrade, type }, ctaText);
});
});
Expand All @@ -269,15 +272,15 @@ describe('OST: merch link creation', () => {
const type = types.price;

it('with default params', async () => {
const link = createLink({ type });
const link = await createLink({ type });
assertLink(link, perpM2M, { osi, type });
});

it('with default params from OST', async () => {
const link = createLink({
const link = await createLink({
type,
displayRecurrence: true,
displayPerUnit: true,
displayPerUnit: false,
displayTax: false,
displayOldPrice: false,
forceTaxExclusive: false,
Expand All @@ -290,7 +293,7 @@ describe('OST: merch link creation', () => {
const displayPerUnit = true;
const displayTax = true;
const forceTaxExclusive = true;
const link = createLink({
const link = await createLink({
displayRecurrence,
displayPerUnit,
displayTax,
Expand Down
Loading