UNPKG

@ecomplus/storefront-template

Version:

Reusable & upgradable views and scripts for E-Com Plus storefront

374 lines (336 loc) 13.6 kB
import ecomUtils from '@ecomplus/utils' import ecomClient from '@ecomplus/client' import EcomSearch from '@ecomplus/search-engine' import BannerSliderEJS from '../../../pages/@/sections/banner-slider.ejs' import InforBarEJS from '../../../pages/@/sections/info-bar.ejs' import IncBannerEJS from '../../../pages/@/sections/inc/banner.ejs' import ProductItemEJS from '../../../pages/@/sections/inc/product-item.ejs' import RetailGridEJS from '../../../pages/@/sections/inc/retail-grid.ejs' import MDContentEJS from '../../../pages/@/sections/inc/md-content.ejs' import TimerEJS from '../../../pages/@/sections/inc/timer.ejs' import IncProductsCarouselEJS from '../../../pages/@/sections/inc/products-carousel.ejs' import ResponsiveBannerEJS from '../../../pages/@/sections/responsive-banner.ejs' import BannerGridEJS from '../../../pages/@/sections/banners-grid.ejs' import CustomContentEJS from '../../../pages/@/sections/custom-content.ejs' import CustomHtmlEjs from '../../../pages/@/sections/custom-html.ejs' import OffersTimerEJS from '../../../pages/@/sections/offers-timer.ejs' import BreadcrumbsEJS from '../../../pages/@/sections/breadcrumbs.ejs' import PageTitleEJS from '../../../pages/@/sections/page-title.ejs' import BlogEJS from '../../../pages/@/sections/blog.ejs' import ProductBlockEJS from '../../../pages/@/sections/product-block.ejs' import RelatedProductsEJS from '../../../pages/@/sections/related-products.ejs' import RecommendedProducts from '../../../pages/@/sections/recommended-products.ejs' import RecommendationsShelfEJS from '../../../pages/@/sections/inc/recommendations-shelf.ejs' import ProductDescriptionEJS from '../../../pages/@/sections/product-description.ejs' import ProductSpecificationsEJS from '../../../pages/@/sections/product-specifications.ejs' import SearchEngineEJS from '../../../pages/@/sections/search-engine.ejs' import LoadingEJS from '../../../pages/@/sections/inc/loading.ejs' import DocumentBannerEJS from '../../../pages/@/sections/document-banner.ejs' import DocumentDescriptionEJS from '../../../pages/@/sections/document-description.ejs' import BrandRetailEJS from '../../../pages/@/sections/brand-retail.ejs' import CategoryRetailEJS from '../../../pages/@/sections/category-retail.ejs' import CollectionRetailEJS from '../../../pages/@/sections/collection-retail.ejs' import CollectionShelfEJS from '../../../pages/@/sections/collection-shelf.ejs' const { _data, _info, _settings, lodash, ejs, React, h, MarkdownIt, i18n } = window const isArrayEqual = (x, y) => lodash.isEmpty(lodash.xorWith(x, y, lodash.isEqual)) const lang = 'pt_br' export default class BasePreview extends React.Component { constructor () { super() this._ = { ..._data, ..._settings, settings: _settings, devMode: true, cms: function (content) { const contents = { info: _info, posts: [ 'post-example', 'post-example', 'post-example', 'post-example', 'post-example', 'post-example' ], 'posts/post-example': { title: 'Esta loja é um Progressive Web App', date: '2019-05-16T16:01:40.618Z', thumbnail: '/img/uploads/pwa-reliable.png', description: 'PWA é uma evolução híbrida entre um aplicativo e uma página web, que torna a experiência de uso de uma página web pelo celular semelhante a de um aplicativo mobile.\n\n', body: '![PWA](/img/uploads/pwa-reliable.png)\n\nProgressive Web App (PWA) é uma criação da da Google que permite a criação de aplicações que são um meio termo entre uma página web e um aplicativo nativo. Nosso Storefront padrão é um PWA!\n\nCom o grande aumento de tráfego mobile no e-commerce um PWA commerce traz enormes vantagens. Veja abaixo as principais vantagens e com alguns exemplos de sucesso com estudos de caso feitos pelo Google\n\n**Digno de estar na tela inicial**\n\nQuando os critérios do Progressive Web App são atendidos, o Chrome solicita que os usuários adicionem o PWA à tela inicial. Com isso a loja se transforma em uma aplicação nativa sem a necessidade de download através de uma app store. Isso pode não funcionar perfeitamente em IOS que tem 13,57% do market share mobile brasileiro, sendo quase todo o resto dominado pelo Android.\n\n**Maior engajamento**\n\nO PWA da [eXtra Electronics](https://developers.google.com/web/showcase/2016/extra) aumentou o re-engajamento em 4X, esses usuários gastam o dobro do tempo no site.\n Com a [OLX ](https://developers.google.com/web/showcase/2017/olx) o aumento do o re-engajamento foi de 250%.\n\n**Aumento na taxa de conversão**\n\nA capacidade de fornecer de um PWA melhorar a experiência ao usuário, ajudou o [AliExpress](https://developers.google.com/web/showcase/2016/aliexpress) a aumentar a taxa de conversão de novos usuários em 104%.', meta_title: 'PWA - My Shop', meta_description: 'Esta loja é um JAMstack PWA' } } if (contents[content]) { return contents[content] } else { return {} } }, dictionary: function (term) { if (term) { if (i18n[term]) { return ecomUtils.i18n(i18n[term], lang) } else if (i18n[`i19${term}`]) { return ecomUtils.i18n(i18n[`i19${term}`], lang) } return this._.cms(`dictionary/${lang}`)[term] || '' } return this._.cms(`dictionary/${lang}`) }, tryImageSize: function () { return {} }, md: new MarkdownIt({ html: true }), route: null, resolveRoute: null, lodash, ecomUtils, ecomClient, EcomSearch, state: {} } this.state = { vDoc: '', html: '', parseHtml: '', cmsEntrys: [] } this.ejs = ejs this.loading = true } componentDidMount () { this.fetchPage() } componentDidUpdate (prevProps, prevState) { setTimeout(() => { const entries = this.getEntrys(this.props) const oldEntrys = this.getEntrys(prevProps) let change = false if (!isArrayEqual(Object.keys(entries), Object.keys(oldEntrys))) { change = true } if (!isArrayEqual(Object.values(entries), Object.values(oldEntrys))) { change = true } for (let i = 0; i < entries.length; i++) { const entry = entries[i] const oldEntry = oldEntrys[i] if (oldEntry && oldEntry.type === entry.type) { for (const key in oldEntry) { if (oldEntry[key]) { switch (typeof oldEntry[key]) { case 'string': case 'number': if (oldEntry[key] !== entry[key]) { change = true } break default: if (Array.isArray(oldEntry[key])) { if (!isArrayEqual(oldEntry[key], entry[key])) { change = true } } break } } } } else { change = true } } if (!change) { for (let i = 0; i < entries.length; i++) { if (oldEntrys[i].type !== entries[i].type) { change = true } } } if (!change && !this.loading) { return } this.setState({ cmsEntrys: entries }) setTimeout(() => { this.parseEjs() }, 500) this.loading = false }, 250) } getEntrys (props) { let i = 0 let entries const entrySections = [] const { entry } = props do { entries = entry.getIn(['data', 'sections', i]) if (entries) { const data = entries.toJSON() if (data.type === 'banner-slider' && data.slides) { const { slides } = data if (Array.isArray(slides) && slides.length) { slides.forEach(slide => { if (slide.end) { delete slide.end } if (slide.start) { delete slide.start } }) } } if (data.type === 'collection-shelf' && data.shuffle) { delete data.shuffle } entrySections.push(data) } i++ } while (entries !== undefined) return entrySections } ejsSections () { return { breadcrumbs: BreadcrumbsEJS, banner: IncBannerEJS, blog: BlogEJS, timer: TimerEJS, loading: LoadingEJS, 'banner-slider': BannerSliderEJS, 'info-bar': InforBarEJS, 'collection-shelf': CollectionShelfEJS, 'products-carousel': IncProductsCarouselEJS, 'product-item': ProductItemEJS, 'widgets-append': '<% %>', 'responsive-banner': ResponsiveBannerEJS, 'banners-grid': BannerGridEJS, 'custom-content': CustomContentEJS, 'offers-timer': OffersTimerEJS, 'page-title': PageTitleEJS, 'retail-grid': RetailGridEJS, 'md-content': MDContentEJS, 'custom-html': CustomHtmlEjs, 'product-block': ProductBlockEJS, 'related-products': RelatedProductsEJS, 'recommended-products': RecommendedProducts, 'recommendations-shelf': RecommendationsShelfEJS, 'product-description': ProductDescriptionEJS, 'product-specifications': ProductSpecificationsEJS, 'search-engine': SearchEngineEJS, 'document-banner': DocumentBannerEJS, 'document-description': DocumentDescriptionEJS, 'brand-retail': BrandRetailEJS, 'category-retail': CategoryRetailEJS, 'collection-retail': CollectionRetailEJS } } async parseEjs (customCmsEntrys = []) { const { state } = this const { vDoc } = state let parseHtml = '' if (!vDoc) { return null } let parse = '' const cmsEntrys = [...state.cmsEntrys, ...customCmsEntrys] if (Array.isArray(cmsEntrys) && cmsEntrys.length) { for (let j = 0; j < cmsEntrys.length; j++) { const opt = cmsEntrys[j] opt.items = this._.items const template = this.ejsSections()[opt.type] if (template) { parse += await ejs.render(template, { _: this._, opt }, { async: true, includer: originalPath => { for (const sectionType in this.ejsSections()) { if (originalPath.endsWith(`/${sectionType}`)) { return { template: this.ejsSections()[sectionType] } } } } }).catch(e => console.error('Parse err', e)) } } } const $sections = vDoc.getElementsByClassName('sections')[0] $sections.innerHTML = '' $sections.innerHTML += parse // glide slider const $slider = vDoc.querySelectorAll('.glide') if ($slider.length) { $slider.forEach(el => el.classList.add(...['glide--ltr', 'glide--slider', 'glide--swipeable'])) } // insert tag img inside <picture/> tags const $pictures = vDoc.querySelectorAll('.banner picture') for (let i = 0; i < $pictures.length; i++) { const $picture = $pictures[i] $picture.classList.remove() $picture.classList.add(...['lozad', 'fade', 'img-fluid', 'show']) $picture.setAttribute('src', $picture.dataset.iesrc) $picture.setAttribute('data-loaded', true) const hasTagImg = Object.values($picture.children).find(children => children.tagName === 'IMG') if (!hasTagImg) { $picture.appendChild(document.createElement('img', { alt: $picture.dataset.alt, src: $picture.dataset.iesrc })) } } // insert tag img inside <picture/> tags const $pictures2 = vDoc.querySelectorAll('.product-card__picture') for (let i = 0; i < $pictures2.length; i++) { const $picture1 = $pictures2[i] $picture1.classList.remove() const $img = $picture1.children[0] $img.classList.remove() $img.classList.add(...['lozad', 'show']) $img.setAttribute('src', $img.dataset.src) $picture1.classList.add(...['lozad', 'fade', 'img-fluid', 'show']) $picture1.setAttribute('src', $img.dataset.src) $picture1.setAttribute('data-loaded', true) } // fix carousel const $caroulse = vDoc.querySelectorAll('.glide__slide.products-carousel__item') if ($caroulse.length) { $caroulse.forEach(element => { const $li = element $li.style.width = '270px' $li.style.marginRight = '5px' $li.classList.add(...['glide__slides', 'products-carousel__list']) const $glide = vDoc.querySelectorAll('.products-carousel .glide') if ($glide.length) { $glide.forEach(el => el.classList.add(...['glide--ltr', 'glide--slider', 'glide--swipeable'])) } const childrens = $li.children for (let i = 0; i < childrens.length; i++) { const child = childrens[i] const $elImg = child.querySelectorAll('img') if ($elImg.length) { $elImg[0].classList.add('show') $elImg[0].setAttribute('src', $elImg[0].dataset.src) $elImg[0].setAttribute('data-loaded', true) } } }) } if (vDoc.childNodes && vDoc.childNodes.length) { parseHtml = vDoc.childNodes[1].innerHTML } this.setState({ parseHtml, vDoc }) } render () { const { parseHtml } = this.state return h('div', { dangerouslySetInnerHTML: { __html: parseHtml } }) } }