const { computePosition, shift, flip, offset, autoUpdate } = window.FloatingUIDOM;
async function clearForMeGetIngredients(sku) {
const url = `https://clickable-api.clearforme.com/api/app/products/details/v2?clientname=credo&sku=${sku}`;
const headers = {
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1YzAxMjk1ODhmN2M0MjJiYThmZDlkYjAiLCJzdG9yZVVybCI6Imh0dHBzOi8vY3JlZG9iZWF1dHkuY29tL3Byb2R1Y3RzLyIsImlhdCI6MTY5MTE2NzgxN30.Sn94-NFiRt1A_YKPc7_-7tD_HOWu0sNKQKr8sO-UfVg'
};
const response = await fetch(url, {headers});
if (response.status !== 200) return null;
return await response.json();
}
async function clearForMeGetIngredientDetails(cfmIngredientId, productIngredientId) {
const url = `https://clickable-api.clearforme.com/api/app/ingredients/definition/v2?clientname=credo&productIngredientId=${productIngredientId}&cfmIngredientId=${cfmIngredientId}`;
const headers = {
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1YzAxMjk1ODhmN2M0MjJiYThmZDlkYjAiLCJzdG9yZVVybCI6Imh0dHBzOi8vY3JlZG9iZWF1dHkuY29tL3Byb2R1Y3RzLyIsImlhdCI6MTY5MTE2NzgxN30.Sn94-NFiRt1A_YKPc7_-7tD_HOWu0sNKQKr8sO-UfVg'
};
const response = await fetch(url, { headers });
if (response.status !== 200) return null;
return await response.json();
}
function trackButtonForTooltip(button, tooltipSelector, elementName) {
const tooltip = document.querySelector(tooltipSelector);
if (!button || !tooltip) {
return
}
const offsetAmount = 6.5;
const updatePosition = () => {
computePosition(button, tooltip, {
placement: 'bottom',
middleware: [
flip(),
shift(),
offset(offsetAmount)
]
}).then(({ x, y, placement, middlewareData }) => {
Object.assign(tooltip.style, {
left: `${x}px`,
top: `${y}px`,
});
});
document.addEventListener(`modal-event-${elementName}-close`, (e) => {
tooltip.style.display = 'none';
tooltip.style.visibility = 'hidden';
});
document.addEventListener(`modal-event-${elementName}-open`, (e) => {
tooltip.style.display = 'block';
tooltip.style.visibility = 'visible';
});
};
let updateCleanup;
// Calculate position initially so that it can be displayed in the right position programatically from other events
updateCleanup = autoUpdate(button, tooltip, updatePosition)
button.addEventListener('click', (e) => {
updateCleanup = autoUpdate(button, tooltip, updatePosition);
requestAnimationFrame(async () => {
// Tooltip logic
const toolTipIsDisabled = tooltip.classList.contains('disabled');
if (!toolTipIsDisabled){
let closeList = ['#support-tooltip', '#chat-unavailable-tooltip', '#chat-unavailable-tooltip', '#account-login-tooltip', 'cart-drawer'];
let fCloseList = closeList.filter(m => m !== elementName);
for (var i = fCloseList.length - 1; i >= 0; i--) {
document.querySelector(fCloseList[i]).style.display = 'none';
document.querySelector(fCloseList[i]).style.visibility = 'hidden';
}
tooltip.style.display = 'block';
tooltip.style.visibility = 'visible';
}
// Force loading state
tooltip.classList.add('tooltip-is-loading');
const detailsDiv = tooltip.querySelector('.ingredient-details');
const title = tooltip.querySelector('.ingredient-title');
title.textContent = '';
detailsDiv.innerHTML = "
";
// Fetch details
const cfmIngredientId = button.getAttribute('data-cfm');
const productIngredientId = button.getAttribute('data-product');
const details = await clearForMeGetIngredientDetails(cfmIngredientId, productIngredientId);
// Remove loading state
tooltip.classList.remove('tooltip-is-loading');
// Update title
title.textContent = details.ingredientName || '';
// Remove loading state
detailsDiv.innerHTML = '';
if (details.definition) {
// Insert 'What it is' and its description
const whatIsItEyebrow = document.createElement('p');
whatIsItEyebrow.className = 'ingredient-eyebrow';
whatIsItEyebrow.textContent = 'What it is';
detailsDiv.appendChild(whatIsItEyebrow);
const whatIsItText = document.createElement('p');
whatIsItText.className = 'ingredient-details-text';
whatIsItText.textContent = details.definition;
detailsDiv.appendChild(whatIsItText);
}
if (details.functions && Array.isArray(details.functions) && details.functions.length > 0) {
const functionEyebrow = document.createElement('p');
functionEyebrow.className = 'ingredient-eyebrow';
functionEyebrow.textContent = 'Purpose';
detailsDiv.appendChild(functionEyebrow);
const functionText = document.createElement('p');
functionText.className = 'ingredient-details-text';
functionText.textContent = details.functions.map(f => f.attribute).join(', ');
detailsDiv.appendChild(functionText);
}
if (details.commonNames && Array.isArray(details.commonNames) && details.commonNames.length > 0) {
const commonNameEyebrow = document.createElement('p');
commonNameEyebrow.className = 'ingredient-eyebrow';
commonNameEyebrow.textContent = 'Commonly Called';
detailsDiv.appendChild(commonNameEyebrow);
const commonNameText = document.createElement('p');
commonNameText.className = 'ingredient-details-text';
commonNameText.textContent = details.commonNames.join(', ');
detailsDiv.appendChild(commonNameText);
}
document.addEventListener('mousemove', trackMouse);
});
});
const trackMouse = (e) => {
const buttonRect = button.getBoundingClientRect();
const tooltipRect = tooltip.getBoundingClientRect();
const isWithinExtendedRect = e.clientX > tooltipRect.left && e.clientX < tooltipRect.right && e.clientY > buttonRect.top && e.clientY < tooltipRect.bottom;
if (!isWithinExtendedRect) {
tooltip.style.display = 'none';
tooltip.style.visibility = 'hidden';
document.removeEventListener('mousemove', trackMouse);
if (updateCleanup) {
updateCleanup();
updateCleanup = null;
}
}
};
}
class ProductIngredients extends HTMLElement {
constructor() {
super();
}
async connectedCallback() {
const sku = this.getAttribute('product-sku');
const data = await clearForMeGetIngredients(sku);
if (data && data['productIngredients'] && data['productIngredients'][0]['ingredientGroup']) {
this.renderIngredients(data['productIngredients'], data['productDetails']['additionalDetails']);
} else {
// if CFM failed check if we have backup data and try rendering that
const backupData = document.querySelector('#productIngredientBackupValue')
if (backupData) {
this.innerHTML = backupData.innerHTML;
// remove the "click to learn more" text since it doesn't work
const clickForMore = document.querySelector('#ingredientsClickForMoreText')
if (clickForMore) {
clickForMore.style.display = 'none';
}
} else {
// bail
this.innerHTML = 'Error loading ingredients.';
}
}
}
async renderIngredients(productIngredients, additionalDetails) {
let ingredientDivs = '';
for (let i = 0; i < productIngredients.length; i++) {
if (!Array.isArray(productIngredients[i]['ingredientGroup']) || !productIngredients[i]['ingredientGroup'].length) {
continue;
}
const ingredientGroupDivs = productIngredients[i]['ingredientGroup'].map(ingredientGroup => {
const toTitleCase = (str) => {
return str.toLowerCase().replace(/(^|\s|-)(.)/g, (match, separator, letter) => {
return separator + letter.toUpperCase();
});
};
const titleCaseComponent = toTitleCase(productIngredients[i].component);
const ingredientHeader = ``;
const ingredientSpans = ingredientGroup.ingredients.map(item => {
const name = item.specialCharacter ? `${item.ingredientName}${item.specialCharacter}` : item.ingredientName;
return `
${name}`;
}).join(', ');
return `
${ingredientHeader}${ingredientSpans}
`;
}).join('');
ingredientDivs += ingredientGroupDivs;
}
const additionalInfo = additionalDetails ? `
${additionalDetails}` : '';
this.innerHTML = `${ingredientDivs}${additionalInfo}`;
// Attach click listeners
this.querySelectorAll('.clickable-ingredient').forEach(span => {
trackButtonForTooltip(span, '#ingredient-tooltip');
});
}
}
customElements.define('product-ingredients', ProductIngredients);