//import { XanoClient, XanoLocalStorage, XanoSessionStorage, XanoObjectStorage } from '@xano/js-sdk'; //import { restartWebflow } from '@finsweet/ts-utils'; import _ from 'lodash'; import type { CMSFilters } from './types/CMSFilters'; import type { CMSList } from './types/CMSList'; import type { Product, Input, FrontFilterObject, //Expression, // GroupExpression, // Statement, // Filters, } from './types/jimmyTypes'; window.onload = async () => { Wized.request.awaitAllPageLoad(async () => { const userData: { email: string; markup: number; name: string; p: string } = await Wized.data.get('r.5.d'); const userPermission = userData.p ? userData.p : 'n'; const { markup } = userData; const authToken: string = await Wized.data.get('c.token'); // console.log(userPermission); // const xano = new XanoClient({ // apiGroupBaseUrl: 'https://xebp-tgjl-md5j.n7.xano.io/api:8Vr1o0ps', // storage: new XanoObjectStorage(), // }); // xano.setDataSource('live'); // xano // .post( // '/diamonds_2', // JSON.parse( // '{"query":{"page":1,"per_page":75,"sort":[{"sortBy":"diamonds.priceR","orderBy":"asc"}],"expression":[{"or":true,"statement":{"left":{"tag":"col","operand":"diamonds.carat"},"op":">=","right":{"operand":"1"}}},{"or":false,"statement":{"left":{"tag":"col","operand":"diamonds.diamondType"},"op":"=","right":{"operand":"Loose Natural Diamond"}}},{"or":false,"statement":{"left":{"tag":"col","operand":"diamonds.priceR"},"op":">","right":{"operand":"0"}}}]}}' // ) // ) // .then((response) => { // const body = response.getBody(); // const headers = response.getHeaders(); // const statusCode = response.getStatusCode(); // }); const params = new URLSearchParams(location.search); const currentDiamondType = params.get('diamondType'); // SORT + FILTER + PAGINATION //page const pageElement = { page: 1, per_page: 75, }; //sort const sortElement = { sort: [ { sortBy: `diamonds.${userPermission === 'n' ? 'carat' : 'priceR'}`, orderBy: 'asc', }, ], }; let diamondType = !currentDiamondType ? 'Loose Lab Diamond' : currentDiamondType; let sortChange = false; // sort initial state let typeChange = false; // type initial state // const priceRange = document.querySelector( // '[fs-rangeslider-element="wrapper-3"]' // ); //selectors const labButton = document.querySelector('[data-element="type-lab"]'); const naturalButton = document.querySelector('[data-element="type-natural"]'); const switcher = document.querySelector('[switcher="switcher"]'); const totalCountElement = document.querySelector('[data-element="total"]'); const sortPriceDesc = document.querySelector( '[fs-cmssort-field="price-desc"]' ); const sortPriceAsc = document.querySelector('[fs-cmssort-field="price-asc"]'); const sortCaratDesc = document.querySelector( '[fs-cmssort-field="carat-desc"]' ); const sortCaratAsc = document.querySelector('[fs-cmssort-field="carat-asc"]'); const sortLabel = document.querySelector( '[fs-cmssort-element="dropdown-label"]' ); const blockDiv = document.querySelector('[data-element="block"]'); //admin view if (userPermission !== 'n' && blockDiv && sortLabel) { blockDiv.style.display = 'none'; //priceRange.classList.remove('hide'); sortLabel.textContent = 'Price (1 → 9)'; sortPriceAsc?.classList.remove('hide'); sortPriceDesc?.classList.remove('hide'); sortCaratAsc?.classList.remove('top-element'); sortPriceAsc?.classList.add('top-element'); } if (sortPriceDesc) sortPriceDesc.addEventListener('click', () => setSort('priceR', 'desc')); if (sortPriceAsc) sortPriceAsc.addEventListener('click', () => setSort('priceR', 'asc')); if (sortCaratDesc) sortCaratDesc.addEventListener('click', () => setSort('carat', 'desc')); if (sortCaratAsc) sortCaratAsc.addEventListener('click', () => setSort('carat', 'asc')); if (switcher) currentDiamondType === 'Loose Natural Diamond' ? switcher.classList.add('on-natural') : switcher.classList.remove('on-natural'); const constantStatements = [ { or: false, statement: { left: { tag: 'col', operand: 'diamonds.priceR', }, op: '>', right: { operand: '0', }, }, }, { type: 'group', group: { expression: [ { or: false, statement: { left: { tag: 'col', operand: 'diamonds.labName', }, op: '!=', right: { operand: 'CGL', }, }, }, { or: false, statement: { left: { tag: 'col', operand: 'diamonds.labName', }, op: '!=', right: { operand: 'EGL', }, }, }, { or: false, statement: { left: { tag: 'col', operand: 'diamonds.labName', }, op: '!=', right: { operand: 'GCAL', }, }, }, { or: false, statement: { left: { tag: 'col', operand: 'diamonds.labName', }, op: '!=', right: { operand: 'GSI', }, }, }, { or: false, statement: { left: { tag: 'col', operand: 'diamonds.labName', }, op: '!=', right: { operand: 'HRD', }, }, }, ], }, }, ]; const typeStatement = { or: false, statement: { left: { tag: 'col', operand: 'diamonds.diamondType', }, op: '=', right: { operand: diamondType, }, }, }; // const filterElement = { // expression: [ // { // or: false, // statement: { // left: { // tag: 'col', // operand: 'diamonds.carat', // }, // op: '>=', // right: { // operand: '0', // }, // }, // }, // typeStatement, // ...constantStatements, // ], // }; // const finalFilter: Input = { query: { ...pageElement, ...sortElement, ...filterElement } }; const emptyStatement = { expression: [ { or: false, statement: { left: { tag: 'col', operand: 'diamonds.carat', }, op: '>', right: { operand: '0', }, }, }, typeStatement, ...constantStatements, ], }; const finalFilter: Input = { query: { ...pageElement, ...sortElement, ...emptyStatement } }; const emptyFilter: Input = _.cloneDeep(finalFilter); window.fsAttributes = window.fsAttributes || []; window.fsAttributes.push([ 'cmsload', async (listInstances: CMSList[]) => { // Get the list instance const [listInstance] = listInstances; // Save a copy of the template const [firstItem] = listInstance.items; const itemTemplateElement = firstItem.element; const loader = document.querySelector('[data-element="loader"]'); //update stones function const updateProducts = async (finalFilter: Input, update: boolean) => { //Show loader if (loader && update) loader.style.display = 'flex'; // Fetch external data if (update && finalFilter.query.page) finalFilter.query.page = 1; if (update && finalFilter.query.per_page) finalFilter.query.per_page = 75; //console.log(finalFilter); const api = await fetchProducts(finalFilter); const fetch: { items: Product[]; page: number; total: number } = { items: api.items, page: api.nextPage, total: api.itemsTotal, }; const products = fetch.items; const nextPage = fetch.page; const totalItems = fetch.total; if (totalCountElement) totalCountElement.innerText = new Intl.NumberFormat('en-US', { style: 'decimal', useGrouping: true, minimumFractionDigits: 0, }).format(totalItems); // Create the new items const newItems = products.map((product: Product) => createItem(product, itemTemplateElement) ); // Remove existing items if (update) { listInstance.clearItems(); //listInstance.originalItemsPerPage = 77; } // Add new items to the memory await listInstance.addItems(newItems); // listInstance.paginationActive = true; // listInstance.totalPages = 5; if (update) listInstance.itemsPerPage = 25; // listInstance.originalItemsPerPage = 25; // listInstance.restartWebflow = true; // listInstance.paginationNext = document.querySelector( // '[data-element="load-more"]' // ); listInstance.renderItems(false, false); const showStatus = nextPage === null ? false : true; const showLoadButton = (show: boolean) => { listInstance.displayElement('paginationNext', show); }; showLoadButton(showStatus); //Hide loader if (loader) loader.style.display = 'none'; }; //intersection test // const callback = ()=>{ // console.log(1); // } // const options = {} // const japi = new IntersectionObserver(callback, options); // japi.observe; // initial stones loading updateProducts(finalFilter, true); let isEmpty = false; //let notEmpty = false; window.fsAttributes.push([ 'cmsfilter', (filterInstances: CMSFilters[]) => { const [filterInstance] = filterInstances; const { filtersData } = filterInstance; // Listenes to renders on page (filter changes in particular) let oldFilterChange; let filterChange; listInstance.on('renderitems', async () => { // fsAttributes.rangeslider.destroy(); // fsAttributes.rangeslider.init(); const frontFilterObjects = filtersData .filter((f) => { return f.values.size > 0; }) .map((f) => { return { key: f.originalFilterKeys[0], values: [...f.values], //Array.from(f.values) }; }); console.log(filtersData); filterChange = frontFilterObjects; if (!_.isEqual(filterChange, oldFilterChange) || sortChange || typeChange) { if (frontFilterObjects.length === 0) { isEmpty = true; } else { isEmpty = false; } //checks filters exist or a sort has changed //let oldFilter; oldFilterChange = _.cloneDeep(filterChange); if (frontFilterObjects.length > 0) { const statementsArray = frontFilterObjects .filter((object) => { if ( object.key !== 'shape' && object.key !== 'labName' && object.key !== 'shapeHidden' && object.key !== 'labType' ) { return true; } }) .map((object) => createStatement(object, false, '=')) .flat(); //console.log('statements:', statementsArray); const groupStatements = frontFilterObjects .filter((object) => { if ( object.key === 'shape' || object.key === 'labName' || object.key === 'shapeHidden' || object.key === 'labType' ) { return true; } }) .map((object) => createStatement(object, true, '=')); //console.log('group', groupStatements); const shapeStatements = groupStatements .filter((statement) => { if ( statement[0].statement.left.operand === 'diamonds.shape' || statement[0].statement.left.operand === 'diamonds.shapeHidden' ) { return true; } }) .flat(); const nonShapeStatements = groupStatements.filter((statement) => { if ( statement[0].statement.left.operand === 'diamonds.labName' || statement[0].statement.left.operand === 'diamonds.labType' ) { return true; } }); //console.log('shapes:', shapeStatements); const shapeGroupsF = () => { if (shapeStatements.length > 0) { return { type: 'group', group: { expression: shapeStatements, }, }; } return; }; const shapeGroups = shapeGroupsF(); //console.log('shapeGroups', shapeGroups); const nonShapeGroups = nonShapeStatements.map((arr) => { return { type: 'group', group: { expression: arr, }, }; }); //console.log('non shapes groups', nonShapeGroups); typeStatement.statement.right.operand = diamondType; const groupExpression = [shapeGroups, ...nonShapeGroups].filter((i) => { return i != null; }); //oldFilter = _.cloneDeep(finalFilter); finalFilter.query.expression = [ ...statementsArray, typeStatement, ...groupExpression, ...constantStatements, ]; // if (!_.isEqual(oldFilter, finalFilter) || sortChange || typeChange) updateProducts(finalFilter, true); //oldFilter = _.cloneDeep(finalFilter); sortChange = false; typeChange = false; } if (isEmpty) { emptyFilter.query.expression[1].statement.right.operand = diamondType; updateProducts(emptyFilter, true); isEmpty = false; sortChange = false; typeChange = false; } } }); }, ]); const labTypeWrapper = document.querySelector( '[data-element="labTypeWrapper"]' ); const setType = (type: string) => { if (switcher) type === 'Loose Natural Diamond' ? switcher.classList.add('on-natural') : switcher.classList.remove('on-natural'); const params = new URLSearchParams(location.search); params.set('diamondType', type); window.history.replaceState({}, '', `${location.pathname}?${params}`); diamondType = type; typeChange = true; if (labTypeWrapper && type === 'Loose Natural Diamond') labTypeWrapper.style.display = 'none'; if (labTypeWrapper && type === 'Loose Lab Diamond') labTypeWrapper.style.display = 'block'; updateProducts(finalFilter, true); }; if (labTypeWrapper && currentDiamondType === 'Loose Natural Diamond') labTypeWrapper.style.display = 'none'; if (labTypeWrapper && currentDiamondType === 'Loose Lab Diamond') labTypeWrapper.style.display = 'block'; if (labButton) labButton.addEventListener('click', () => { setType('Loose Lab Diamond'); }); if (naturalButton) naturalButton.addEventListener('click', () => { setType('Loose Natural Diamond'); }); // Listen for button click const loadMore = document.querySelector('[data-element="load-more"]'); if (loadMore) { loadMore.removeAttribute('href'); loadMore.addEventListener('click', async () => { if (finalFilter.query.page) finalFilter.query.page = finalFilter.query.page + 1; if (finalFilter.query.per_page) finalFilter.query.per_page = 75; //fix this listInstance.itemsPerPage = listInstance.itemsPerPage + 25; // fix this //listInstance.renderItems(false, false); updateProducts(finalFilter, false); }); } }, ]); // sort change function const setSort = (sortBy: string, orderBy: string) => { sortElement.sort[0].orderBy = orderBy; sortElement.sort[0].sortBy = `diamonds.${sortBy}`; emptyFilter.query.sort[0].orderBy = orderBy; emptyFilter.query.sort[0].sortBy = `diamonds.${sortBy}`; sortChange = true; }; const rangeStatement = (obj: { key: string; values: number[] }) => { if (obj.values.length === 1) { return [ { or: false, statement: { left: { tag: 'col', operand: `diamonds.${obj.key}`, }, op: '>=', right: { operand: `${ obj.key === 'price' || obj.key === 'carat' ? obj.values[0] : +obj.values[0] + 1 }`, }, }, }, ]; } return [ { or: false, statement: { left: { tag: 'col', operand: `diamonds.${obj.key}`, }, op: '>=', right: { operand: obj.values[0] === undefined ? '0' : `${ obj.key === 'price' || obj.key === 'carat' ? obj.values[0] : +obj.values[0] + 1 }`, }, }, }, { or: false, statement: { left: { tag: 'col', operand: `diamonds.${obj.key}`, }, op: '<=', right: { operand: `${obj.values[1]}`, }, }, }, ]; }; const createStatement = (object: FrontFilterObject, or: boolean, op: string) => { const { values } = object; //array of filter options const { key } = object; // filter key(field) switch (key) { case 'price': case 'colorSort': case 'claritySort': case 'polishSort': case 'symmetrySort': case 'cutSort': case 'carat': return rangeStatement(object); case 'shape': case 'labName': case 'fluorescence': case 'location': case 'labType': case 'shapeHidden': const output = values.map((value) => { return { or, statement: { left: { tag: 'col', operand: `diamonds.${key}`, }, op, right: { operand: key === 'labType' ? `${value}`.toLowerCase() : value, }, }, }; }); return output; } }; /** * Fetches API and * @returns An array of {@link Product}. */ const fetchProducts = async (input: Input) => { try { const baseUrl = 'https://api.jimmyswipe.com/api:8Vr1o0ps/'; const body = JSON.stringify(input); const options = { method: 'POST', headers: { accept: 'application/json', 'content-type': 'application/json', }, body: body, }; if (userPermission === 'u' || userPermission === 'a') options.headers.Authorization = `Bearer ${authToken}`; const response = await fetch(`${baseUrl}diamonds/${userPermission}`, options); const data = await response.json(); return data; } catch (error) { return {}; } }; /** * Creates an item from the template element. * @param product The product data to create the item from. * @param templateElement The template element. * * @returns A new Collection Item element. */ const createItem = (product: Product, templateElement: HTMLDivElement) => { // Clone the template element const newItem = templateElement.cloneNode(true) as HTMLDivElement; // Query inner elements const image = newItem.querySelector('[data-element="image"]'); const shape = newItem.querySelector('[data-element="shape"]'); const shapeHidden = newItem.querySelector('[data-element="shapeHidden"]'); const location = newItem.querySelector('[data-element="location"]'); const cut = newItem.querySelector('[data-element="cut"]'); const clarity = newItem.querySelector('[data-element="clarity"]'); const title = newItem.querySelector('[data-element="title"]'); const discount = newItem.querySelector('[data-element="discount"]'); const labName = newItem.querySelector('[data-element="labName"]'); const price = newItem.querySelector('[data-element="price"]'); const carat = newItem.querySelector('[data-element="carat"]'); const colorSort = newItem.querySelector('[data-element="colorSort"]'); const color = newItem.querySelector('[data-element="color"]'); const claritySort = newItem.querySelector('[data-element="claritySort"]'); const cutSort = newItem.querySelector('[data-element="cutSort"]'); const polishSort = newItem.querySelector('[data-element="polishSort"]'); const symmetrySort = newItem.querySelector('[data-element="symmetrySort"]'); const certificateId = newItem.querySelector('[data-element="certificateId"]'); const diamLink = newItem.querySelector('[data-element="link"]'); const length = newItem.querySelector('[data-element="length"]'); const width = newItem.querySelector('[data-element="width"]'); const lwRatio = newItem.querySelector('[data-element="lwRation"]'); const fluorescence = newItem.querySelector('[data-element="fluorescence"]'); const treatment = newItem.querySelector('[data-element="treatment"]'); const vendorName = newItem.querySelector('[data-element="vendorName"]'); const imageWrapper = newItem.querySelector('[data-element="image-wrapper"]'); const vendorDiscountWrapper = newItem.querySelector( '[data-element="vendor-discount"]' ); const viewPrice = newItem.querySelector('[data-element="viewPrice"]'); const labType = newItem.querySelector('[data-element="labType"]'); // Populate inner elements const normalizeText = (str: string) => _.startCase(_.toLower(str)); if (image) image.src = !product.shopifyImageUrl ? 'https://img.icons8.com/ios/250/FFFFFF/diamond--v1.png' : product.shopifyImageUrl; //if (image) image.style.transform = `scale(${product.carat})`; if (colorSort) colorSort.textContent = product.colorSort; if (claritySort) claritySort.textContent = product.claritySort; if (cutSort) cutSort.textContent = product.cutSort; if (symmetrySort) symmetrySort.textContent = product.symmetrySort; if (polishSort) polishSort.textContent = product.polishSort; if (shape) shape.textContent = normalizeText(product.shape); if (shapeHidden) shapeHidden.textContent = product.shapeHidden; if (labName) labName.textContent = product.labName.toUpperCase(); if (location) location.textContent = normalizeText(product.location); if (cut) cut.textContent = product.cut; if (color) color.textContent = product.color; if (clarity) clarity.textContent = product.clarity; const discountNew: number = (1 - (product.priceR * (1 + markup)) / (product.rapPrice * product.carat)) * 100; if (userPermission === 'n' && discount) discount.style.display = 'none'; if (discount) discount.textContent = new Intl.NumberFormat('en-US', { style: 'decimal', maximumFractionDigits: 2, }).format(discountNew) + '%'; if (userPermission === 'n' && price) price.style.display = 'none'; if (price && !markup) price.textContent = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', useGrouping: false, maximumFractionDigits: 0, }).format(product.priceR); // const offMarkup = (discount: number, carat: number) => { // const absDisc = Math.abs(discount); // if (carat < 0.5) return absDisc - 0.03; // if (carat >= 0.5 && carat < 1.5) return absDisc - 0.02; // if (carat >= 1.5 && carat < 3) return absDisc - 0.015; // if (carat >= 3) return absDisc - 0.01; // }; // offMarkup(product.discount, product.price); // if (discount) // discount.textContent = // new Intl.NumberFormat('en-US', { // style: 'decimal', // maximumFractionDigits: 2, // }).format(offMarkup(product.discount, product.price) * 100) + '%'; if (viewPrice && userPermission === 'n') { viewPrice.style.display = 'block'; viewPrice.href = '/sign-up'; } if ( (markup && price && userPermission !== 'n') || (markup === 0 && price && userPermission !== 'n') ) // price.textContent = new Intl.NumberFormat('en-US', { // style: 'currency', // currency: 'USD', // useGrouping: true, // maximumFractionDigits: 0, // }).format( // product.rapPrice * product.carat * (1 - offMarkup(product.discount, product.price)) // ); price.textContent = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', useGrouping: true, maximumFractionDigits: 0, }).format(product.priceR * (1 + markup)); if (carat) carat.textContent = new Intl.NumberFormat('en-US', { style: 'decimal', maximumFractionDigits: 2, useGrouping: false, minimumFractionDigits: 2, }).format(product.carat); if (length && product.length !== 0) length.textContent = new Intl.NumberFormat('en-US', { style: 'decimal', maximumFractionDigits: 2, }).format(product.length) + 'mm'; if (width && product.width !== 0) width.textContent = new Intl.NumberFormat('en-US', { style: 'decimal', maximumFractionDigits: 2, }).format(product.width) + 'mm'; if (lwRatio && product.lwRatio !== 0) lwRatio.textContent = new Intl.NumberFormat('en-US', { style: 'decimal', maximumFractionDigits: 2, }).format(product.lwRatio === 0 ? product.length / product.width : product.lwRatio); if (title) title.textContent = product.title; if (certificateId) certificateId.textContent = product.certificateId; if (diamLink) diamLink.href = '/diamond?item=' + product.certificateId; if (fluorescence) fluorescence.textContent = product.fluorescence; if (treatment) treatment.textContent = product.treatment === '' ? '' : '*'; if (labType && product.labType && product.labType !== 'na') labType.textContent = product.labType.toUpperCase(); if (userPermission !== 'a' && vendorName && vendorDiscountWrapper) { vendorName.style.display = 'none'; vendorDiscountWrapper.style.justifyContent = 'flex-end'; } if (vendorName) vendorName.textContent = product.vendorName; if (product.diamondType === 'Loose Natural Diamond') imageWrapper?.classList.add('natural'); return newItem; }; }); };