`\n reduxComponents(ReactRailsUJS, store)\n}\n","import axios from '@client/lib/axios'\n\nexport const fetchProducts = (params) => {\n return axios({\n method: 'get',\n url: '/api/1/products',\n params: params\n })\n}\n\nexport const fetchPrices = ({ ids }) => {\n return axios({\n method: 'get',\n url: '/api/1/products/prices',\n params: {\n ids\n }\n })\n}\n\nexport const fetchEssentials = ({ id }) => {\n return axios({\n method: 'get',\n url: `/api/1/products/${id}/essentials`,\n params: {\n id\n }\n })\n}\n\nexport const searchProducts = ({ ids }) => {\n return axios({\n method: 'get',\n url: `/api/1/products/search`,\n params: {\n ids\n }\n })\n}\n","export default {\n nl: {\n formats: {\n date: {\n date: 'DD-MM-YYYY',\n time: 'HH:mm',\n default: 'DD-MM-YYYY HH:mm'\n }\n }\n }\n}\n","import moment from '@client/i18n/moment'\nimport { enUS, nl, de, fr } from \"date-fns/locale\";\n\nimport allTranslations from './translations'\nimport capitalize from 'lodash/capitalize'\n\nimport { i18n } from '../../../../app/javascript/packs/localized-i18n'\n\nconst t = (...args) => i18n.t(...args)\n\nconst translations = () => {\n return allTranslations['nl']\n}\n\nconst formatDate = (date, type) => {\n const formats = translations().formats.date\n const format = formats[type] || formats.default\n return moment(date).format(format)\n}\n\nconst date = (datetime) => {\n const format = 'dd DD MMM Y'\n const day = moment(datetime).format('dd')\n return capitalize(moment(datetime).format(format))\n}\n\nconst calendarDate = (datetime, small = false) => {\n const params = {\n lastDay: `[${t('date.yesterday')}]`,\n sameDay: `[${t('date.today')}]`,\n nextDay: `[${t('date.tomorrow')}]`,\n lastWeek: small ? 'dd' : 'dddd',\n nextWeek: small ? 'dd' : 'dddd',\n sameElse: small ? 'dd' : 'dddd'\n }\n const format = small ? ', D MMM HH:mm' : ', D MMMM HH:mm'\n const day = moment(datetime).calendar(null, params)\n\n return capitalize(day) + moment(datetime).format(format)\n}\n\nconst getDateFnsLocale = () => {\n switch (i18n.locale) {\n case 'nl':\n return nl\n case 'fr':\n return fr\n case 'de':\n return de\n default:\n return enUS\n }\n}\n\nconst translatePeriodLabel = (label) => {\n let displayLabel = ''\n\n // Check if the label contains a charge period (1 day, 10 week)\n if (/^\\d+\\s\\w+/gm.test(label)) {\n const [amount, chargeType] = label.split(' ')\n\n displayLabel = t(`charge_types.${chargeType}`, { count: parseInt(amount) })\n }\n\n return displayLabel\n}\n\nexport { t, translations, formatDate, calendarDate, date, getDateFnsLocale, translatePeriodLabel }\n","import { i18n } from '../../../../app/javascript/packs/localized-i18n'\nimport moment from 'moment-timezone'\n\nconst i18nMoment = (opts) => {\n moment.locale(i18n.locale)\n moment.tz.setDefault('Europe/Amsterdam')\n return moment(opts)\n}\n\nexport default i18nMoment\n","import axios from 'axios'\nimport applyCaseMiddleware from 'axios-case-converter'\nimport { i18n } from '../../../../app/javascript/packs/localized-i18n'\n\nimport window from 'global'\n\nconst instance = applyCaseMiddleware(\n axios.create(),\n {\n ignoreHeaders: true,\n ignoreParams: true,\n preservedKeys: (input) => {\n const keysToTransform = [\n \"image_thumb\",\n \"image_thumb_large\",\n \"price_in_cents\",\n \"formatted_coupon_discount\",\n \"formatted_discount\",\n \"formatted_grand_total\",\n \"formatted_insurance\",\n \"formatted_price\",\n \"formatted_subtotal\",\n \"formatted_total\",\n \"formatted_vat\",\n \"formatted_vat_amount\",\n ];\n return !keysToTransform.includes(input);\n }\n },\n)\n\nwindow.axiosPendingRequests = 0\n\ninstance.interceptors.request.use(config => {\n config.params = {\n // add your default ones\n locale: i18n.locale,\n // spread the request's params\n ...config.params,\n }\n return config\n})\n\ninstance.interceptors.request.use(request => {\n if (request.method !== 'get') {\n const token = document.querySelector('[name=csrf-token]')?.content\n request.headers['X-CSRF-TOKEN'] = token\n }\n\n return request\n}, (error) => {\n return Promise.reject(error)\n})\n\n// Track pending requests\ninstance.interceptors.request.use(config => {\n window.axiosPendingRequests++\n\n return config\n}, (error) => {\n return Promise.reject(error)\n})\n\n// Track pending requests\ninstance.interceptors.response.use(response => {\n window.axiosPendingRequests--\n\n return response\n}, (error) => {\n window.axiosPendingRequests--\n\n return Promise.reject(error)\n})\n\nexport default instance\n","import React from 'react'\n\n// Utils\nimport { times } from 'lodash'\n\nconst List = ({ accessories, isSelected, onChange }) => {\n const renderList = (accessories) => {\n return accessories.flatMap(accessory => {\n return times(accessory.qty, (i) => {\n const id = `${accessory.id}::${i}`\n\n return (\n \n )\n })\n })\n }\n\n return (\n \n {renderList(accessories)}\n
\n )\n}\n\nconst ListItem = ({ id, name, image, onChange, selected }) => {\n const modifiers = []\n\n if (selected) { modifiers.push('selected') }\n\n const handleClick = (e) => {\n onChange(id, e.target.checked)\n }\n\n return (\n \n )\n}\n\nexport default List\n","import React from 'react'\n\n// Components\nimport Icon from '@client/react/components/AddProductToCart/Icon'\n\nconst Button = ({ label, className, onClick, disabled, loading, success }) => {\n if (loading || success) {\n return (\n \n )\n } else {\n return (\n \n )\n }\n}\n\nexport default Button\n","import React from 'react'\n\nimport Tippy from '@tippyjs/react'\n\nconst instance = ({ content, visible, onClickOutside, theme = 'custom', children}) => {\n const popperOptions = {\n strategy: 'fixed',\n modifiers: [\n {\n name: 'flip',\n options: {\n fallbackPlacements: ['bottom', 'right', 'left', 'top'],\n },\n }\n ]\n }\n\n return (\n {children}\n )\n}\n\nexport default instance\n","import React from 'react'\n\n// Libraries\nimport Tippy from '@client/lib/tippy'\n\n// Components\nimport AccessoryList from './AccessoryList'\nimport Button from './Button'\nimport Modal from '@client/react/components/Modal'\n\n// Utils\nimport { t } from '@client/i18n/localize'\n\nconst Desktop = ({ productName, accessories, isSelected, onChange, onClick, selected, loading, success, visible, onClickOutside, type, children }) => {\n\n const renderButtons = () => {\n if (type === 'edit') {\n return (\n \n )\n } else {\n return (\n \n
\n )\n }\n }\n\n return (\n \n \n {t('pickup_accessories.title')}\n {t('pickup_accessories.sub_title', { product: productName })}\n \n \n \n \n \n {renderButtons()}\n \n \n }\n >\n {children}\n \n )\n}\n\nexport default Desktop\n","import React from 'react'\n\n// Components\nimport AccessoryList from './AccessoryList'\nimport Button from './Button'\nimport Modal from '@client/react/components/Modal'\n\n// Utils\nimport { t } from '@client/i18n/localize'\n\nconst Mobile = ({ productName, accessories, isSelected, onChange, onClick, selected, loading, success, visible, onClickOutside, type, children }) => {\n\n const renderButtons = () => {\n if (type === 'edit') {\n return (\n \n )\n } else {\n return (\n \n 0}\n label={t('skip')}\n loading={loading && selected.length === 0}\n success={success && selected.length === 0} />\n 0}\n success={success && selected.length > 0} />\n
\n )\n }\n }\n\n return (\n <>\n \n \n {t('pickup_accessories.title')}\n {t('pickup_accessories.sub_title', { product: productName })}\n \n \n \n \n \n {renderButtons()}\n \n \n {children}\n >\n )\n}\n\nexport default Mobile\n","import React, { useState, useEffect } from 'react'\n\n// Components\nimport Mobile from './Mobile'\nimport Desktop from './Desktop'\n\n// Utils\nimport isMobile from '@client/utils/isMobile'\nimport { countBy, times, flatMap } from 'lodash'\n\nconst AddOptionalAccessories = ({ visible, onClose, onClick, accessories, loading, success, productName, selectedAccessories = [], type, children }) => {\n const [selected, setSelected] = useState([])\n\n useEffect(() => {\n const count = countBy(selectedAccessories, (accessory) => accessory.product_id)\n const selected = flatMap(count, (v, k) => {\n return times(v, (i) => {\n return `${k}::${i}`\n })\n })\n setSelected(selected)\n }, [selectedAccessories.length])\n\n const isSelected = (accessoryId) => {\n return selected.indexOf(accessoryId) >= 0\n }\n\n const handleChange = (accessoryId, checked) => {\n if (checked) {\n setSelected(prev => [...prev, accessoryId])\n } else {\n setSelected(prev => prev.filter((id) => id !== accessoryId))\n }\n }\n\n const handleClick = (e) => {\n e.preventDefault()\n\n onClick({ accessoryIds: selected.map(x => x.split('::')[0]) })\n }\n\n const Component = isMobile() ? Mobile : Desktop\n return (\n \n {children}\n \n )\n}\n\nexport default AddOptionalAccessories\n","import React, { forwardRef } from 'react'\n\n// Components\nimport Icon from './Icon'\n\nconst Button = forwardRef(({ onClick, loading, success, label }, ref) => {\n const isDisabled = loading || success\n const buttonClasses = `btn btn-primary ${isDisabled ? 'disabled' : ''} ${label ? 'w-100' : ''}`\n\n let buttonContent;\n if (isDisabled) {\n buttonContent = ;\n } else if (label) {\n buttonContent = (\n <>\n \n {label}\n >\n );\n } else {\n buttonContent = \n }\n\n return (\n \n {buttonContent}\n \n );\n});\n\nexport default Button\n","import React, { forwardRef } from 'react'\n\nconst Icon = forwardRef(({ onClick, loading, success }, ref) => {\n if (success) {\n return \n } else if (loading) {\n return \n } else {\n return \n }\n})\n\nexport default Icon\n","import React, { forwardRef } from 'react'\n\n// Components\nimport Icon from './Icon'\n\nconst Link = forwardRef(({ onClick, loading, label }, ref) => {\n const handleClick = (e) => {\n e.preventDefault()\n onClick && onClick()\n }\n\n if (loading || success) {\n return \n } else {\n return (\n \n {label}\n \n \n )\n }\n})\n\nexport default Link\n","import React from 'react'\n\n// Utils\nimport { t } from '@client/i18n/localize'\n\n// Components\nimport Modal from '@client/react/components/Modal'\n\nconst Login = ({ product }) => {\n return (\n <>\n \n {t('wishlists.add_products')}\n {product.name}\n \n \n \n
\n

\n
\n
\n
{t('wishlists.login')}
\n
\n
\n \n \n {t('login')}\n \n >\n )\n}\n\nexport default Login\n","import React, { useState, useEffect } from 'react'\n\n// Libraries\nimport { useDispatch } from 'react-redux'\n\n// Actions\nimport { createWishlist } from '@client/redux/actions/wishlists'\n\n// Utils\nimport { t } from '@client/i18n/localize'\n\n// Components\nimport Modal from '@client/react/components/Modal'\n\nconst NewList = ({ product, onBack }) => {\n const dispatch = useDispatch()\n const [name, setName] = useState('')\n const [valid, setValid] = useState(false)\n\n useEffect(() => {\n setValid(name && name.length > 0)\n }, [name])\n\n const handleClickBack = (e) => {\n e.preventDefault()\n onBack()\n }\n\n const handleChangeName = (e) => {\n setName(e.target.value)\n }\n\n const handleSubmit = (e) => {\n e.preventDefault()\n\n if (valid) {\n dispatch(createWishlist({ name, productIds: [product.id] })).then(() => {\n onBack()\n })\n }\n }\n\n return (\n \n )\n}\n\nexport default NewList\n","import React, { useEffect } from 'react'\n\n// Libraries\nimport { useDispatch, useSelector } from 'react-redux'\n\n// Actions\nimport { fetchWishlists, addToWishlist, removeFromWishlist } from '@client/redux/actions/wishlists'\n\n// Utils\nimport { t } from '@client/i18n/localize'\n\n// Components\nimport Modal from '@client/react/components/Modal'\n\nconst ListItem = ({ id, name, selected, onChange }) => {\n const handleClick = (e) => {\n e.preventDefault()\n\n onChange(id, e.target.checked)\n }\n\n const modifiers = []\n\n if (selected) { modifiers.push('selected') }\n\n const modifierClass = modifiers.map(m => `accessory-item--${m}`).join(' ')\n const iconClass = selected ? 'fas fa-check text-primary' : 'fas fa-plus'\n\n return (\n \n )\n}\n\nconst List = ({ wishlists, product, onChange }) => {\n const isSelected = (wishlist) => {\n return wishlist.product_ids.indexOf(product.id) >= 0\n }\n\n if (wishlists.length === 0) {\n return (\n {t('wishlists.no_lists_yet')}
\n )\n } else {\n return (\n \n {wishlists.map((wishlist) => (\n \n ))}\n
\n )\n }\n}\n\nconst SelectList = ({ product, onNewList }) => {\n const dispatch = useDispatch()\n const wishlists = useSelector(state => state.wishlists)\n\n useEffect(() => {\n dispatch(fetchWishlists())\n }, [])\n\n const handleToggle = (wishlistId, checked) => {\n if (checked) {\n dispatch(addToWishlist({ id: wishlistId, productId: product.id }))\n } else {\n dispatch(removeFromWishlist({ id: wishlistId, productId: product.id }))\n }\n }\n\n return (\n <>\n \n {t('wishlists.choose_list')}\n {product.name}\n \n \n \n
\n

\n
\n
\n
\n
\n
\n \n \n \n {t('wishlists.create_new_list')}\n \n \n >\n )\n}\n\nexport default SelectList\n","import React, { useState, useEffect } from 'react'\n\n// Components\nimport Modal from '@client/react/components/Modal'\nimport SelectList from './SelectList'\nimport NewList from './NewList'\nimport Login from './Login'\n\n// Libraries\nimport { useSelector } from 'react-redux'\n\nconst AddProductToWishlist = ({ product, onClose, show }) => {\n const user = useSelector(state => state.user)\n const [step, setStep] = useState('selectList')\n\n useEffect(() => {\n setStep(user?.id ? 'selectList' : 'login')\n }, [user])\n\n const handleNewList = () => {\n setStep('newList')\n }\n\n const handleBack = () => {\n setStep('selectList')\n }\n\n return (\n \n {step === 'login' && }\n {step === 'selectList' && }\n {step === 'newList' && }\n \n )\n}\n\nexport default AddProductToWishlist\n","import React, { useState, useEffect, useRef } from 'react'\n\n// Libraries\nimport { useDispatch, useSelector } from \"react-redux\"\nimport isEmpty from 'lodash/isEmpty'\nimport trim from 'lodash/trim'\n\n// Utils\nimport { t } from '@client/i18n/localize'\nimport { track } from '@client/utils/tracking'\n\n// Actions\nimport { applyCouponOnCart, removeCouponFromCart } from '@client/redux/actions/cart'\n\n// Components\nimport Loader from '@client/react/components/Loader'\n\nconst Information = () => {\n const dispatch = useDispatch()\n const cart = useSelector((state) => state.cart)\n const coupon = cart.coupon || {}\n const inputNode = useRef(null)\n\n const [show, setShow] = useState(coupon.code ? true : false)\n const [code, setCode] = useState(coupon.code || '')\n const [status, setStatus] = useState(null)\n const hasCoupon = !isEmpty(trim(coupon.code))\n\n const handleShow = (e) => {\n e.preventDefault()\n\n setShow(true)\n }\n\n const handleChangeCoupon = (e) => {\n setCode(e.target.value)\n }\n\n const handleSubmit = (e) => {\n e.preventDefault()\n\n setStatus('loading')\n\n if (isEmpty(trim(code))) {\n dispatch(removeCouponFromCart()).then(() => {\n setShow(false)\n setStatus(null)\n }).then(() => {\n track('cart.remove_coupon', { code: coupon.code })\n })\n } else {\n dispatch(applyCouponOnCart({ coupon: code })).then(({ type }) => {\n if (type.endsWith('_ERROR')) {\n setStatus('error')\n } else {\n setStatus('success')\n track('cart.apply_coupon', { code })\n }\n })\n }\n }\n\n useEffect(() => {\n if (show) {\n inputNode.current.focus()\n }\n }, [show])\n\n useEffect(() => {\n if (hasCoupon) {\n setShow(true)\n setCode(coupon.code)\n }\n }, [coupon.code])\n\n return (\n <>\n {!show && (\n \n {t('shopping_cart.have_a_coupon')}\n \n )}\n {show && (\n \n )}\n >\n )\n}\n\nexport default Information\n","import React from 'react'\n\n// Libraries\nimport { useSelector } from 'react-redux'\n\n// Components\nimport Modal from '@client/react/components/Modal'\n\n// Utils\nimport { t } from '@client/i18n/localize'\nimport { url } from '@client/utils'\n\nconst Delivery = ({ onClose, show }) => {\n const company = useSelector(state => state.company)\n\n const faq_url = url(`${t('routes.faq')}#faq-10`)\n\n const faq_link = `${t('delivery_service.faq_link_text')}`\n const paragraphs = t('delivery_service.body', { faq_link: faq_link, price: company.delivery_service_price_from }).split('\\n\\n')\n\n return (\n \n \n {t('delivery_service.title')}\n \n \n \n {paragraphs.map((p, i) =>
)}\n
\n \n \n )\n}\n\nexport default Delivery\n","import React, { useState } from \"react\"\n\n// Libraries\nimport { useDispatch, useSelector } from \"react-redux\"\n\n// Utils\nimport { formatDate, t } from '@client/i18n/localize'\n\n// Actions\nimport { openPicker } from '@client/redux/actions/picker'\n\nconst Information = ({ readonly=false }) => {\n const dispatch = useDispatch()\n const cart = useSelector(state => state.cart)\n\n const dataRequired = [cart.return_depot_id, cart.pickup_depot_id, cart.pickup_time, cart.return_time]\n const isValid = dataRequired.indexOf(undefined) === -1 && dataRequired.indexOf(null) === -1\n\n const pickupDepot = cart.pickup_depot\n const returnDepot = cart.return_depot\n\n const handleEditPickup = (e) => {\n e.preventDefault()\n dispatch(openPicker())\n }\n\n const handleEditReturn = (e) => {\n e.preventDefault()\n dispatch(openPicker())\n }\n\n if (!isValid) {\n return (\n \n )\n }\n\n return (\n \n
\n
\n
\n \n {pickupDepot.name}\n
\n
\n
\n \n {formatDate(cart.pickup_time, 'date')}\n
\n
\n \n {formatDate(cart.pickup_time, 'time')}\n
\n
\n
\n
\n
\n
\n
\n \n {returnDepot.name}\n
\n
\n
\n \n {formatDate(cart.return_time, 'date')}\n
\n
\n \n {formatDate(cart.return_time, 'time')}\n
\n
\n
\n
\n )\n}\n\nexport default Information\n","import React from 'react'\n\n// Libraries\nimport { useSelector } from 'react-redux'\n\n// Components\nimport Modal from '@client/react/components/Modal'\n\n// Utils\nimport { t } from '@client/i18n/localize'\n\nconst Insurance = ({ onClose, show }) => {\n const company = useSelector((state) => state.company)\n\n const paragraphs = t('insurance.body', { company_name: company.name_legal || company.name }).split('\\n\\n')\n\n return (\n \n \n {t('insurance.title')}\n \n \n \n {paragraphs.map((p, i) =>
{p}
)}\n
\n \n \n )\n}\n\nexport default Insurance\n","const close = () => {\n return {\n type: 'CLOSE_PICKER',\n payload: {}\n }\n}\n\nconst closePicker = () => {\n return (dispatch) => {\n return dispatch(close())\n }\n}\n\nexport default closePicker\n","// React\nimport React, { useEffect, useRef } from 'react'\n\n// Utils\nimport { t } from '@client/i18n/localize'\n\n// Components\nimport Intro from '@client/react/components/Picker/Intro'\n\n// Libraries\nimport { useDispatch, useSelector } from 'react-redux'\n\n// Shared\nimport { updateCart } from '@client/redux/actions/cart'\nimport { openPicker, closePicker } from '@client/redux/actions/picker'\n\nconst ModalWrapper = ({ errors, values, children, intro }) => {\n const dispatch = useDispatch()\n\n const modalVisible = useSelector(state => state.picker.open)\n\n const backgroundNode = useRef(null)\n\n useEffect(() => {\n const html = document.querySelector('html')\n\n if (modalVisible) {\n html.classList.add('no-overflow')\n } else {\n html.classList.remove('no-overflow')\n }\n }, [modalVisible])\n\n const handleModalVisible = () => {\n if (modalVisible) {\n dispatch(closePicker())\n } else {\n dispatch(openPicker())\n }\n }\n\n const handleApplyPress = () => {\n if (!errors.length) {\n dispatch(updateCart({\n pickup_time: values.pickupTime,\n return_time: values.returnTime,\n pickup_depot_id: values.pickupDepot.id,\n return_depot_id: values.returnDepot.id\n }))\n\n handleModalVisible()\n }\n }\n\n const handleClick = (e) => {\n if (e.target === backgroundNode.current) {\n handleModalVisible()\n }\n }\n\n const isDisabled = () => {\n if (!values || errors.length) return true\n\n for (const value in values) {\n if (values[value] === null || values[value] === undefined) return true\n }\n\n return false\n }\n\n return (\n <>\n {intro && }\n\n {modalVisible && (\n \n
\n
\n
\n
\n
\n
{t('cart_picker.choose_period_short')}
\n \n \n {children}\n {errors.length !== 0 && errors.map((error, index) =>
{error.message}
)}\n
\n \n {t('cancel')}\n \n {t('save')}\n
\n
\n
\n
\n
\n
\n )}\n >\n )\n}\n\nexport default ModalWrapper\n","import React from 'react'\n\nconst OptionalAccessoryProduct = ({ accessories }) => {\n const products = [...accessories.reduce((acc, accessory) => {\n const { product, qty } = accessory\n const item = acc.get(product.id) || Object.assign({}, product, { qty: 0 })\n\n item.qty += qty\n\n return acc.set(product.id, item)\n }, new Map).values()]\n\n return (\n \n {products.map((product) => (\n - \n {product.qty}x {product.name}\n
\n ))}\n
\n )\n}\n\nexport default OptionalAccessoryProduct\n","import { fetchAll } from '@client/api/opening_times'\n\nconst start = () => {\n return {\n type: 'FETCH_OPENING_TIMES_START'\n }\n}\n\nconst success = ({ opening_times }) => {\n return {\n type: 'FETCH_OPENING_TIMES_SUCCESS',\n payload: { opening_times }\n }\n}\n\nconst error = ({ response }) => {\n return {\n type: 'FETCH_OPENING_TIMES_ERROR',\n payload: { response }\n }\n}\n\nconst fetchOpeningTimes = () => {\n return (dispatch) => {\n dispatch(start())\n\n return fetchAll()\n .then(\n (response) => { dispatch(success({ opening_times: response.data.opening_times })) },\n (errorData) => { dispatch(error({ response: errorData.response })) }\n )\n }\n}\n\nexport default fetchOpeningTimes\n","import axios from '@client/lib/axios'\n\nexport const fetchAll = () => {\n return axios({\n method: 'get',\n url: '/api/2/opening_times'\n })\n}\n","import { fetchAll } from '@client/api/timeslots'\n\nconst start = () => {\n return {\n type: 'FETCH_TIMESLOTS_START'\n }\n}\n\nconst success = ({ timeslots }) => {\n return {\n type: 'FETCH_TIMESLOTS_SUCCESS',\n payload: { timeslots }\n }\n}\n\nconst error = ({ response }) => {\n return {\n type: 'FETCH_TIMESLOTS_ERROR',\n payload: { response }\n }\n}\n\nconst fetchTimeslots = () => {\n return (dispatch) => {\n dispatch(start())\n\n return fetchAll()\n .then(\n (response) => { dispatch(success({ timeslots: response.data.timeslots })) },\n (errorData) => { dispatch(error({ response: errorData.response })) }\n )\n }\n}\n\nexport default fetchTimeslots\n","import axios from '@client/lib/axios'\n\nexport const fetchAll = () => {\n return axios({\n method: 'get',\n url: '/api/2/timeslots'\n })\n}\n","import { fetchAll } from '@client/api/depots'\n\nconst start = () => {\n return {\n type: 'FETCH_DEPOTS_START'\n }\n}\n\nconst success = ({ depots }) => {\n return {\n type: 'FETCH_DEPOTS_SUCCESS',\n payload: { depots }\n }\n}\n\nconst error = ({ response }) => {\n return {\n type: 'FETCH_DEPOTS_ERROR',\n payload: { response }\n }\n}\n\nconst fetchDepots = () => {\n return (dispatch) => {\n dispatch(start())\n\n return fetchAll()\n .then(\n (response) => { dispatch(success({ depots: response.data.depots })) },\n (errorData) => { dispatch(error({ response: errorData.response })) }\n )\n }\n}\n\nexport default fetchDepots\n","import axios from '@client/lib/axios'\n\nexport const fetchAll = (params) => {\n return axios({\n method: 'get',\n url: '/api/2/depots'\n })\n}\n\nexport const fetch = ({ id }) => {\n return axios({\n method: 'get',\n url: `/api/2/depots/${id}`\n })\n}\n","// React\nimport React, { useState, useEffect } from 'react'\n\n// Libraries\nimport { useDispatch, useSelector } from 'react-redux'\nimport moment from '@client/i18n/moment'\nimport isEqual from 'lodash/isEqual'\n\n// Utils\nimport { getTimeslot, filterTimeslots, fromAsTime, tillAsTime } from '@client/utils/timeslots'\nimport Validation from './Validation'\n\n// Actions\nimport { updateCart } from '@client/redux/actions/cart'\nimport { fetchOpeningTimes } from '@client/redux/actions/openingTimes'\nimport { fetchTimeslots } from '@client/redux/actions/timeslots'\nimport { fetchDepots } from '@client/redux/actions/depots'\n\n// Components\nimport LocationDate from '@client/react/components/Picker'\nimport ModalWrapper from '@client/react/components/Cart/ModalWrapper'\n\nconst Picker = ({ intro = true, ...props }) => {\n const dispatch = useDispatch()\n const cart = useSelector(state => state.cart)\n const timeslots = useSelector(state => state.timeslots)\n const depots = useSelector(state => state.depots)\n const openingTimes = useSelector(state => state.openingTimes)\n const currentDomain = useSelector(state => state.domain)\n\n const [values, setValues] = useState({\n pickupTime: null,\n returnTime: null,\n pickupDate: null,\n returnDate: null,\n pickupTimeslot: null,\n returnTimeslot: null,\n pickupDepot: null,\n returnDepot: null\n })\n\n const [pickupOpeningTimes, setPickupOpeningTimes] = useState([])\n const [returnOpeningTimes, setReturnOpeningTimes] = useState([])\n const [pickupTimeslots, setPickupTimeslots] = useState([])\n const [returnTimeslots, setReturnTimeslots] = useState([])\n\n // Only depots for current domain\n const pickupDepots = depots.filter(depot => depot.domain_id === currentDomain.id)\n\n // All depots with current domain depots in front\n const otherDepots = depots.filter(depot => depot.domain_id !== currentDomain.id)\n const returnDepots = pickupDepots.concat(otherDepots)\n\n const handleReset = () => {\n const values = {\n pickupTime: cart.pickup_time && moment(cart.pickup_time),\n pickupDate: cart.pickup_time && moment(cart.pickup_time).toDate(),\n pickupDepot: depots.find((depot) => depot.id === cart.pickup_depot_id),\n returnTime: cart.return_time && moment(cart.return_time),\n returnDate: cart.return_time && moment(cart.return_time).toDate(),\n returnDepot: depots.find((depot) => depot.id === cart.return_depot_id)\n }\n\n if (values.pickupTime && values.returnTime) {\n values.pickupTimeslot = getTimeslot(values.pickupTime, timeslots, openingTimes, values.pickupDepot.id, 'pickup')\n values.returnTimeslot = getTimeslot(values.returnTime, timeslots, openingTimes, values.returnDepot.id, 'return')\n }\n\n setValues(prevValues => {\n return {\n ...prevValues,\n ...values\n }\n })\n }\n\n // Fetch opening times and timeslots data on component mount\n useEffect(() => {\n if (openingTimes.length === 0) {\n dispatch(fetchOpeningTimes())\n }\n if (timeslots.length === 0) {\n dispatch(fetchTimeslots())\n }\n if (depots.length === 0) {\n dispatch(fetchDepots())\n }\n }, [])\n\n // Reflect cart changes\n useEffect(() => {\n handleReset()\n }, [cart.pickup_time, cart.return_time, cart.pickup_depot_id, cart.return_depot_id])\n\n useEffect(() => {\n const { pickupDepot, pickupDate, pickupTimeslot, returnDepot } = values\n\n if (pickupDepot) {\n const timeRules = openingTimes.filter((el) => el.depot_id === pickupDepot.id)\n\n if (!isEqual(timeRules, pickupOpeningTimes)) {\n setPickupOpeningTimes(timeRules)\n }\n\n // Set the same return depot if it's value is empty\n if (!returnDepot) {\n setValues((prevValues) => {\n return {\n ...prevValues,\n pickupTime: fromAsTime(pickupDate, pickupTimeslot),\n returnDepot: pickupDepot\n }\n })\n } else {\n // Set/Update pickup time when pickup depot changes\n setValues((prevValues) => {\n return {\n ...prevValues,\n pickupTime: fromAsTime(pickupDate, pickupTimeslot)\n }\n })\n }\n }\n }, [values.pickupDepot, openingTimes])\n\n useEffect(() => {\n const { returnDepot, returnDate, returnTimeslot } = values\n\n if (returnDepot) {\n const timeRules = openingTimes.filter((el) => el.depot_id === returnDepot.id)\n\n if (!isEqual(timeRules, returnOpeningTimes)) {\n setReturnOpeningTimes(timeRules)\n }\n\n // Set/Update return time when return depot changes\n setValues((prevValues) => {\n return {\n ...prevValues,\n returnTime: tillAsTime(returnDate, returnTimeslot)\n }\n })\n }\n }, [values.returnDepot, openingTimes])\n\n useEffect(() => {\n const { pickupTimeslot } = values\n\n if (pickupTimeslot && pickupTimeslots) {\n const timeslot = pickupTimeslots.find((timeslot) => timeslot.from_time_integer === pickupTimeslot.from_time_integer)\n\n setValues((prevValues) => {\n return {\n ...prevValues,\n pickupTimeslot: timeslot ? timeslot : null\n }\n })\n }\n }, [pickupTimeslots])\n\n useEffect(() => {\n const { returnTimeslot } = values\n\n if (returnTimeslot && returnTimeslots) {\n const timeslot = returnTimeslots.find((timeslot) => timeslot.from_time_integer === returnTimeslot.from_time_integer)\n\n setValues((prevValues) => {\n return {\n ...prevValues,\n returnTimeslot: timeslot ? timeslot : null\n }\n })\n }\n }, [returnTimeslots])\n\n useEffect(() => {\n const { pickupDate, pickupDepot, pickupTimeslot } = values\n\n if (pickupDate) {\n setValues((prevValues) => {\n return {\n ...prevValues,\n pickupTime: fromAsTime(pickupDate, pickupTimeslot)\n }\n })\n\n if (pickupOpeningTimes.length > 0) {\n const newTimeslots = filterTimeslots(pickupDate, timeslots, pickupOpeningTimes, pickupDepot.id)\n\n if (!isEqual(newTimeslots, pickupTimeslots) && newTimeslots.length > 0) {\n setPickupTimeslots(newTimeslots)\n }\n }\n }\n }, [values.pickupDate, values.pickupDepot, pickupOpeningTimes, timeslots])\n\n useEffect(() => {\n const { returnDate, returnDepot, returnTimeslot } = values\n\n if (returnDate) {\n setValues((prevValues) => {\n return {\n ...prevValues,\n returnTime: tillAsTime(returnDate, returnTimeslot)\n }\n })\n\n if (returnOpeningTimes.length > 0) {\n const newTimeslots = filterTimeslots(returnDate, timeslots, returnOpeningTimes, returnDepot.id)\n\n if (!isEqual(newTimeslots, returnTimeslots) && newTimeslots.length > 0) {\n setReturnTimeslots(newTimeslots)\n }\n }\n }\n }, [values.returnDate, values.returnDepot, returnOpeningTimes, timeslots])\n\n useEffect(() => {\n const { pickupDate, pickupTimeslot } = values\n\n if (pickupTimeslot) {\n setValues((prevValues) => {\n return {\n ...prevValues,\n pickupTime: fromAsTime(pickupDate, pickupTimeslot)\n }\n })\n }\n }, [values.pickupTimeslot])\n\n useEffect(() => {\n const { returnDate, returnTimeslot } = values\n\n if (returnTimeslot) {\n setValues((prevValues) => {\n return {\n ...prevValues,\n returnTime: tillAsTime(returnDate, returnTimeslot)\n }\n })\n }\n }, [values.returnTimeslot])\n\n const handleSave = () => {\n dispatch(updateCart({\n pickup_time: values.pickupTime,\n return_time: values.returnTime,\n pickup_depot_id: values.pickupDepot.id,\n return_depot_id: values.returnDepot.id\n }))\n }\n\n const handleChange = (key, value) => {\n setValues(prevValues => {\n return {\n ...prevValues,\n [key]: value\n }\n })\n }\n\n const hasError = (errors, type, data) => {\n if (errors.length) {\n return errors.find((error) => error.error.data === data && error.error.type === type) !== undefined\n } else {\n return false\n }\n }\n\n const validation = new Validation({ openingTimes, ...values })\n\n return (\n \n \n \n )\n}\n\nexport default Picker\n","import React, { useState, useEffect } from 'react'\n\n// Libraries\nimport { useDispatch, useSelector } from 'react-redux'\nimport moment from '@client/i18n/moment'\n\n// Actions\nimport { updateCart } from '@client/redux/actions/cart'\nimport { openPicker } from '@client/redux/actions/picker'\n\n// Utils\nimport { t } from '@client/i18n/localize'\nimport { getTimeslot } from '@client/utils/timeslots'\n\n// Components\nimport Insurance from './Insurance'\nimport Delivery from './Delivery'\nimport Validation from './Validation'\n\nconst Prices = ({ reservePath, readonly, orderButtonMessage }) => {\n const dispatch = useDispatch()\n\n const cart = useSelector((state) => state.cart)\n const timeslots = useSelector(state => state.timeslots)\n const depots = useSelector(state => state.depots)\n const openingTimes = useSelector(state => state.openingTimes)\n const company = useSelector(state => state.company)\n\n const [hasInsurance, setHasInsurance] = useState(false)\n const [showInsuranceInfo, setShowInsuranceInfo] = useState(null)\n\n const [wantsDelivery, setWantsDelivery] = useState(false)\n const [showDeliveryInfo, setShowDeliveryInfo] = useState(null)\n\n const validationValues = () => {\n const values = {\n pickupTime: cart.pickup_time && moment(cart.pickup_time),\n pickupDate: cart.pickup_time && moment(cart.pickup_time).toDate(),\n pickupDepot: depots.find((depot) => depot.id === cart.pickup_depot_id),\n returnTime: cart.return_time && moment(cart.return_time),\n returnDate: cart.return_time && moment(cart.return_time).toDate(),\n returnDepot: depots.find((depot) => depot.id === cart.return_depot_id)\n }\n\n if (values.pickupTime && values.returnTime) {\n values.pickupTimeslot = getTimeslot(values.pickupTime, timeslots, openingTimes, values.pickupDepot.id, 'pickup')\n values.returnTimeslot = getTimeslot(values.returnTime, timeslots, openingTimes, values.returnDepot.id, 'return')\n }\n\n return values\n }\n\n const validation = new Validation({ openingTimes, ...validationValues() })\n const dataRequired = [cart.return_depot_id, cart.pickup_depot_id, cart.pickup_time, cart.return_time]\n const isValid = dataRequired.indexOf(undefined) === -1 && dataRequired.indexOf(null) === -1\n const canReserve = validation.errors.length === 0 && isValid\n\n useEffect(() => {\n setHasInsurance(cart.wants_insurance)\n }, [cart.wants_insurance])\n\n useEffect(() => {\n setWantsDelivery(cart.delivery_service)\n }, [cart.delivery_service])\n\n const handleToggleInsurance = () => {\n setHasInsurance(!hasInsurance)\n dispatch(updateCart({ wants_insurance: !hasInsurance }))\n }\n\n const handleShowInsuranceInfo = () => {\n setShowInsuranceInfo(!showInsuranceInfo)\n }\n\n const handleToggleDelivery = () => {\n setWantsDelivery(!wantsDelivery)\n dispatch(updateCart({ delivery_service: !wantsDelivery }))\n }\n\n const handleShowDeliveryInfo = () => {\n setShowDeliveryInfo(!showDeliveryInfo)\n }\n\n const handleReserveClick = (e) => {\n e.preventDefault()\n\n if (canReserve && reservePath) {\n window.location = reservePath\n } else {\n dispatch(openPicker())\n }\n }\n\n return (\n <>\n \n
\n {t('prices.subtotal')}\n {cart.formattedSubtotal}\n
\n {cart.discount_in_cents > 0 && (\n
\n {t('prices.discount')}\n {cart.formattedDiscount}\n
\n )}\n {cart.coupon_discount_in_cents > 0 && (\n
\n {t('prices.discount')}\n {cart.formattedCouponDiscount}\n
\n )}\n
\n
\n {!readonly && (\n
\n \n \n
\n )}\n {readonly && t('prices.insurance')}\n
\n
\n
\n
{cart.formattedInsurance}\n
\n {company.delivery_service_price_from &&
\n
\n {!readonly && (\n
\n \n \n
\n )}\n {readonly && t('prices.delivery_service')}\n
\n
\n
\n
{t('prices.delivery_price_from', { price: company.delivery_service_price_from })}\n
}\n {cart.total_in_cents !== cart.subtotal_in_cents && (\n <>\n
\n
\n {t('prices.subtotal')}\n {cart.formattedTotal}\n
\n >\n )}\n {cart.vat_in_cents > 0 && (\n
\n {t('prices.tax_amount', { percentage: cart.vat_amount })}\n {cart.formattedVat}\n
\n )}\n
\n
\n {t('prices.total')}\n {cart.formattedGrandTotal}\n
\n
\n\n {!readonly && (\n \n )}\n >\n )\n}\n\nexport default Prices\n","import { updateQuantity } from '@client/api/carts_products'\n\nconst start = (params) => {\n return {\n type: 'CARTS_PRODUCT_UPDATE_QUANTITY_START',\n payload: params\n }\n}\n\nconst success = ({ cart }) => {\n return {\n type: 'CARTS_PRODUCT_UPDATE_QUANTITY_SUCCESS',\n payload: { cart }\n }\n}\n\nconst error = ({ response }) => {\n return {\n type: 'CARTS_PRODUCT_UPDATE_QUANTITY_ERROR',\n payload: { response }\n }\n}\n\nconst updateQty = ({ id, quantity }) => {\n return (dispatch) => {\n dispatch(start({ id, quantity }))\n\n return updateQuantity({ id, quantity })\n .then(\n (response) => dispatch(success(response.data)),\n (errorData) => dispatch(error({ response: errorData.response }))\n )\n }\n}\n\nexport default updateQty\n","import axios from '@client/lib/axios'\n\nexport const updateQuantity = ({ id, quantity }) => {\n return axios({\n method: 'patch',\n url: `/api/2/carts_products/${id}/update_quantity`,\n params: {\n quantity: quantity\n }\n })\n}\n\nexport const update = ({ id, accessoryIds, quantity }) => {\n return axios({\n method: 'patch',\n url: `/api/2/carts_products/${id}`,\n params: {\n quantity: quantity,\n accessory_ids: accessoryIds\n }\n })\n}\n","import { update } from '@client/api/carts_products'\n\nconst start = (params) => {\n return {\n type: 'CARTS_PRODUCT_UPDATE_START',\n payload: params\n }\n}\n\nconst success = ({ cart }) => {\n return {\n type: 'CARTS_PRODUCT_UPDATE_SUCCESS',\n payload: { cart }\n }\n}\n\nconst error = ({ response }) => {\n return {\n type: 'CARTS_PRODUCT_UPDATE_ERROR',\n payload: { response }\n }\n}\n\nconst updateCartsProduct = ({ id, accessoryIds, quantity }) => {\n return (dispatch) => {\n dispatch(start({ id, accessoryIds, quantity }))\n\n return update({ id, accessoryIds, quantity })\n .then(\n (response) => dispatch(success(response.data)),\n (errorData) => dispatch(error({ response: errorData.response }))\n )\n }\n}\n\nexport default updateCartsProduct\n","import React, { useState } from 'react'\nimport { useDispatch, useSelector } from 'react-redux'\nimport _ from 'lodash'\n\nimport AddOptionalAccessories from '@client/react/components/AddOptionalAccessories'\nimport { clearCart } from '@client/redux/actions/cart'\nimport { updateQuantity, updateCartsProduct } from '@client/redux/actions/carts_products'\nimport { t, translatePeriodLabel } from '@client/i18n/localize'\nimport { track } from '@client/utils/tracking'\nimport { productPath } from '@client/utils'\n\nimport OptionalAccessoryProduct from './OptionalAccessoryProduct'\n\nconst CartProduct = ({ id, product, qty, formattedTotal, charge_label, accessories }) => {\n const dispatch = useDispatch()\n const [quantity, setQuantity] = useState(qty)\n const path = productPath(product.slug)\n\n const [visible, setVisible] = useState(false)\n const hide = () => setVisible(false)\n\n const [loading, setLoading] = useState(false)\n const [success, setSuccess] = useState(null)\n\n const handlePopover = () => {\n setVisible(!visible)\n }\n\n const changeQuantity = (newQuantity) => {\n if (quantity !== newQuantity) {\n dispatch(updateQuantity({ id: id, type: 'change', quantity: newQuantity })).then(({ payload }) => {\n track('cart.change_product_quantity', {\n oldQuantity: quantity,\n newQuantity,\n product,\n cart: payload.cart\n })\n })\n }\n }\n\n const debouncedChangeQuantity = _.debounce(changeQuantity, 300)\n\n const handleChangeQuantity = (newQuantity) => {\n setQuantity(newQuantity)\n debouncedChangeQuantity(newQuantity)\n }\n\n const handleIncreaseQuantity = () => {\n handleChangeQuantity(quantity + 1)\n }\n\n const handleDecreaseQuantity = () => {\n handleChangeQuantity(quantity - 1)\n }\n\n const handleDelete = () => {\n handleChangeQuantity(0)\n }\n\n const handleQuantityInput = (e) => {\n let newQuantity\n\n if (e.target.value === '') {\n newQuantity = 0\n } else {\n newQuantity = _.max([parseInt(e.target.value), 1])\n }\n\n handleChangeQuantity(newQuantity)\n }\n\n const handleUpdateAccessories = ({ accessoryIds = [] }) => {\n setLoading(true)\n\n dispatch(updateCartsProduct({ id, accessoryIds }))\n .then(({ payload }) => {\n if (!payload.cart) { throw ('No cart') }\n\n setSuccess(true)\n setLoading(false)\n hide()\n\n track('cart.book_product', {\n productId: id,\n cart: payload.cart,\n source\n })\n\n setTimeout(() => { setSuccess(null) }, 2000)\n })\n .catch(() => {\n setSuccess(false)\n setLoading(false)\n hide()\n })\n }\n\n const renderAccessoryPopover = () => {\n const component = handlePopover()} />\n\n return (\n hide()}\n onClick={(props) => handleUpdateAccessories(props)}\n accessories={product.optional_pickup_accessories}\n selectedAccessories={accessories}\n loading={loading}\n success={success}\n productName={product.name}\n type=\"edit\"\n >{component}\n\n )\n }\n\n if (quantity === 0) return null\n\n return (\n \n
\n

\n
\n
\n
{product.name}\n
\n
\n
\n \n {quantity}\n \n \n
\n
\n
{formattedTotal}
\n
{translatePeriodLabel(charge_label)}
\n
\n
\n {accessories.length > 0 &&\n
\n
\n {t(\"accessories\")}\n \n
\n
\n {renderAccessoryPopover()}\n
\n
\n }\n
\n
\n )\n}\n\nconst Products = ({ showClearCart = true }) => {\n const dispatch = useDispatch()\n const cart = useSelector((state) => state.cart)\n\n const cartsProducts = cart.carts_products\n const mainProducts = cartsProducts.filter(item => item.parent_id === null)\n const accessoryProducts = cartsProducts.filter(item => item.parent_id !== null)\n\n const handleClickClear = (e) => {\n e.preventDefault()\n\n dispatch(clearCart())\n window.location.reload()\n }\n\n const getSelectedAccessoriesForProduct = (cartsProductId) => {\n return accessoryProducts.filter(item => item.parent_id === cartsProductId)\n }\n\n return (\n <>\n \n
{t('shopping_cart.title')}
\n {showClearCart && (\n \n {t('shopping_cart.clear')}\n \n )}\n \n\n {\n mainProducts.map((cartsProduct) => (\n \n ))\n }\n >\n )\n}\n\nexport default Products\n","import React, { useState, useRef } from 'react'\n\n// Libraries\nimport { t } from '@client/i18n/localize'\nimport { CopyToClipboard } from 'react-copy-to-clipboard';\n\n// Components\nimport Modal from '@client/react/components/Modal'\n\nconst Share = ({ url }) => {\n const [show, setShow] = useState(null)\n const [copySuccess, setCopySuccess] = useState(false)\n\n const handleOpen = (e) => {\n e && e.preventDefault()\n setShow(true)\n setCopySuccess(false)\n }\n\n const handleClose = (e) => {\n e && e.preventDefault()\n setShow(false)\n }\n\n const onCopy = () => {\n setCopySuccess(true)\n }\n\n return (\n <>\n \n {t('shopping_cart.share.link')}\n
\n\n \n \n {t('shopping_cart.share.title')}\n \n \n \n
{t('shopping_cart.share.text')}
\n
\n
\n \n \n \n {copySuccess ?\n \n \n
\n : {t('copy')}\n }\n \n \n \n >\n )\n}\n\nexport default Share\n","import moment from '@client/i18n/moment'\nimport some from 'lodash/some'\nimport every from 'lodash/every'\n\nconst diffInDays = (date1, date2) => {\n return Math.abs(moment(date1).clone().startOf('day').diff(moment(date2).clone().startOf('day'), 'days'))\n}\n\nconst timeAsInteger = (datetime) => {\n datetime = moment(datetime)\n return (datetime.hours() * 100) + datetime.minutes()\n}\n\nconst timeStringAsInteger = (timeString) => {\n const [hourString, minuteString] = timeString.split(':')\n return (parseInt(hourString) * 100) + parseInt(minuteString)\n}\n\nconst isOpeningTimeRelevantToDomain = (openingTime, domainId) => {\n return [domainId, null, undefined].includes(openingTime.domain_id)\n}\n\nconst isOpeningTimeRelevantToDepot = (openingTime, depotId) => {\n return [depotId, null, undefined].includes(openingTime.depot_id)\n}\n\nconst openingTimesForDepot = (depot, openingTimes) => {\n return openingTimes.filter((openingTime) =>\n isOpeningTimeRelevantToDomain(openingTime, depot.domain_id) &&\n isOpeningTimeRelevantToDepot(openingTime, depot.id)\n )\n}\n\n/**\n * Returns whether given day is open for the given depot.\n */\nexport const isDayOpen = (date, depot, openingTimes) => {\n const depotOpeningTimes = openingTimesForDepot(depot, openingTimes)\n const dateOpeningTimes = depotOpeningTimes.filter((o) => o.date && o.date === date.format('YYYY-MM-DD'))\n const recurringOpeningTimes = depotOpeningTimes.filter((o) => !o.date && o.weekday === date.day())\n\n // Check by date\n const dateOpen = every(dateOpeningTimes, (openingTime) => {\n if (openingTime.entire_day) {\n return openingTime.open\n } else {\n return true\n }\n })\n\n if (!dateOpen) {\n return false\n }\n\n // Check by recurring day of the week\n return some(recurringOpeningTimes, (openingTime) => {\n return openingTime.open\n })\n}\n\n/**\n * Returns whether given timeslot is open for the given depot and date.\n */\nexport const isTimeslotOpen = (timeslot, date, depot, openingTimes) => {\n if (!isDayOpen(date, depot, openingTimes)) {\n return false\n }\n\n const depotOpeningTimes = openingTimesForDepot(depot, openingTimes)\n .filter((openingTime) => {\n return !openingTime.entire_day && openingTime.weekday === date.day() || openingTime.date === date.format('YYYY-MM-DD')\n })\n\n const timeslotFrom = timeStringAsInteger(timeslot.from_time)\n const timeslotTill = timeStringAsInteger(timeslot.till_time)\n\n return every(depotOpeningTimes, (openingTime) => {\n if (openingTime.entire_day) {\n return openingTime.open\n }\n\n const from = timeStringAsInteger(openingTime.from_time)\n const till = timeStringAsInteger(openingTime.until_time)\n\n // Timeslot should overlap the depot opening times\n const withinOpeningTime = Math.max(from, timeslotFrom) < Math.min(till, timeslotTill)\n\n if (openingTime.open) {\n return withinOpeningTime\n } else {\n return !withinOpeningTime\n }\n })\n}\n\n/**\n * Returns whether the given day is disabled for given options.\n */\nexport const isDayDisabled = ({ day, type, pickupAt, returnAt, pickupDepot, returnDepot, openingTimes, firstAvailableAt }) => {\n const date = moment(day)\n\n // Disable days before the first available moment\n if (firstAvailableAt && date.isBefore(moment(firstAvailableAt), 'day')) {\n return true\n }\n\n // Disabled past\n if (date < moment().startOf('day')) {\n return true\n }\n\n // Return day not before pickup day\n if (type === 'return' && pickupAt && date.startOf('day') < pickupAt.clone().startOf('day')) {\n return true\n }\n\n // Closed pickup day\n if (type === 'pickup' && pickupDepot && !isDayOpen(date, pickupDepot, openingTimes)) {\n return true\n }\n\n // Closed return day\n if (type === 'return' && returnDepot && !isDayOpen(date, returnDepot, openingTimes)) {\n return true\n }\n\n return false\n}\n\n/**\n * Returns whether the given timeslot is disabled for given options.\n */\nexport const isTimeslotDisabled = ({ timeslot, type, pickupAt, returnAt, pickupDepot, returnDepot, openingTimes, firstAvailableAt }) => {\n // Disable timeslots before the first available moment\n if (type === 'pickup' && firstAvailableAt && pickupAt.isSame(moment(firstAvailableAt), 'day')) {\n if (timeslot.from_time_integer < timeAsInteger(firstAvailableAt)) {\n return true\n }\n }\n\n // Disable past pickups\n if (type === 'pickup' && pickupAt && diffInDays(pickupAt, moment()) === 0) {\n if (timeslot.till_time_integer < timeAsInteger(moment())) {\n return true\n }\n }\n\n // Disable past returns\n if (type === 'return' && returnAt && diffInDays(returnAt, moment()) === 0) {\n if (timeslot.from_time_integer < timeAsInteger(moment())) {\n return true\n }\n }\n\n // Pickup and return are on the same day\n if (pickupAt && returnAt && diffInDays(pickupAt, returnAt) === 0) {\n // Disable pickup time later than return time\n if (type === 'pickup') {\n if (timeslot.till_time_integer > timeAsInteger(returnAt)) {\n return true\n }\n }\n\n // Disable return time earlier than pickup time\n if (type === 'return') {\n if (timeslot.from_time_integer < timeAsInteger(pickupAt)) {\n return true\n }\n }\n }\n\n // Return time is later than 60 days\n if (diffInDays(returnAt, pickupAt) >= 60) {\n return true\n }\n\n // Closed pickup timeslot\n if (type === 'pickup' && pickupAt && pickupDepot && !isTimeslotOpen(timeslot, pickupAt, pickupDepot, openingTimes)) {\n return true\n }\n\n // Closed return timeslot\n if (type === 'return' && returnAt && returnDepot && !isTimeslotOpen(timeslot, returnAt, returnDepot, openingTimes)) {\n return true\n }\n\n return false\n}\n","// Libraries\nimport moment from '@client/i18n/moment'\n\n// Utils\nimport { isDayDisabled, isTimeslotDisabled } from '@client/utils/openingTimes'\nimport { t } from '@client/i18n/localize'\n\n/*\n props: {\n pickupDate,\n pickupTime,\n pickupTimeslot,\n pickupDepot,\n returnDate,\n returnTime,\n returnTimeslot,\n returnDepot,\n openingTimes\n }\n*/\nclass Validation {\n constructor(props) {\n this.props = {}\n Object.assign(this.props, props)\n }\n\n get firstAvailableAt() {\n return this.props.pickupDepot ? moment(this.props.pickupDepot.first_available_at).toDate() : null\n }\n\n isDayAvailable(day, type) {\n return !isDayDisabled({\n day,\n type,\n pickupAt: this.props.pickupTime,\n returnAt: this.props.returnTime,\n pickupDepot: this.props.pickupDepot,\n returnDepot: this.props.returnDepot,\n openingTimes: this.props.openingTimes,\n firstAvailableAt: this.firstAvailableAt\n })\n }\n\n isTimeslotAvailable(timeslot, type) {\n return !isTimeslotDisabled({\n timeslot,\n type,\n pickupAt: this.props.pickupTime,\n returnAt: this.props.returnTime,\n pickupDepot: this.props.pickupDepot,\n returnDepot: this.props.returnDepot,\n openingTimes: this.props.openingTimes,\n firstAvailableAt: this.firstAvailableAt\n })\n }\n\n valuesFor(type) {\n switch (type) {\n case 'pickup':\n return {\n date: this.props.pickupDate,\n depot: this.props.pickupDepot,\n timeslot: this.props.pickupTimeslot\n }\n case 'return':\n return {\n date: this.props.returnDate,\n depot: this.props.returnDepot,\n timeslot: this.props.returnTimeslot\n }\n }\n }\n\n get errors() {\n const types = ['pickup', 'return']\n const errors = []\n\n if (this.props.pickupTime && this.props.returnTime) {\n const diffDays = this.props.returnTime.diff(this.props.pickupTime, 'days')\n const pickupAfter = this.props.pickupTime.isAfter(this.props.returnTime)\n\n if (diffDays >= 30) {\n errors.push({\n message: t('shopping_cart.periode_error_duration'),\n error: { data: 'date', type: 'return', value: this.props.returnDate }\n })\n }\n\n if (pickupAfter) {\n errors.push({\n message: t('shopping_cart.periode_error_return_before_pickup'),\n error: { data: 'date', type: 'return', value: this.props.returnDate }\n })\n }\n }\n\n types.forEach((type) => {\n const value = this.valuesFor(type)\n\n if (!value.date || !value.depot || !value.timeslot) return null\n\n if (!this.isDayAvailable(value.date, type)) {\n errors.push({\n message: t(`shopping_cart.periode_error_${type}.date`),\n error: { data: 'date', type, value: value.date }\n })\n } else if (!this.isTimeslotAvailable(value.timeslot, type)) {\n errors.push({\n message: t(`shopping_cart.periode_error_${type}.timeslot`),\n error: { data: 'timeslot', type, value: value.timeslot }\n })\n }\n })\n\n return errors\n }\n}\n\nexport default Validation\n","import React, { useState } from 'react'\n\n// Libraries\nimport { useSelector } from 'react-redux'\n\n// Utils\nimport { t, translatePeriodLabel } from '@client/i18n/localize'\nimport useClickOutside from '@client/react/hooks/useClickOutside'\nimport { productPath, productsPath } from '@client/utils'\n\nconst Product = ({ name, image, slug, quantity, priceLabel, formattedTotal }) => {\n return (\n \n

\n
\n
{name}\n {quantity > 1 &&
{t('shopping_cart.amount')} {quantity}}\n
\n
\n
{formattedTotal}
\n
{translatePeriodLabel(priceLabel)}
\n
\n
\n )\n}\n\nconst Dropdown = ({ cartPath, cart }) => {\n return (\n \n {cart.count === 0 && }\n {cart.count > 0 &&
}\n
\n )\n}\n\nconst Empty = () => {\n return (\n <>\n \n
{t('shopping_cart.empty.title')}
\n \n {t('shopping_cart.empty.body')}
\n {t('shopping_cart.empty.button')}\n >\n )\n}\n\nconst List = ({ cartPath, cart }) => {\n const mainProducts = cart.carts_products.filter(item => item.parent_id === null)\n\n return (\n <>\n \n \n {mainProducts.map((cartProduct) => (\n
\n ))}\n
\n \n
{t('prices.subtotal')}
\n
\n
{cart.formattedSubtotal}
\n
({t('prices.excluding_tax')})
\n
\n
\n \n >\n )\n}\n\nconst CartIcon = ({ cartPath, dropdown }) => {\n const cart = useSelector(state => state.cart)\n\n const [collapsed, setCollapsed] = useState(true)\n const node = useClickOutside(() => { setCollapsed(true) })\n\n const handleToggle = (e) => {\n e && e.preventDefault()\n setCollapsed(!collapsed)\n }\n\n const classNames = ['dropdown shopping-cart']\n classNames.push(collapsed ? 'dropdown--collapsed' : 'dropdown--expanded')\n\n const cartIcon = cart.count > 8 ? 'fa-truck-field' : 'fa-shopping-cart'\n const dropdownIcon = collapsed ? 'fa-chevron-down' : 'fa-chevron-up'\n const cartCount = cart.count > 99 ? '99+' : cart.count\n\n return (\n \n )\n}\n\nexport default CartIcon\n","// React\nimport React, { useState, useRef, useEffect } from 'react'\nimport { createPortal } from 'react-dom'\n\n// Components\nimport Calendar from './Picker/Calendar'\n\n// Libraries\nimport { usePopper } from 'react-popper'\n\n// Shared\nimport { date } from '@client/i18n/localize'\nimport getPopoverPosition from '@client/utils/getPopoverPosition'\n\nconst PopoverContainer = ({ style, forwardRef, children, ...props }) => (\n \n {children}\n
\n)\n\nconst Popover = (props) => {\n const useOutsideClick = (ref) => {\n useEffect(() => {\n const handleOutsideClick = (e) => {\n if (ref && ref.current && !ref.current.contains(e.target)) {\n if (props.parentRef && props.parentRef.current && props.parentRef.current.contains(e.target)) return\n\n props.handleOutsideClick()\n }\n }\n\n document.addEventListener('mousedown', handleOutsideClick)\n\n return () => {\n document.removeEventListener('mousedown', handleOutsideClick)\n }\n }, [ref])\n }\n\n const ref = useRef(null)\n useOutsideClick(ref)\n\n const selectedDay = (type, range) => {\n const date = {\n \"pickup\": range.from,\n \"return\": range.to || range.from\n }\n\n return date[type]\n }\n\n return (\n \n \n
\n )\n}\n\nconst DatePicker = (props) => {\n const [popoverOpen, setPopoverOpen] = useState(false)\n\n const [referenceElement, setReferenceElement] = useState(null)\n const [popperElement, setPopperElement] = useState(null)\n const { styles, attributes } = usePopper(referenceElement, popperElement, {\n placement: 'bottom-start',\n modifiers: [\n {\n name: 'flip',\n options: {\n fallbackPlacements: ['top-start'],\n },\n },\n {\n name: 'offset',\n options: {\n offset: [0, -5],\n },\n }\n ],\n })\n\n useEffect(() => {\n if (ref && ref.current) {\n setPopoverPosition(getPopoverPosition(ref.current, props.popoverPosition, true))\n }\n }, [ref])\n\n const ref = useRef(null)\n\n const handleOutsideClick = (e) => {\n e && e.preventDefault()\n setPopoverOpen(false)\n }\n\n const handleDateChange = (value) => {\n props.onChange(value)\n setPopoverOpen(false)\n }\n\n const hasError = (type, data) => {\n if (props.errors.length) {\n return props.errors.find((error) => error.error.data === data && error.error.type === type) !== undefined\n }\n\n return false\n }\n\n return (\n <>\n {\n if (!props.disabled) {\n setPopoverOpen(!popoverOpen)\n }\n }}\n >\n
\n {props.value ? (\n
\n \n {date(props.value)}\n
\n ) : (\n
{props.placeholder}
\n )}\n
\n {!props.disabled && (\n
\n \n
\n )}\n
\n {popoverOpen &&\n createPortal(\n \n \n ,\n document.body\n )}\n >\n )\n}\n\nexport default DatePicker\n","/**\n * Get popover position from target dom node in a desired direction and with an optional offset\n *\n * @param {Node} node\n * Target node\n * @param {String} direction\n * One of: top, right, bottom, left, topRight, bottomLeft, topLeft, bottomRight\n * @param {Boolean} scrollable\n * Offset from target in pixels\n * @example\n * getPopoverPosition(node, 'left', true)\n * @return {Object} style properties for desired position\n */\nexport default function getPopoverPosition (node, direction, scrollable, offset = 0, clampX) {\n const boundingRect = node.getBoundingClientRect()\n\n const top = scrollable ? boundingRect.top + window.scrollY : boundingRect.top\n const bottom = scrollable ? boundingRect.bottom + window.scrollY : boundingRect.bottom\n\n switch (direction) {\n case 'top':\n return {\n top: top - offset,\n left: clampX || boundingRect.left + boundingRect.width / 2,\n transform: 'translate(-50%, -100%)'\n }\n case 'right':\n return {\n top: top + boundingRect.height / 2,\n left: boundingRect.right + offset,\n transform: 'translateY(-50%)'\n }\n case 'bottom':\n return {\n top: bottom + offset,\n left: clampX || boundingRect.left + boundingRect.width / 2,\n transform: 'translateX(-50%)'\n }\n case 'left':\n return {\n top: top + boundingRect.height / 2,\n left: boundingRect.left - offset,\n transform: 'translate(-100%, -50%)'\n }\n case 'topRight':\n return {\n // 20 is the offset the popover arrow has here\n top: top + boundingRect.height / 2 + 20,\n left: boundingRect.right + offset,\n transform: 'translateY(-100%)'\n }\n case 'topLeft':\n return {\n top: top + boundingRect.height / 2 + 20,\n left: boundingRect.left - offset,\n transform: 'translate(-100%, -100%)'\n }\n case 'bottomRight':\n return {\n top: top + boundingRect.height / 2 - 20,\n left: boundingRect.right + offset,\n transform: 'translateY(0%)'\n }\n case 'bottomLeft':\n return {\n top: bottom + offset,\n left: clampX || boundingRect.left\n }\n }\n}\n","import React, { useState, useEffect } from 'react'\n\n// API\nimport { fetchEssentials } from '@client/api/products'\n\n// Libraries\nimport sortBy from 'lodash/sortBy'\n\n// Utils\nimport { t } from '@client/i18n/localize'\n\n// Components\nimport ProductList from './ProductList'\nimport Modal from '@client/react/components/Modal'\n\nconst Category = ({ name, products }) => {\n return (\n <>\n {name}
\n \n >\n )\n}\n\nconst Essentials = ({ id, name, initialize, setInitialize }) => {\n const [essentials, setEssentials] = useState(null)\n const [show, setShow] = useState(false)\n\n useEffect(() => {\n if (!initialize) return\n\n fetchEssentials({ id }).then(({ data }) => {\n const products = data.products\n const categories = data.meta.categories\n\n if (products.length === 0) {\n return\n }\n\n // Group products by category name\n const categoryData = categories.map((category) => {\n return {\n ...category,\n products: products.filter((product) => product.essential_category_id === category.id)\n }\n })\n\n // Sort by product count\n const sortedData = sortBy(categoryData, (data) => -data.products.length)\n\n setEssentials(sortedData)\n setShow(true)\n })\n }, [initialize])\n\n const handleClose = (e) => {\n e && e.preventDefault()\n setShow(false)\n setInitialize(false)\n }\n\n return (\n \n \n {t('essentials.for_product', { name })}\n {t('essentials.make_complete')}\n \n \n {essentials?.map((data) => (\n \n ))}\n \n \n )\n}\n\nexport default Essentials\n","import React from 'react'\n\nconst Loader = ({ className }) => {\n return \n}\n\nexport default Loader\n","import React, { useRef, useEffect } from 'react'\n\n// Utils\nimport { isEmpty } from 'lodash'\n\nconst ModalTitle = ({ children }) => {\n return (\n {children}
\n )\n}\n\nconst ModalSubTitle = ({ children }) => {\n return (\n {children}
\n )\n}\n\nconst ModalHeader = ({ children, border }) => {\n const modifiers = []\n\n if (border) { modifiers.push('border') }\n\n return (\n `bc-modal__header--${m}`)}`}>\n {children}\n
\n )\n}\n\nconst ModalBody = ({ children }) => {\n return (\n {children}
\n )\n}\n\nconst ModalFooter = ({ children }) => {\n return (\n {children}
\n )\n}\n\nconst Modal = ({ children, onClose, onClickOutside, show }) => {\n const backgroundNode = useRef(null)\n\n const handleOutsideClick = (e) => {\n if (e.target === backgroundNode.current) {\n (onClickOutside && onClickOutside()) || (onClose && onClose())\n }\n }\n\n const handleToggleShow = (show) => {\n const html = document.querySelector('html')\n\n if (show) {\n if (isEmpty(html.style.top)) {\n // Only set top when it's not set\n html.style.top = `-${window.scrollY}px`\n }\n html.classList.add('scroll-lock')\n } else {\n if (document.querySelector('.bc-modal')) {\n // Don't hide when another modal is present\n return\n }\n\n const scrollY = html.style.top;\n\n if (isEmpty(scrollY)) {\n // Don't reset scroll since Modal has not been shown yet\n return\n }\n\n html.style.top = '';\n html.classList.remove('scroll-lock')\n window.scrollTo(0, parseInt(scrollY || '0') * -1)\n }\n }\n\n useEffect(() => {\n if (show !== null) {\n handleToggleShow(show)\n }\n }, [show])\n\n if (!show) return null\n\n return (\n \n
\n {onClose && }\n {children}\n
\n
\n )\n}\n\nexport default Object.assign(Modal, {\n Header: ModalHeader,\n Title: ModalTitle,\n SubTitle: ModalSubTitle,\n Body: ModalBody,\n Footer: ModalFooter\n})\n","import { ReactNotifications, Store } from 'react-notifications-component'\n\nconst notification = ({ title, message, type = 'default' }) => {\n Store.addNotification({\n title: title,\n message: message,\n type: type,\n insert: \"top\",\n container: \"top-center\",\n animationIn: [\"animate__animated\", \"animate__fadeIn\"],\n animationOut: [\"animate__animated\", \"animate__fadeOut\"],\n dismiss: {\n duration: 2000\n }\n })\n}\n\nexport { ReactNotifications as default, notification }\n","import React from \"react\";\n\nconst HomeScreenIcon = ({ className, modern = false }) => {\n if (modern) {\n return (\n \n );\n }\n\n return (\n \n );\n};\n\nexport default HomeScreenIcon;\n","import React, { useEffect, useState } from \"react\";\n\nimport ShareIcon from \"./ShareIcon\";\nimport HomeScreenIcon from \"./HomeScreenIcon\";\n\nconst PWAPrompt = ({\n delay,\n copyTitle,\n copyBody,\n copyAddHomeButtonLabel,\n copyShareButtonLabel,\n copyClosePrompt,\n permanentlyHideOnDismiss,\n promptData,\n maxVisits,\n onClose,\n}) => {\n const [isVisible, setVisibility] = useState(!Boolean(delay));\n\n useEffect(() => {\n if (delay) {\n setTimeout(() => {\n // Prevent keyboard appearing over the prompt if a text input has autofocus set\n if (document.activeElement) {\n document.activeElement.blur();\n }\n\n setVisibility(true);\n }, delay);\n }\n }, []);\n\n useEffect(() => {\n if (isVisible) {\n document.body.classList.add('noScroll');\n }\n }, [isVisible]);\n\n const isiOS13 = /OS 13/.test(window.navigator.userAgent);\n const visibilityClass = isVisible ? 'visible' : 'hidden';\n\n const dismissPrompt = (evt) => {\n document.body.classList.remove('noScroll');\n setVisibility(false);\n\n if (permanentlyHideOnDismiss) {\n localStorage.setItem(\n \"iosPwaPrompt\",\n JSON.stringify({\n ...promptData,\n visits: maxVisits,\n })\n );\n }\n\n if (typeof onClose === \"function\") {\n onClose(evt);\n }\n };\n\n const onTransitionOut = (evt) => {\n if (!isVisible) {\n evt.currentTarget.style.display = \"none\";\n }\n };\n\n return (\n <>\n \n \n
\n
\n {copyTitle}\n
\n
\n {copyClosePrompt}\n \n
\n
\n
\n
\n
\n
\n {copyShareButtonLabel}\n
\n
\n
\n
\n
\n {copyAddHomeButtonLabel}\n
\n
\n
\n
\n >\n );\n};\n\nexport default PWAPrompt;\n","import React from \"react\";\n\nconst ShareIcon = ({ className, modern = false }) => {\n if (modern) {\n return (\n \n );\n }\n\n return (\n \n );\n};\n\n//#007aff\n\nexport default ShareIcon;\n","// https://github.com/chrisdancee/react-ios-pwa-prompt\n\nimport React from \"react\";\n\nimport PWAPrompt from \"./components/PWAPrompt\";\n\nconst deviceCheck = () => {\n const isiOS = /iphone|ipad|ipod/.test(\n window.navigator.userAgent.toLowerCase()\n );\n const isiPadOS =\n navigator.platform === \"MacIntel\" && navigator.maxTouchPoints > 1;\n const isStandalone =\n \"standalone\" in window.navigator && window.navigator.standalone;\n\n return (isiOS || isiPadOS) && !isStandalone;\n};\n\nexport default ({\n timesToShow = 1,\n promptOnVisit = 1,\n permanentlyHideOnDismiss = true,\n copyTitle = \"Add to Home Screen\",\n copyBody = \"This website has app functionality. Add it to your home screen to use it in fullscreen and while offline.\",\n copyShareButtonLabel = \"1) Press the 'Share' button on the menu bar below.\",\n copyAddHomeButtonLabel = \"2) Press 'Add to Home Screen'.\",\n copyClosePrompt = \"Cancel\",\n delay = 1000,\n debug = false,\n onClose = () => {},\n}) => {\n let promptData = JSON.parse(localStorage.getItem(\"iosPwaPrompt\"));\n\n if (promptData === null) {\n promptData = { isiOS: deviceCheck(), visits: 0 };\n localStorage.setItem(\"iosPwaPrompt\", JSON.stringify(promptData));\n }\n\n if (promptData.isiOS || debug) {\n const aboveMinVisits = promptData.visits + 1 >= promptOnVisit;\n const belowMaxVisits = promptData.visits + 1 < promptOnVisit + timesToShow;\n\n if (belowMaxVisits || debug) {\n localStorage.setItem(\n \"iosPwaPrompt\",\n JSON.stringify({\n ...promptData,\n visits: promptData.visits + 1,\n })\n );\n\n if (aboveMinVisits || debug) {\n return (\n \n );\n }\n }\n }\n\n return null;\n};\n","import React from 'react'\nimport { DayPicker } from 'react-day-picker'\n\nimport { getDateFnsLocale } from '@client/i18n/localize'\n\nconst Calendar = ({ type, selectedDay, onChange, isDayAvailable, firstAvailableAt, range }) => {\n const unavailable = day => {\n return !isDayAvailable(day, type)\n }\n\n const handleClickDay = (day, modifiers = {}) => {\n if (modifiers.disabled || modifiers.unavailable) {\n return\n }\n\n onChange(day)\n }\n\n return (\n \n )\n}\n\nexport default Calendar\n","// React\nimport React from 'react'\n\n// Libraries\nimport groupBy from 'lodash/groupBy'\nimport map from 'lodash/map'\n\n// Components\nimport Select from '../Select'\nimport DatePicker from '../DatePicker'\nimport OptionSidebar from './OptionSidebar'\nimport SelectPlaceholder from './SelectPlaceholder'\n\n// Utils\nimport { t } from '@client/i18n/localize'\n\n// Shared\n\nconst Desktop = ({\n errors,\n pickupDepots,\n returnDepots,\n values,\n handleDepotSelect,\n isDayAvailable,\n isTimeslotAvailable,\n range,\n handleDateSelect,\n pickupTimeslots,\n handleTimeslotSelect,\n returnTimeslots,\n hasError,\n firstAvailableAt\n}) => {\n\n const groupReturnDepots = (depots) => {\n const grouped = groupBy(depots, (depot) => depot.domain.country_name)\n return map(grouped, (v, k) => ({ label: k, options: v }))\n }\n\n const returnDepotOptions = groupReturnDepots(returnDepots)\n\n return (\n \n
\n
{t('cart_picker.pickup')}
\n
\n
\n
\n
\n
\n handleDateSelect('pickup', value)}\n errors={errors}\n isDayAvailable={isDayAvailable}\n firstAvailableAt={firstAvailableAt}\n range={range}\n type=\"pickup\"\n placeholder={}\n popoverPosition=\"bottomLeft\"\n value={values.pickupDate}\n disabled={!values.pickupDepot}\n icon=\"calendar-alt\"\n id=\"pickup_date\"\n />\n
\n
\n
\n
\n
\n
\n
\n
{t('cart_picker.return')}
\n
\n
\n
\n
\n
\n handleDateSelect('return', value)}\n errors={errors}\n isDayAvailable={isDayAvailable}\n range={range}\n type=\"return\"\n placeholder={}\n popoverPosition=\"bottomLeft\"\n value={values.returnDate}\n disabled={!values.returnDepot}\n icon=\"calendar-alt\"\n id=\"return_date\"\n />\n
\n
\n
\n
\n
\n
\n
\n )\n}\n\nexport default Desktop\n","// React\nimport React from 'react'\n\n// Components\n\n// Libraries\nimport Tooltip from '@client/react/components/Tooltip'\n\n// Shared\nimport isMobile from '@client/utils/isMobile'\nimport { t, calendarDate } from '@client/i18n/localize'\nimport { fromAsTime, tillAsTime } from '@client/utils/timeslots'\n\nconst formatCalendarDate = (date, timeslot, type, format = false) => {\n const datetime = (type === 'return') ? tillAsTime(date, timeslot) : fromAsTime(date, timeslot)\n return calendarDate(datetime, format)\n}\n\nconst Action = ({ date, depot, type, timeslot, onClick, label }) => {\n const noData = !date || !depot || !timeslot\n\n if (isMobile()) {\n return (\n \n {noData ? (\n label\n ) : (\n <>\n
{depot.name}
\n
{formatCalendarDate(date, timeslot, type, true)}
\n >\n )}\n
\n )\n } else {\n return (\n \n {noData ? (\n label\n ) : (\n
\n
{depot.name}
\n
|
\n
{formatCalendarDate(date, timeslot, type)}
\n
\n
\n )}\n
\n )\n }\n}\n\nconst Intro = ({ values, onClick, errors }) => {\n const { pickupTime, returnTime, pickupDepot, returnDepot, pickupTimeslot, returnTimeslot } = values\n const noData = !pickupTime || !returnTime || !pickupDepot || !returnDepot\n const hasErrors = errors.length !== 0\n\n if (noData) {\n if (isMobile()) {\n return (\n \n {t('cart_picker.choose_period_short')}\n
\n )\n } else {\n return (\n \n {t('cart_picker.price_duration')} {t('cart_picker.choose_period_short')}\n
\n )\n }\n }\n\n const wrapperClasses = ['hstack justify-content-center bc-date-select-wrapper']\n if (hasErrors) wrapperClasses.push('bc-date-select-wrapper--error')\n\n return (\n \n )\n}\n\nexport default Intro\n","// React\nimport React, { useState } from 'react'\n\n// Components\nimport Details from './modes'\nimport Dates from './modes/Dates'\nimport Depots from './modes/Depots'\nimport Timeslots from './modes/Timeslots'\n\n// Libraries\nimport { date, t } from '@client/i18n/localize'\n\n// Shared\n\nconst Select = ({ id, value, onClick, icon, iconStyle, type, placeholder, disabled, error }) => {\n const getValue = (mode) => {\n switch (mode) {\n case 'depot':\n return value.name\n case 'date':\n return date(value)\n case 'timeslot':\n return value.label\n }\n }\n\n return (\n \n {icon &&
}\n {value ? (\n
\n {getValue(type)}\n
\n ) : (\n
\n {placeholder}\n
\n )}\n
\n
\n )\n}\n\nconst Mobile = ({\n values,\n pickupDepots,\n returnDepots,\n pickupTimeslots,\n returnTimeslots,\n isDayAvailable,\n isTimeslotAvailable,\n handleDepotSelect,\n handleDateSelect,\n handleTimeslotSelect,\n range,\n errors,\n hasError\n}) => {\n const [mode, setMode] = useState(null)\n\n const onBackPress = () => setMode(null)\n\n const titles = {\n pickupDepot: t('cart_picker.pickup_depot'),\n pickupTimeslot: t('cart_picker.pickup_timeslot'),\n pickupDate: t('cart_picker.pickup_date'),\n returnDepot: t('cart_picker.return_depot'),\n returnTimeslot: t('cart_picker.return_timeslot'),\n returnDate: t('cart_picker.return_date'),\n }\n\n const modes = (mode) => {\n switch (mode) {\n case 'pickupDepot':\n return (\n {\n handleDepotSelect('pickup', depot)\n setMode(null)\n }}\n />\n )\n case 'pickupDate':\n return (\n {\n handleDateSelect('pickup', value)\n setMode(null)\n }}\n />\n )\n case 'pickupTimeslot':\n return (\n {\n handleTimeslotSelect('pickup', value)\n setMode(null)\n }}\n timeslots={pickupTimeslots}\n type=\"pickup\"\n errors={errors}\n values={values}\n />\n )\n case 'returnDepot':\n return (\n {\n handleDepotSelect('return', depot)\n setMode(null)\n }}\n errors={errors}\n type=\"return\"\n />\n )\n case 'returnDate':\n return (\n {\n handleDateSelect('return', value)\n setMode(null)\n }}\n errors={errors}\n />\n )\n case 'returnTimeslot':\n return (\n {\n handleTimeslotSelect('return', value)\n setMode(null)\n }}\n timeslots={returnTimeslots}\n type=\"return\"\n errors={errors}\n />\n )\n }\n }\n\n return (\n \n
\n
{t('cart_picker.pickup')}
\n
\n
\n
{t('cart_picker.return')}
\n
setMode('returnDepot')}\n error={hasError(errors, 'return', 'depot')}\n />\n \n setMode('returnDate')}\n type=\"date\"\n disabled={!values.returnDepot}\n error={hasError(errors, 'return', 'date')}\n />\n setMode('returnTimeslot')}\n type=\"timeslot\"\n icon=\"clock\"\n iconStyle=\"fas\"\n disabled={!values.returnDate}\n error={hasError(errors, 'return', 'timeslot')}\n />\n
\n \n {mode && (\n
\n {modes(mode)}\n \n )}\n
\n )\n}\n\nexport default Mobile\n","// React\nimport React, { useState, useEffect, useRef } from 'react'\nimport { createPortal } from 'react-dom'\n\n// Components\n\n// Libraries\nimport { t } from '@client/i18n/localize'\n\n// Shared\n\nconst Sidebar = ({ data: depot, style }) => {\n return (\n \n
\n
\n
{depot.name}
\n
{t(`depot.store_types.${depot.store_type}`)}
\n
\n

\n
\n
{depot.excerpt}\n
\n
\n
\n {depot.street} {depot.street_no}\n {depot.zipcode} {depot.city}\n
\n
\n {depot.opening_hours.length > 0 && (\n
\n
\n
\n {depot.opening_hours.map((openingHour, i) => (\n
\n {openingHour.days}\n {openingHour.time}\n
\n ))}\n
\n
\n )}\n
\n )\n}\n\nconst Option = (props) => {\n const [entered, setEntered] = useState(false)\n const [popoverPosition, setPopoverPosition] = useState({ left: -10000, top: -10000 })\n\n useEffect(() => {\n if (ref && ref.current) {\n const boundingRect = ref.current.closest('.bc-select__menu-list').getBoundingClientRect()\n const popoverPosition = {\n position: 'fixed',\n top: boundingRect.top - 1,\n left: boundingRect.left + boundingRect.width - 1\n }\n\n setPopoverPosition(popoverPosition)\n }\n }, [ref])\n\n const ref = useRef(null)\n\n const handleMouseEnter = () => {\n setEntered(true)\n }\n const handleMouseLeave = () => {\n setEntered(false)\n }\n\n const classNames = (props, entered) => {\n let classes = ['bc-select__option']\n\n if (props.isSelected) { classes.push('bc-select__option--is-selected') }\n if (entered) { classes.push('bc-select__option--borderless') }\n\n return classes.join(' ');\n }\n\n return (\n \n
\n {props.label}\n {entered && createPortal(, document.body)}\n
\n
\n )\n}\n\nexport default Option\n","// React\nimport React from 'react'\n\n// Components\n\n// Libraries\n\n// Shared\n\nconst selectPlaceholder = ({ icon, value }) => (\n <>\n {value}\n >\n)\n\nexport default selectPlaceholder\n","// React\nimport React from 'react'\n\n// Libraries\n\n// Components\nimport Mobile from './Mobile'\nimport Desktop from './Desktop'\n\n// Shared\nimport isMobile from '@client/utils/isMobile'\n\nconst Picker = ({ onChange, pickupOpeningTimes, returnOpeningTimes, ...props }) => {\n const handleDepotSelect = (type, depot) => {\n switch (type) {\n case 'pickup':\n onChange('pickupDepot', depot)\n break\n case 'return':\n onChange('returnDepot', depot)\n break\n }\n }\n\n const handleTimeslotSelect = (type, timeslot) => {\n switch (type) {\n case 'pickup':\n onChange('pickupTimeslot', timeslot)\n break\n case 'return':\n onChange('returnTimeslot', timeslot)\n break\n }\n }\n\n const handleDateSelect = (type, date) => {\n switch (type) {\n case 'pickup':\n onChange('pickupDate', date)\n break\n case 'return':\n onChange('returnDate', date)\n break\n }\n }\n\n const WrapperComponent = isMobile() ? Mobile : Desktop\n\n return (\n \n )\n}\n\nexport default Picker\n","// React\nimport React from 'react'\n\n// Components\nimport Calendar from '../Calendar'\n\n// Libraries\n\n// Shared\n\nconst Date = ({ onChange, isDayAvailable, range, type, errors }) => {\n const selectedDay = (type, range) => {\n const date = {\n \"pickup\": range.from,\n \"return\": range.to || range.from\n }\n\n return date[type]\n }\n\n return (\n \n )\n}\n\nexport default Date\n","// React\nimport React from 'react'\n\n// Components\n\n// Libraries\nimport { t } from '@client/i18n/localize'\nimport groupBy from 'lodash/groupBy'\nimport map from 'lodash/map'\n\n// Shared\nconst groupDepots = (depots) => {\n const grouped = groupBy(depots, (depot) => depot.domain.country_name)\n return map(grouped, (v, k) => ({ name: k, depots: v }))\n}\n\nconst Depots = ({ depots, type, onChange }) => {\n const groupedDepots = groupDepots(depots)\n\n const storeType = (type) => {\n if (type != 'normal') {\n return (({t(`depot.store_types.${type}`)}))\n }\n }\n\n const renderDepotGroup = (group) => (\n \n
{group.name}
\n
\n {group.depots.map(depot => renderDepot(depot))}\n
\n
\n )\n\n const renderDepot = (depot) => (\n onChange(depot)} className=\"bc-mobile-picker-depot\" key={depot.id}>\n {depot.name} {storeType(depot.store_type)}
\n {depot.address}
\n \n )\n\n return (\n \n {groupedDepots.map(group => renderDepotGroup(group))}\n
\n )\n}\n\nexport default Depots\n","// React\nimport React from 'react'\n\n// Components\n\n// Libraries\n\n// Shared\n\nconst Timeslots = ({ onChange, isTimeslotAvailable, timeslots, type, values }) => {\n const handleChange = (timeslot) => {\n if (isTimeslotAvailable(timeslot, type, type === 'pickup')) {\n onChange(timeslot)\n }\n }\n\n return (\n \n {timeslots.map((timeslot) => (\n - handleChange(timeslot)}\n >\n
{timeslot.label}
\n \n ))}\n
\n )\n}\n\nexport default Timeslots\n","// React\nimport React from 'react'\n\n// Components\n\n// Libraries\n\n// Shared\n\nconst Details = ({ onBackPress, title, children }) => {\n return (\n \n
\n \n
{title}
\n \n {children}\n
\n )\n}\n\nexport default Details\n","import React from 'react'\n\nconst ResultOption = ({ title, percentage }) => {\n return (\n \n
\n {title}\n {percentage}%\n
\n
\n
\n )\n}\n\nconst Results = ({ options }) => {\n const votes = options.reduce((a, option) => (option.poll_votes_count || 0) + a, 0)\n\n const percentage = (option) => {\n const count = (option.poll_votes_count || 0)\n return ((count / votes) * 100).toFixed()\n }\n\n return (\n \n {options.map((option) => (\n \n ))}\n
\n )\n}\n\nexport default Results\n","import React, { useState } from 'react'\n\n// Utils\nimport { t } from '@client/i18n/localize'\n\nconst VoteOption = ({ id, title, selected, onClick }) => {\n return (\n \n )\n}\n\nconst Vote = ({ options, onSubmit }) => {\n const [selectedOption, setSelectedOption] = useState(null)\n\n const handleClickOption = (id) => {\n setSelectedOption(id)\n }\n\n const handleSubmit = (e) => {\n e.preventDefault()\n onSubmit(selectedOption)\n }\n\n return (\n <>\n {options.map((option) => (\n \n ))}\n\n \n \n
\n >\n )\n}\n\nexport default Vote\n","import React, { useState, useEffect } from 'react'\n\n// API\nimport { fetchPoll, votePoll } from '@client/api/poll'\n\n// Utils\nimport { t } from '@client/i18n/localize'\n\n// Components\nimport Vote from './Vote'\nimport Results from './Results'\n\nconst Poll = ({ title, description, options, onSubmit, step }) => {\n return (\n \n
\n
{title}
\n
{description}
\n\n {step === 'vote' &&
}\n {step === 'results' &&
}\n
\n )\n}\n\nconst Container = () => {\n const [step, setStep] = useState('vote')\n const [poll, setPoll] = useState(null)\n\n useEffect(() => {\n fetchPoll().then(({ data }) => {\n if (data.meta.voted) {\n setStep('results')\n }\n\n setPoll(data.poll)\n })\n }, [])\n\n const handleVote = (id) => {\n votePoll({ option: id }).then(({ data }) => {\n setPoll(data.poll)\n setStep('results')\n })\n }\n\n if (poll) {\n return (\n \n )\n } else {\n return null\n }\n}\n\nexport default Container\n","import axios from '@client/lib/axios'\n\nexport const fetchPoll = (params) => {\n return axios({\n method: 'get',\n url: '/api/1/poll'\n })\n}\n\nexport const votePoll = ({ option }) => {\n return axios({\n method: 'post',\n url: '/api/1/poll/vote',\n params: {\n option\n }\n })\n}\n","import React, { useRef } from 'react'\n\nconst Popover = ({ children, title, subtitle, onClose, fixedWidth }) => {\n const backgroundNode = useRef(null)\n const modifiers = []\n\n if (fixedWidth) modifiers.push('fixed-width')\n\n const handleClick = (e) => {\n if (e.target === backgroundNode.current) {\n onClose()\n }\n }\n\n return (\n `bc-popover--${m}`)}`} onClick={handleClick} ref={backgroundNode}>\n
\n {onClose &&
}\n
\n {title &&
{title}
}\n {(title || subtitle) &&
}\n {children}\n
\n
\n
\n )\n}\n\nexport default Popover\n","import React, { useState } from 'react'\n\n// Actions\nimport { bookProductToCart } from '@client/redux/actions/cart'\n\n// Libraries\nimport { useDispatch } from 'react-redux'\n\n// Utils\nimport { track } from '@client/utils/tracking'\nimport { t } from '@client/i18n/localize'\n\n// Components\nimport Essentials from '@client/react/components/Essentials'\nimport { Button, Link, Icon } from '@client/react/components/AddProductToCart'\nimport AddOptionalAccessories from '@client/react/components/AddOptionalAccessories'\nimport { notification } from '@client/react/components/Notification'\n\nconst AddToCart = ({ id, name, type, label, source, optionalPickupAccessories }) => {\n const dispatch = useDispatch()\n const [loading, setLoading] = useState(false)\n const [success, setSuccess] = useState(null)\n const [initializeEssentials, setInitializeEssentials] = useState(false)\n\n const [visible, setVisible] = useState(false)\n const show = () => setVisible(true)\n const hide = () => setVisible(false)\n\n const components = {\n button: Button,\n icon: Icon,\n link: Link\n }\n const Component = components[type] || Icon\n\n const hasOptionalPickupAccessories = () => {\n return optionalPickupAccessories && optionalPickupAccessories.length > 0\n }\n\n const handleVisible = () => {\n visible ? hide() : show()\n }\n\n const handleClick = ({ accessoryIds=[] }) => {\n setLoading(true)\n\n dispatch(bookProductToCart({ id, accessoryIds }))\n .then(({ payload }) => {\n if (!payload.cart) { throw('No cart') }\n\n const ids = [].concat(id)\n\n if (ids.length === 1 && source !== 'essentials') {\n setInitializeEssentials(true)\n }\n\n setSuccess(true)\n setLoading(false)\n hide()\n notification({ message: t('shopping_cart.product_added', { product: name }) })\n\n ids.forEach(id => {\n track('cart.book_product', {\n productId: id,\n cart: payload.cart,\n source\n })\n })\n\n setTimeout(() => { setSuccess(null) }, 2000)\n })\n .catch(() => {\n notification({ message: t('pages.error_500.page_title'), type: 'danger' })\n setSuccess(false)\n setLoading(false)\n hide()\n })\n }\n\n return (\n <>\n {hasOptionalPickupAccessories() &&\n \n \n \n }\n {!hasOptionalPickupAccessories() && }\n\n {}\n >\n )\n}\n\nexport default AddToCart\n","import React, { useState, useEffect } from 'react'\n\n// Libraries\nimport { useSelector } from 'react-redux'\n\n// Utils\nimport { t } from '@client/i18n/localize'\n\n// Components\nimport AddProductToWishlist from '@client/react/components/AddProductToWishlist'\nimport Tooltip from '@client/react/components/Tooltip'\n\nconst AddToWishlist = (props) => {\n const wishlists = useSelector((state) => state.wishlists)\n const addedFromState = !!wishlists.find((wishlist) => wishlist.product_ids.indexOf(props.id) >= 0)\n\n const [show, setShow] = useState(null)\n const [added, setAdded] = useState(false)\n const [isMounted, setIsMounted] = useState(false)\n const classNames = ['fa-heart add-to-wishlist animated animated-heartbeat link-light']\n\n // Selected state\n classNames.push(added ? 'fas text-danger' : 'fas')\n\n if (props.className) classNames.push(props.className)\n\n useEffect(() => {\n setIsMounted(true)\n }, [])\n\n // Use two-step render to render an updated version\n useEffect(() => {\n if (isMounted) {\n setAdded(addedFromState)\n }\n }, [isMounted, addedFromState])\n\n const handleClick = () => {\n setShow(true)\n }\n\n const handleClose = () => {\n setShow(false)\n }\n\n return (\n <>\n \n {props.type !== 'label' && (\n <>\n \n \n >\n )}\n {props.type === 'label' && (\n \n \n \n )}\n >\n )\n}\n\nexport default AddToWishlist\n","import React from 'react'\n\n// Utils\nimport { t, translatePeriodLabel } from '@client/i18n/localize'\n\n\n// Components\nimport AddToCart from './AddToCart'\nimport AddToWishlist from './AddToWishlist'\nimport Price from './Price'\nimport Tooltip from '@client/react/components/Tooltip'\n\nconst PricesListComponent = (prices) => {\n return (\n \n {t('tooltips.multiple_day_prices')}\n\n
\n
\n )\n}\n\nconst PricesListItemComponent = ({ price }) => {\n return (\n <>\n {price && (\n \n {translatePeriodLabel(price.label)}\n\n \n {price.formattedPrice}\n \n \n )}\n >\n )\n}\n\nconst PriceComponent = ({ id, prices }) => {\n const [currentPrice, ...futurePrices] = prices;\n const classNames = ['fas fa-tags item-card__future-prices future-prices__icon animated animated-zoom link-light']\n\n return (\n <>\n \n
{translatePeriodLabel(currentPrice.label || currentPrice.title)}\n
{currentPrice.formattedPrice}\n\n
\n \n \n
\n\n \n \n \n >\n )\n}\n\nconst ListItem = ({ id, name, path, imageThumbLarge, imageThumb, prices, wishlist, source, optional_pickup_accessories, onClick }) => {\n return (\n \n
\n
\n \n
\n
\n
{name}\n {wishlist !== false &&
}\n
\n
\n
\n
\n )\n}\n\nexport default ListItem\n","import React, { useState, useEffect } from 'react'\n\n// Actions\nimport { fetchProductPrices } from '@client/redux/actions/products'\n\n// Libraries\nimport { useSelector, useDispatch } from 'react-redux'\n\nconst Price = ({ id, component, initialPrice, fetchData }) => {\n const dispatch = useDispatch()\n const pricesFromState = useSelector(state => (state.prices || {})[id])\n const cart = useSelector(state => state.cart)\n\n const [isMounted, setIsMounted] = useState(false)\n const [prices, setPrices] = useState(initialPrice)\n\n const Component = component\n\n useEffect(() => {\n setIsMounted(true)\n }, [])\n\n // Use two-step render to render an updated version with prices\n useEffect(() => {\n if (isMounted && pricesFromState) {\n setPrices(pricesFromState)\n }\n }, [isMounted, pricesFromState])\n\n // Fetch price data if `fetchData` is true\n useEffect(() => {\n if (fetchData) {\n dispatch(fetchProductPrices({ ids: id }))\n }\n }, [cart.pickup_time, cart.return_time])\n\n return (\n \n )\n}\n\nexport default Price\n","import React from 'react'\n\n// Utils\nimport { t, translatePeriodLabel } from '@client/i18n/localize'\n\n// Components\nimport AddToCart from './AddToCart'\nimport AddToWishlist from './AddToWishlist'\nimport Price from './Price'\n\nconst getLabel = ({ isNew, expected }) => {\n if (expected) {\n return {\n label: t('expected'),\n color: 'bg-dark text-white'\n }\n } else if (isNew) {\n return {\n label: t('new'),\n color: 'bg-green text-white'\n }\n } else {\n return false\n }\n}\n\nconst Label = ({ label, color }) => {\n return {label}\n}\n\nconst renderItem = ({ title, label, formattedPrice }) => {\n return (\n \n { translatePeriodLabel(title || label) }\n {formattedPrice}\n
\n )\n}\n\nconst PriceComponent = ({ prices }) => {\n return (\n prices.map((price, index) => {renderItem(price)})\n )\n}\n\nconst ExpectedProduct = ({ id, name, path, imageThumbLarge, imageThumb, expected, onClick }) => {\n const label = getLabel({ expected })\n\n return (\n \n )\n}\n\nconst BookableProduct = ({ id, name, path, imageThumbLarge, imageThumb, prices, isNew, expected, wishlist, source, onClick, optionalPickupAccessories }) => {\n const label = getLabel({ isNew, expected })\n\n return (\n \n )\n}\n\nconst Tile = (product) => {\n if (product.expected) {\n return \n } else {\n return \n }\n}\n\nexport default Tile\n","import React from 'react'\n\n// Utils\nimport { productPath } from '@client/utils'\n\n// Components\nimport Tile from './Tile'\nimport ListItem from './ListItem'\n\nconst Product = (props) => {\n const { type, slug, ...otherProps } = props\n const path = productPath(slug)\n\n const components = {\n tile: Tile,\n list: ListItem\n }\n const Component = components[type] || Tile\n\n return \n}\n\nexport default Product\n","import React from 'react'\n\n// Utils\nimport { translatePeriodLabel } from '@client/i18n/localize'\n\n// Components\nimport Price from '@client/react/components/Product/Price'\n\nconst PriceComponent = ({ prices }) => {\n const renderItem = ({label, formattedPrice, index}) => {\n const priceClass = index === 0 ? 'h2' : 'text-large text-medium'\n return (\n \n {translatePeriodLabel(label)}\n {formattedPrice}\n
\n )\n }\n\n return (\n \n {prices.map((price, index) => (\n \n {renderItem({...price, index})}\n \n ))}\n
\n )\n}\n\nconst ProductDetailPrice = ({ id, prices }) => {\n return \n}\n\nexport default ProductDetailPrice\n","import React, { useState } from 'react'\n\n// Libraries\nimport { values, flatten } from 'lodash'\n\n// Utils\nimport { t } from '@client/i18n/localize'\nimport { productsPath } from '@client/utils'\n\n// Components\nimport ProductFilter from './'\nimport Modal from '@client/react/components/Modal'\n\nconst Button = ({ filterGroups, current, counters }) => {\n const [show, setShow] = useState(false)\n const [filterPath, setFilterPath] = useState(productsPath())\n\n const handleToggleShow = (e) => {\n e && e.preventDefault()\n setShow(!show)\n }\n\n const handleChangeFilters = (filters, path) => {\n setFilterPath(path)\n }\n\n const handleApplyFilters = (e) => {\n e.preventDefault()\n Turbolinks.visit(filterPath)\n }\n\n const selectedCount = flatten(values(current)).length\n\n return (\n <>\n \n {t('product_results.filters')} ({selectedCount}) \n \n {show &&\n \n \n \n \n \n {t('product_results.apply_filters')}\n \n \n }\n >\n )\n}\n\nexport default Button\n","import React from 'react'\n\nconst Filter = ({ name, path, count, selected, handleToggleFilter, navigate }) => {\n const onChange = (e) => {\n handleToggleFilter()\n }\n\n const handleClick = (e) => {\n if (!navigate) {\n e.preventDefault()\n handleToggleFilter()\n }\n }\n\n return (\n \n )\n}\n\nexport default Filter\n","import React, { useState, useEffect } from 'react'\n\n// Libraries\nimport { take, sortBy, reverse } from 'lodash'\n\n// Utils\nimport { isFilterPresent, filterPath } from './utils'\n\n// Components\nimport Filter from './Filter'\n\nconst FilterGroup = ({ name, slug, filters, selectedFilters, handleToggleFilter, counters, navigate }) => {\n if (!name) return null\n\n if (counters) {\n filters = sortBy(filters, (filter) => {\n const selected = isFilterPresent(filter.slug, slug, selectedFilters)\n let count = counters[`${slug}--${filter.slug}`] || 0\n\n return count\n })\n filters = reverse(filters)\n }\n\n const renderedFilters = filters.map((filter) => {\n const selected = isFilterPresent(filter.slug, slug, selectedFilters)\n const path = filterPath(filter.slug, slug)\n const count = (counters || {})[`${slug}--${filter.slug}`] || 0\n\n if (counters && count === 0) { return }\n\n return (\n \n )\n }).filter((filter) => filter)\n\n return (\n <>\n {renderedFilters.length > 0 &&\n \n
{name}
\n
{renderedFilters}
\n
}\n >\n )\n}\n\nexport default FilterGroup\n","import React from 'react'\n\n// Utils\nimport { productsPath } from '@client/utils'\nimport { t } from '@client/i18n/localize'\n\n// Components\nimport FilterGroup from './FilterGroup'\n\nconst List = ({ selectedCount, filterGroups, selectedFilters, handleToggleFilter, counters, navigate }) => {\n return (\n \n
\n {filterGroups.map((filterGroup) => (\n
\n ))}\n
\n )\n}\n\nexport default List\n","import React from 'react'\n\n// Utils\nimport { productsPath } from '@client/utils'\nimport { t } from '@client/i18n/localize'\n\nconst NoResults = () => {\n return (\n \n )\n}\n\nexport default NoResults\n","import React from 'react'\n\n// Libraries\nimport { isEmpty, map, flatten } from 'lodash'\n\n// Utils\nimport { isFilterPresent } from './utils'\n\nconst Tags = ({ filterGroups, selectedFilters, handleToggleFilter }) => {\n const renderedFilters = map(filterGroups, (filterGroup) => {\n return filterGroup.filters.map((filter) => {\n const selected = isFilterPresent(filter.slug, filterGroup.slug, selectedFilters)\n\n if (selected) {\n return (\n handleToggleFilter(filter.slug, filterGroup.slug)}>\n {filter.name}\n \n
\n )\n }\n }).filter((item) => item)\n }).filter((item) => item)\n\n return (\n <>\n {!isEmpty(flatten(renderedFilters)) && \n {flatten(renderedFilters)}\n
}\n >\n )\n}\n\nexport default Tags\n","import React, { useState, useEffect } from 'react'\n\n// Libraries\nimport { values, flatten } from 'lodash'\n\n// Utils\nimport { toggleFilter, filtersPath } from './utils'\n\n// Components\nimport FilterList from './List'\nimport FilterTags from './Tags'\n\nconst ProductFilter = ({ filterGroups, current, counters, onChange, navigate = true, view = 'list' }) => {\n const [isMounted, setIsMounted] = useState(false)\n const [selectedFilters, setSelectedFilters] = useState({})\n const selectedCount = flatten(values(selectedFilters)).length\n const [currentCounters, setCurrentCounters] = useState(null)\n\n useEffect(() => {\n setIsMounted(true)\n }, [])\n\n // Use two-step render to render an updated version\n useEffect(() => {\n if (isMounted) {\n setCurrentCounters(counters)\n setSelectedFilters({ ...current })\n }\n }, [isMounted])\n\n const handleToggleFilter = (filterSlug, filterGroupSlug) => {\n const newSelectedFilters = toggleFilter(filterSlug, filterGroupSlug, selectedFilters)\n const path = `${filtersPath(newSelectedFilters)}${window.location.search}`\n\n setSelectedFilters(newSelectedFilters)\n\n if (navigate) {\n window.restoreScrollPosition = true\n Turbolinks.visit(path)\n } else {\n // On change is implemented, this means that the behavior of selecting filters is handled elsewhere\n onChange(newSelectedFilters, path)\n }\n }\n\n const View = {\n list: FilterList,\n tags: FilterTags\n }[view]\n\n return (\n \n )\n}\n\nexport default ProductFilter\n","import { map, toPairs, sortBy, fromPairs } from 'lodash'\nimport { productsPath } from '@client/utils'\n\nconst filterPath = (filterSlug, filterGroupSlug, filters) => {\n return filtersPath(toggleFilter(filterSlug, filterGroupSlug, filters, true))\n}\n\nconst filtersPath = (filters) => {\n const sortedFilters = fromPairs(sortBy(toPairs(filters)))\n const path = map(sortedFilters, (filters, groupSlug) => {\n if (filters.length === 0) return null\n return `${groupSlug}/${sortBy(filters).join('--')}`\n }).join('/')\n\n return productsPath(path)\n}\n\nconst toggleFilter = (filterSlug, filterGroupSlug, filters={}, addOnly=false) => {\n const isAdded = isFilterPresent(filterSlug, filterGroupSlug, filters)\n\n if (isAdded && addOnly) {\n return filters\n }\n\n const newFilters = { ...filters }\n const currentFilters = newFilters[filterGroupSlug] = (newFilters[filterGroupSlug] || []).concat([])\n\n if (isAdded) {\n currentFilters.splice(currentFilters.indexOf(filterSlug), 1)\n } else {\n currentFilters.push(filterSlug)\n }\n\n return newFilters\n}\n\n/**\n * Checks if given filter slug is present.\n *\n * @param {String} filterSlug\n * @param {String} filterGroupSlug\n * @param {Object} filters\n */\nconst isFilterPresent = (filterSlug, filterGroupSlug, filters) => {\n return filters[filterGroupSlug] && filters[filterGroupSlug].indexOf(filterSlug) >= 0\n}\n\nexport { filterPath, filtersPath, toggleFilter, isFilterPresent, productsPath }\n","import React, { useState, useRef } from 'react'\nimport { tween, easing } from 'popmotion'\n\n// Components\nimport ProductList from './ProductList'\n\nconst ProductGroup = (props) => {\n const node = useRef(null)\n const [step, setStep] = useState(0)\n const [itemsVisible, setItemsVisible] = useState(0)\n\n const scrollTo = (from, to) => {\n const { easeIn, mirrored } = easing\n const easeInOut = mirrored(easeIn)\n\n tween({ from, to, ease: easeInOut }).start((x) => {\n node.current.scrollLeft = x\n })\n }\n\n const handleStep = (add) => {\n const containerWidth = node.current.clientWidth\n const itemWidth = node.current.querySelector('div').getBoundingClientRect().width\n const itemsVisible = Math.ceil(containerWidth / itemWidth)\n const maxStep = props.products.length - itemsVisible\n const newStep = Math.min(Math.max(step + add, 0), maxStep)\n\n scrollTo(step * itemWidth, newStep * itemWidth)\n setStep(newStep)\n setItemsVisible(itemsVisible)\n }\n\n const handeClickleft = () => {\n handleStep(-2)\n }\n\n const handeClickRight = () => {\n handleStep(2)\n }\n\n return (\n \n {step > 0 && (\n
\n \n
\n )}\n\n
\n\n {step + itemsVisible < props.products.length && (\n
\n \n
\n )}\n
\n )\n}\n\nexport default ProductGroup\n","import React, { useState, useEffect } from 'react'\n\n// Actions\nimport { fetchProductPrices } from '@client/redux/actions/products'\n\n// Libraries\nimport { useDispatch, useSelector } from 'react-redux'\nimport map from 'lodash/map'\nimport difference from 'lodash/difference'\nimport { track } from '@client/utils/tracking'\n\n// Components\nimport Product from './Product'\n\nconst ProductList = ({ products, productClassName, component, type, wishlist, source, trackImpression = true }) => {\n const dispatch = useDispatch()\n const cart = useSelector(state => state.cart)\n const [ids, setIds] = useState([])\n\n const handleClickProduct = (product) => {\n // Remove possible scroll-lock class from Modal\n document.querySelector('html').classList.remove('scroll-lock')\n track('product.click', { product, source })\n }\n\n // Update prices for new products\n useEffect(() => {\n const allIds = map(products, (product) => product.id)\n const newIds = ids.length === 0 ? allIds : difference(allIds, ids)\n\n if (newIds.length > 0) {\n setIds(allIds)\n dispatch(fetchProductPrices({ ids: newIds }))\n }\n }, [products])\n\n // Track impressions\n useEffect(() => {\n if (trackImpression) track('product.impression', { products, source })\n }, [products])\n\n // Update prices on cart update\n useEffect(() => {\n if (ids.length > 0) {\n dispatch(fetchProductPrices({ ids }))\n }\n }, [cart.pickup_time, cart.return_time])\n\n const Component = component || Product\n const renderProduct = (product) => (\n \n )\n\n if (component) {\n return products.map(renderProduct)\n } else {\n return products.map(product => (\n \n {renderProduct(product)}\n
\n ))\n }\n}\n\nexport default ProductList\n","import React, { useState } from 'react'\n\n// API\nimport { fetchProducts } from '@client/api/products'\n\n// Utils\nimport { t } from '@client/i18n/localize'\nimport isEmpty from 'lodash/isEmpty'\n\n// Components\nimport ProductFilter from './ProductFilter'\nimport ProductList from './ProductList'\nimport ProductFilterButton from './ProductFilter/Button'\nimport NoResults from './ProductFilter/NoResults'\n\nconst getDynamicProps = (component) => {\n const props = global.dynamicComponentProps || {}\n\n return props[component] || {}\n}\n\nconst ProductResults = (props) => {\n const { products, filterGroups, currentFilters, filterCounters, currentParams, productClassName } = { ...props, ...getDynamicProps('ProductResults')}\n const [loading, setLoading] = useState(false)\n const [currentProducts, setCurrentProducts] = useState(products)\n const [params, setParams] = useState({ ...currentParams, per: 24, page: 1 })\n const [hasNextPage, setHasNextPage] = useState(currentProducts.length < params.per ? false : true)\n\n const hasResults = currentProducts.length > 0 || !isEmpty(currentFilters)\n\n const handleNextPage = (e) => {\n if (e) e.preventDefault()\n\n const newParams = { ...params, page: params.page + 1 }\n\n setParams(newParams)\n setLoading(true)\n\n fetchProducts(newParams).then((response) => {\n const newProducts = response.data.products\n\n if (newProducts.length < params.per) {\n setHasNextPage(false)\n }\n\n setCurrentProducts([...currentProducts, ...newProducts])\n setLoading(false)\n })\n }\n\n return (\n \n {hasResults && <>\n
\n
\n
\n
\n
\n
\n\n {currentProducts.length === 0 &&
}\n\n {hasNextPage &&\n
\n }\n
\n
\n >}\n {!hasResults &&
}\n
\n )\n}\n\nexport default ProductResults\n","import React, { useState } from 'react'\n\n// Libraries\nimport find from 'lodash/find'\n\n// Utils\nimport { t } from '@client/i18n/localize'\nimport queryParams from '@client/utils/queryParams'\n\n// Components\nimport Select from './Select'\n\nconst ProductSort = ({ sort, direction, currentParams }) => {\n const sortOptions = [\n {\n value: '',\n label: t('product_results.sort.popularity')\n },\n {\n value: 'price_in_cents asc',\n label: t('product_results.sort.price_in_cents_asc')\n },\n {\n value: 'price_in_cents desc',\n label: t('product_results.sort.price_in_cents_desc')\n }\n ]\n const selectedOption = find(sortOptions, (option) =>\n option.value === `${sort} ${direction}`\n )\n\n const [value, setValue] = useState(selectedOption || sortOptions[0])\n\n const onChange = (option) => {\n setValue(option)\n\n const [newSort, newDirection] = option.value.split(' ')\n const path = window.location.pathname\n const params = { ...currentParams }\n\n if (newSort !== '') {\n params.sort = newSort\n params.direction = newDirection\n }\n\n Turbolinks.visit(`${path}?${queryParams(params)}`)\n }\n\n return (\n \n {t('product_results.sort.sort_by')}\n \n
\n )\n}\n\nexport default ProductSort\n","export default (data) => {\n const ret = []\n for (let d in data)\n if (data[d]) {\n ret.push(encodeURIComponent(d) + '=' + encodeURIComponent(data[d]))\n }\n return ret.join('&')\n}\n","import React, { useState, useEffect } from 'react'\n\n// Libraries\nimport { useDispatch } from 'react-redux'\nimport map from 'lodash/map'\n\n// Actions\nimport { fetchProductPrices } from '@client/redux/actions/products'\n\n// Utils\nimport { t } from '@client/i18n/localize'\n\n// Components\nimport AddToCart from '@client/react/components/Product/AddToCart'\n\nconst ProductSum = ({ ids, name, source }) => {\n const dispatch = useDispatch()\n const [loading, setLoading] = useState(true)\n const [data, setData] = useState({})\n\n useEffect(() => {\n dispatch(fetchProductPrices({ ids })).then((action) => {\n setLoading(false)\n setData(action.payload)\n })\n }, [ids.sort().join('-')])\n\n if (loading) return null\n\n return (\n \n
\n
\n {t('prices.subtotal')}\n {data.formattedSubtotal}\n
\n {map(data.vat, (amount, percentage) => (\n
\n {t('prices.tax_amount', { percentage: parseInt(percentage) })}\n {data.formattedVatAmount}\n
\n ))}\n
\n
\n {t('prices.total')}\n {data.formattedTotal}\n
\n
\n
\n
\n )\n}\n\nexport default ProductSum\n","import React, { useState, useEffect, useRef } from 'react'\n\nimport Modal from '@client/react/components/Modal'\nimport SearchInput from './SearchInput'\n\nconst positionModal = () => {\n const modalElement = document.querySelector('.bc-modal');\n if (!modalElement) return;\n\n const inputElement = document.querySelector('#tweakwise-input-mobile');\n const searchInputPosition = inputElement.getBoundingClientRect();\n\n const topPosition = searchInputPosition.bottom + 10;\n modalElement.style.top = `${topPosition}px`;\n modalElement.style.height = `calc(100vh - ${topPosition}px)`;\n}\n\nconst SearchBar = () => {\n const [show, setShow] = useState(null)\n const searchRef = useRef()\n\n useEffect(() => {\n if(!show) {\n return\n }\n\n // Trigger tweakwise again to attach event handler on input\n window['twn-starter-instance'] && window['twn-starter-instance'].refresh()\n }, [show])\n\n useEffect(() => {\n if (!show) {\n return\n }\n\n positionModal()\n }, [show]);\n\n const handleClose = (event) => {\n event && event.preventDefault()\n setShow(false)\n searchRef.current.reset()\n }\n\n const handleChange = (value) => {\n setShow(value.length > 0)\n }\n\n return (\n <>\n \n\n \n \n \n \n \n >\n )\n}\n\nexport default SearchBar\n","import React, { useState } from 'react'\n\n// Utils\nimport useClickOutside from '@client/react/hooks/useClickOutside'\n\n// Components\nimport SearchInput from './SearchInput'\n\nconst SearchTablet = () => {\n const [expanded, setExpanded] = useState(false)\n\n const [showIcon, setShowIcon] = useState(true)\n const [showSearch, setShowSearch] = useState(false)\n\n const handleToggleExpand = (expanded) => {\n setExpanded(!expanded)\n handleShowIcon(expanded)\n handleShowSearch(expanded)\n }\n\n const handleShowIcon = (expanded) => {\n if (expanded) {\n setShowIcon(true)\n } else {\n setShowIcon(false)\n }\n }\n\n const handleShowSearch = (expanded) => {\n if (expanded) {\n const timer = setTimeout(() => {\n setShowSearch(false)\n }, 200)\n return () => clearTimeout(timer)\n } else {\n const timer = setTimeout(() => {\n setShowSearch(true)\n }, 50)\n return () => clearTimeout(timer)\n }\n }\n\n const node = useClickOutside(() => {\n if (expanded) {\n handleToggleExpand(expanded)\n }\n })\n\n return (\n \n )\n}\n\nexport default SearchTablet\n","import React, { useState, forwardRef, useImperativeHandle } from 'react'\n\nimport { t } from '@client/i18n/localize'\nimport { getQuery, resetQuery } from '@client/utils/tweakwise'\n\nconst SearchInput = forwardRef(({ initialValue, modifier, autoFocus, onIconClick, id, onChange }, ref) => {\n const queryValue = initialValue || getQuery(window.location.hash)\n const [query, setQuery] = useState(queryValue || '')\n\n useImperativeHandle(ref, () => ({\n reset() {\n setQuery('')\n resetQuery()\n }\n }))\n\n const handleChangeQuery = (e) => {\n setQuery(e.target.value)\n onChange && onChange(e.target.value)\n }\n\n const handleSubmit = (e) => {\n e.preventDefault()\n }\n\n return (\n \n \n
\n )\n})\n\nexport default SearchInput\n","import React from 'react'\nimport ReactSelect, { components } from 'react-select'\n\nconst customDropdown = ({ innerProps, isDisabled }) => {\n return !isDisabled ? (\n \n ) : null\n}\n\nconst CustomSelectValue = ({ children, selectProps }) => {\n return(\n <>\n \n {children}\n >\n )\n}\n\n// handle options group header click event\n// hide and show the options under clicked group\nconst handleHeaderClick = id => {\n const node = document.querySelector(`#${id}`).parentElement.nextElementSibling\n const classes = node.classList\n\n if (classes.contains(\"collapsed\")) {\n node.classList.remove(\"collapsed\")\n } else {\n node.classList.add(\"collapsed\")\n }\n}\n\n// Create custom GroupHeading component, which will wrap\n// react-select GroupHeading component inside a div and\n// register onClick event on that div\nconst CustomGroupHeading = props => {\n return (\n handleHeaderClick(props.id)}\n >\n \n
\n );\n};\n\nconst Select = ({\n id,\n options,\n styles,\n onChange,\n value,\n defaultValue,\n getOptionLabel,\n getOptionValue,\n usePortal,\n placeholder,\n isOptionDisabled,\n components = {},\n icon,\n disabled\n}) => {\n return (\n \n )\n}\n\nexport default Select\n","import React from 'react'\nimport { Tooltip as ReactTooltip } from \"react-tooltip\"\nimport 'react-tooltip/dist/react-tooltip.css';\n\nconst Tooltip = ({ id, children}) => {\n return (\n \n {children}\n \n )\n}\n\nexport default Tooltip\n","import React, { useEffect } from 'react'\n\n// Libraries\nimport { useDispatch, useSelector } from 'react-redux'\n\n// Actions\nimport { fetchWishlist } from '@client/redux/actions/wishlists'\n\n// Components\nimport ProductList from '@client/react/components/ProductList'\nimport ProductSum from '@client/react/components/ProductSum'\n\nconst Wishlist = ({ id }) => {\n const dispatch = useDispatch()\n const wishlist = useSelector(state => state.wishlists.find((wishlist) => wishlist.id === id))\n\n useEffect(() => {\n dispatch(fetchWishlist({ id }))\n }, [id])\n\n if (!wishlist) return null\n\n const products = wishlist.products\n\n return (\n <>\n \n \n
p.id)} name={wishlist.name} source=\"wishlist\" />\n \n >\n )\n}\n\nexport default Wishlist\n","import { useEffect, useRef } from 'react'\n\n// Utils\nimport isMobile from '@client/utils/isMobile'\n\nexport default (callback) => {\n const node = useRef()\n\n const handleClickOutside = (e) => {\n if (!node.current || !node.current.contains(e.target)) {\n callback()\n }\n }\n\n useEffect(() => {\n document.addEventListener(isMobile() ? 'touchstart' : 'mousedown', handleClickOutside)\n\n return () => {\n document.removeEventListener(isMobile() ? 'touchstart' : 'mousedown', handleClickOutside)\n }\n }, [node.current, callback])\n\n return node\n}\n","import axios from '@client/lib/axios'\n\nexport const fetch = ({ token }) => {\n return axios({\n method: 'get',\n url: '/api/1/cart',\n params: { token }\n })\n}\n\nexport const update = (params) => {\n const { token, ...cartParams } = params\n\n return axios({\n method: 'put',\n url: '/api/1/cart',\n data: {\n token,\n cart: cartParams\n }\n })\n}\n\nexport const book = ({ token, id, type, quantity = 1, accessoryIds }) => {\n return axios({\n method: 'post',\n url: '/api/1/cart/book',\n params: {\n token,\n type,\n quantity,\n product_id: id,\n accessory_ids: accessoryIds\n }\n })\n}\n\nexport const clear = () => {\n return axios({\n method: 'post',\n url: '/api/1/cart/clear'\n })\n}\n\nexport const applyCoupon = ({ coupon, customerId }) => {\n return axios({\n method: 'post',\n url: '/api/1/cart/apply_coupon',\n data: {\n coupon,\n customer_id: customerId\n }\n })\n}\n\nexport const removeCoupon = ({ coupon, customerId }) => {\n return axios({\n method: 'post',\n url: '/api/1/cart/remove_coupon'\n })\n}\n","import { update } from '@client/api/cart'\n\nconst start = (params) => {\n return {\n type: 'CART_UPDATE_START',\n payload: params\n }\n}\n\nconst success = ({ cart }) => {\n return {\n type: 'CART_UPDATE_SUCCESS',\n payload: { cart }\n }\n}\n\nconst error = ({ response }) => {\n return {\n type: 'CART_UPDATE_ERROR',\n payload: { response }\n }\n}\n\nconst updateCart = (params) => {\n return (dispatch, getState) => {\n const { cart } = getState()\n\n dispatch(start({ ...params, token: cart.token }))\n\n return update({ ...params, token: cart.token })\n .then(\n (response) => { dispatch(success(response.data)) },\n (errorData) => { dispatch(error({ ...params, token: cart.token, response: errorData.response })) }\n )\n }\n}\n\nexport default updateCart\n","import { book } from '@client/api/cart'\n\nconst cartBookStart = ({ id, type, quantity, accessoryIds }) => {\n return {\n type: 'CART_BOOK_START',\n payload: { id, type, quantity, accessoryIds }\n }\n}\n\nconst cartBookSuccess = ({ cart }) => {\n return {\n type: 'CART_BOOK_SUCCESS',\n payload: { cart }\n }\n}\n\nconst cartBookError = ({ response }) => {\n return {\n type: 'CART_BOOK_ERROR',\n payload: { response }\n }\n}\n\nconst bookProductToCart = ({ id, type, quantity = 1, accessoryIds }) => {\n return (dispatch, getState) => {\n const { cart } = getState()\n\n dispatch(cartBookStart({ id, type, quantity, accessoryIds }))\n\n return book({ token: cart.token, type, id, quantity, accessoryIds })\n .then(\n (response) => dispatch(cartBookSuccess(response.data)),\n (error) => dispatch(cartBookError({ response: error.response }))\n )\n }\n}\n\nexport default bookProductToCart\n","import { clear } from '@client/api/cart'\n\nconst start = ({ id, type }) => {\n return {\n type: 'CART_CLEAR_START',\n payload: { id, type }\n }\n}\n\nconst success = ({ cart }) => {\n return {\n type: 'CART_CLEAR_SUCCESS',\n payload: { cart }\n }\n}\n\nconst error = ({ response }) => {\n return {\n type: 'CART_CLEAR_ERROR',\n payload: { response }\n }\n}\n\nconst clearCart = () => {\n return (dispatch, getState) => {\n const { cart } = getState()\n\n dispatch(start({ token: cart.token }))\n\n return clear({ token: cart.token })\n .then(\n (response) => { dispatch(success(response.data)) },\n (errorData) => { dispatch(error({ token: cart.token, response: errorData.response })) }\n )\n }\n}\n\nexport default clearCart\n","import { applyCoupon } from '@client/api/cart'\n\nconst start = ({ id, type }) => {\n return {\n type: 'CART_APPLY_COUPON_START',\n payload: { id, type }\n }\n}\n\nconst success = ({ cart }) => {\n return {\n type: 'CART_APPLY_COUPON_SUCCESS',\n payload: { cart }\n }\n}\n\nconst error = ({ response }) => {\n return {\n type: 'CART_APPLY_COUPON_ERROR',\n payload: { response }\n }\n}\n\nconst applyCouponOnCart = ({ coupon, customerId }) => {\n return (dispatch, getState) => {\n const { cart } = getState()\n\n dispatch(start({ token: cart.token, coupon, customerId }))\n\n return applyCoupon({ token: cart.token, coupon, customerId })\n .then(\n (response) => dispatch(success(response.data)),\n (errorData) => dispatch(error({ token: cart.token, coupon, customerId, response: errorData.response }))\n )\n }\n}\n\nexport default applyCouponOnCart\n","import { removeCoupon } from '@client/api/cart'\n\nconst start = ({ id, type }) => {\n return {\n type: 'CART_REMOVE_COUPON_START',\n payload: { id, type }\n }\n}\n\nconst success = ({ cart }) => {\n return {\n type: 'CART_REMOVE_COUPON_SUCCESS',\n payload: { cart }\n }\n}\n\nconst error = ({ response }) => {\n return {\n type: 'CART_REMOVE_COUPON_ERROR',\n payload: { response }\n }\n}\n\nconst removeCouponFromCart = () => {\n return (dispatch, getState) => {\n const { cart } = getState()\n\n dispatch(start({ token: cart.token }))\n\n return removeCoupon({ token: cart.token })\n .then(\n (response) => dispatch(success(response.data)),\n (errorData) => dispatch(error({ token: cart.token, response: errorData.response }))\n )\n }\n}\n\nexport default removeCouponFromCart\n","const open = () => {\n return {\n type: 'OPEN_PICKER',\n payload: {}\n }\n}\n\nconst openPicker = () => {\n return (dispatch) => {\n return dispatch(open())\n }\n}\n\nexport default openPicker\n","import { fetchPrices } from '@client/api/products'\n\nconst start = ({ ids }) => {\n return {\n type: 'FETCH_PRODUCT_PRICES_START',\n payload: { ids }\n }\n}\n\nconst success = (payload) => {\n return {\n type: 'FETCH_PRODUCT_PRICES_SUCCESS',\n payload,\n }\n}\n\nconst error = ({ response }) => {\n return {\n type: 'FETCH_PRODUCT_PRICES_ERROR',\n payload: { response }\n }\n}\n\nconst fetchProductPrices = ({ ids }) => {\n return (dispatch) => {\n dispatch(start({ ids }))\n\n return fetchPrices({ ids })\n .then(\n (response) => dispatch(success(response.data)),\n (errorData) => dispatch(error({ ids, response: errorData.response }))\n )\n }\n}\n\nexport default fetchProductPrices\n","import { fetchAll } from '@client/api/wishlists'\n\nconst start = () => {\n return {\n type: 'FETCH_WISHLISTS_START'\n }\n}\n\nconst success = ({ wishlists }) => {\n return {\n type: 'FETCH_WISHLISTS_SUCCESS',\n payload: { wishlists }\n }\n}\n\nconst error = ({ response }) => {\n return {\n type: 'FETCH_WISHLISTS_ERROR',\n payload: { response }\n }\n}\n\nconst fetchWishlists = () => {\n return (dispatch) => {\n dispatch(start())\n\n return fetchAll()\n .then(\n (response) => { dispatch(success({ wishlists: response.data.wishlists })) },\n (errorData) => { dispatch(error({ response: errorData.response })) }\n )\n }\n}\n\nexport default fetchWishlists\n","import axios from '@client/lib/axios'\n\nexport const fetchAll = (params={}) => {\n return axios({\n method: 'get',\n url: '/api/1/wishlists',\n params: params\n })\n}\n\nexport const fetch = ({ id }) => {\n return axios({\n method: 'get',\n url: `/api/1/wishlists/${id}`\n })\n}\n\nexport const create = ({ name, productIds }) => {\n return axios({\n method: 'post',\n url: '/api/1/wishlists',\n data: { wishlist: { name, product_ids: productIds } }\n })\n}\n\nexport const add = ({ id, productId }) => {\n return axios({\n method: 'post',\n url: `/api/1/wishlists/${id}/add`,\n params: { product_id: productId }\n })\n}\n\nexport const remove = ({ id, productId }) => {\n return axios({\n method: 'post',\n url: `/api/1/wishlists/${id}/remove`,\n params: { product_id: productId }\n })\n}\n","import { fetch } from '@client/api/wishlists'\n\nconst start = () => {\n return {\n type: 'FETCH_WISHLIST_START'\n }\n}\n\nconst success = ({ wishlist }) => {\n return {\n type: 'FETCH_WISHLIST_SUCCESS',\n payload: { wishlist }\n }\n}\n\nconst error = ({ response }) => {\n return {\n type: 'FETCH_WISHLIST_ERROR',\n payload: { response }\n }\n}\n\nconst fetchWishlist = ({ id }) => {\n return (dispatch) => {\n dispatch(start())\n\n return fetch({ id })\n .then(\n (response) => { dispatch(success({ wishlist: response.data.wishlist })) },\n (errorData) => { dispatch(error({ response: errorData.response })) }\n )\n }\n}\n\nexport default fetchWishlist\n","import { create } from '@client/api/wishlists'\n\nconst start = ({ name, productIds }) => {\n return {\n type: 'CREATE_WISHLIST_START',\n payload: { name, productIds }\n }\n}\n\nconst success = ({ name, productIds, wishlist }) => {\n return {\n type: 'CREATE_WISHLIST_SUCCESS',\n payload: { name, productIds, wishlist }\n }\n}\n\nconst error = ({ name, productIds, response }) => {\n return {\n type: 'CREATE_WISHLIST_ERROR',\n payload: { name, productIds, response }\n }\n}\n\nconst createWishlist = ({ name, productIds }) => {\n return (dispatch) => {\n dispatch(start({ name, productIds }))\n\n return create({ name, productIds })\n .then(\n (response) => { dispatch(success({ name, productIds, wishlist: response.data.wishlist })) },\n (errorData) => { dispatch(error({ name, productIds, response: errorData.response })) }\n )\n }\n}\n\nexport default createWishlist\n","import { add } from '@client/api/wishlists'\n\nconst start = ({ id, productId }) => {\n return {\n type: 'ADD_TO_WISHLIST_START',\n payload: { id, productId }\n }\n}\n\nconst success = ({ id, productId, wishlist }) => {\n return {\n type: 'ADD_TO_WISHLIST_SUCCESS',\n payload: { id, productId, wishlist }\n }\n}\n\nconst error = ({ id, productId, response }) => {\n return {\n type: 'ADD_TO_WISHLIST_ERROR',\n payload: { id, productId, response }\n }\n}\n\nconst addToWishlist = ({ id, productId }) => {\n return (dispatch) => {\n dispatch(start({ id, productId }))\n\n return add({ id, productId })\n .then(\n (response) => { dispatch(success({ id, productId, wishlist: response.data.wishlist })) },\n (errorData) => { dispatch(error({ id, productId, response: errorData.response })) }\n )\n }\n}\n\nexport default addToWishlist\n","import { remove } from '@client/api/wishlists'\n\nconst start = ({ id, productId }) => {\n return {\n type: 'REMOVE_FROM_WISHLIST_START',\n payload: { id, productId }\n }\n}\n\nconst success = ({ id, productId, wishlist }) => {\n return {\n type: 'REMOVE_FROM_WISHLIST_SUCCESS',\n payload: { id, productId, wishlist }\n }\n}\n\nconst error = ({ id, productId, response }) => {\n return {\n type: 'REMOVE_FROM_WISHLIST_ERROR',\n payload: { id, productId, response }\n }\n}\n\nconst removeFromWishlist = ({ id, productId }) => {\n return (dispatch) => {\n dispatch(start({ id, productId }))\n\n return remove({ id, productId })\n .then(\n (response) => { dispatch(success({ id, productId, wishlist: response.data.wishlist })) },\n (errorData) => { dispatch(error({ id, productId, response: errorData.response })) }\n )\n }\n}\n\nexport default removeFromWishlist\n","import { i18n } from '../../../../app/javascript/packs/localized-i18n'\nimport { t } from '@client/i18n/localize'\n\nconst url = (path) => {\n if (i18n.useLocaleInPath) {\n return `/${i18n.locale}/${path}`\n } else {\n return `/${path}`\n }\n}\n\nconst productsPath = (path = null) => {\n const producten = t('routes.producten')\n\n if (path) {\n return url(`${producten}/${path}`)\n } else {\n return url(`${producten}`)\n }\n}\n\nconst productPath = (id) => {\n const product = t('routes.product')\n\n return url(`${product}/${id}`)\n}\n\nexport { url, productsPath, productPath }\n","export default () => {\n if (typeof window === 'undefined') { return false }\n\n const width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;\n const breakpoint = 768\n\n return width < breakpoint\n}\n","import moment from '@client/i18n/moment'\nimport { sortBy } from 'lodash'\n\nconst filterTimeslots = (time, timeslots, openingTimes, depotId) => {\n const weekday = moment(time).day()\n const openingTime = openingTimes.find((el) => el.weekday === weekday && el.depot_id === depotId)\n\n if (openingTime) {\n const filterdTimeslots = timeslots.filter((el) => el.opening_time_id === openingTime.id)\n\n return sortBy(filterdTimeslots, 'from_time_integer')\n }\n\n return []\n}\n\nconst getTimeslot = (time, timeslots, openingTimes, depotId, type) => {\n time = moment(time)\n\n const timeInteger = (time.hour() * 100) + time.minute()\n\n return filterTimeslots(time, timeslots, openingTimes, depotId).find((timeslot) => {\n if (type === 'pickup') {\n return timeInteger >= timeslot.from_time_integer && timeInteger < timeslot.till_time_integer\n } else {\n return timeInteger > timeslot.from_time_integer && timeInteger <= timeslot.till_time_integer\n }\n })\n}\n\nconst asTime = (datetime, timeString) => {\n const [hourString, minuteString] = timeString.split(':')\n return moment(datetime).hour(parseInt(hourString)).minute(parseInt(minuteString))\n}\n\nconst fromAsTime = (datetime, timeslot) => {\n if (datetime && timeslot) {\n return asTime(datetime, timeslot.from_time)\n } else {\n return moment(datetime)\n }\n}\n\nconst tillAsTime = (datetime, timeslot) => {\n if (datetime && timeslot) {\n return asTime(datetime, timeslot.till_time)\n } else {\n return moment(datetime)\n }\n}\n\nexport { getTimeslot, filterTimeslots, asTime, fromAsTime, tillAsTime }\n","const fakeTracker = (...args) => {\n console.log('ga', ...args)\n}\n\nconst tracker = () => {\n return window.ga && typeof window.ga === 'function' ? window.ga : fakeTracker\n}\n\nconst analyticsAllowed = () => {\n return window.CookieConsent.acceptedCategory('analytics')\n}\n\nconst listSources = {\n essentials: 'Essential list',\n quicksearch: 'Quicksearch'\n}\n\nconst track = (event, data) => {\n const ga = tracker()\n\n if (event !== 'page.visit' && !analyticsAllowed()) {\n return\n }\n\n // Always anonymize IP addresses (last three digits IP4)\n ga('set', 'anonymizeIp', true);\n\n switch (event) {\n case 'page.visit': {\n ga('set', 'location', data.url)\n ga('send', 'pageview', { 'title': document.title })\n\n break\n }\n case 'customer': {\n if (data.category) ga('set', 'dimension1', data.category)\n if (data.segment) ga('set', 'dimension2', data.segment)\n\n ga('send', 'event', 'customer', 'information', { 'nonInteraction': 1 })\n\n break\n }\n case 'cart.book_product': {\n const line = data.cart.carts_products.find((line) => line.product_id === data.productId)\n\n ga('ec:addProduct', {\n 'id': line.product.id,\n 'name': line.product.name,\n 'quantity': 1\n })\n ga('ec:setAction', 'click', { 'list': data.source })\n ga('send', 'event', 'UX', 'click', 'Add to cart')\n\n break\n }\n case 'cart.change_product_quantity': {\n ga('ec:addProduct', {\n 'id': data.product.id,\n 'name': data.product.name,\n 'quantity': Math.abs(data.newQuantity)\n })\n\n if (data.newQuantity > 0) {\n ga('ec:setAction', 'click', { 'list': 'Cart' })\n ga('send', 'event', 'automatic', 'click', 'Add to cart', { 'nonInteraction': 1 })\n } else {\n ga('ec:setAction', 'remove')\n ga('send', 'event', 'UX', 'click', 'Remove from cart')\n }\n\n break\n }\n case 'checkout.cart':\n case 'checkout.sign_in':\n case 'checkout.information': {\n const checkoutSteps = {\n 'checkout.cart': 1,\n 'checkout.sign_in': 2,\n 'checkout.information': 3\n }\n\n data.products.forEach((product) => { ga('ec:addProduct', product) })\n ga('ec:setAction','checkout', { step: checkoutSteps[event] })\n\n ga('send', 'event', 'checkout', event.split('.')[1]);\n\n break\n }\n case 'checkout.confirmation': {\n data.products.forEach((product) => {\n ga('ec:addProduct', product)\n })\n\n ga('ec:setAction','purchase', { id: data.id, revenue: data.total })\n\n window.goog_report_conversion && window.goog_report_conversion()\n\n ga('send', 'event', 'checkout', 'confirmation');\n\n break\n }\n case 'product.impression': {\n data.products.forEach((product) => {\n ga('ec:addImpression', {\n name: product.name,\n id: product.id,\n list: listSources[data.source] || data.source\n })\n })\n\n if (data.source === 'essentials') {\n ga('send', 'event', 'automatic', 'click', 'Select essentials', { 'nonInteraction': 1 })\n } else {\n ga('send', 'event', 'product', 'impression', { 'nonInteraction': 1 });\n }\n\n break\n }\n case 'product.view': {\n ga('ec:addProduct', data.product)\n ga('ec:setAction', 'detail')\n ga('send', 'event', 'product', 'view', data.product.name);\n\n break\n }\n case 'product.click': {\n const sources = {\n quicksearch: 'Product in quicksearch'\n }\n\n ga('ec:addProduct', {\n 'name': data.product.name,\n 'id': data.product.id\n })\n ga('ec:setAction', 'click', {\n 'list': listSources[data.source] || data.source\n })\n ga('send', 'event', 'UX', 'click', sources[data.source] || data.source)\n }\n }\n}\n\nexport default track\n","const fakeTracker = (...args) => {\n console.log('gtm', ...args);\n};\n\nexport const tracker = () => {\n if (!window.dataLayer) {\n return fakeTracker;\n }\n\n window.dataLayer = window.dataLayer || [];\n return (event) => window.dataLayer.push(event);\n};\n\nconst listSources = {\n essentials: 'Essential list',\n quicksearch: 'Quicksearch',\n};\n\nconst splitAndTrimCategories = (categories) => (\n categories ? categories.split('|').map(category => category.trim()) : []\n);\n\nconst productLabel = (label) => (\n label?.trim() || undefined\n);\n\nconst createProductObject = (product, source) => {\n const { name, id, categories_nl, categories_translated, label } = product;\n\n const categoriesNL = splitAndTrimCategories(categories_nl);\n const categoriesTranslated = splitAndTrimCategories(categories_translated);\n\n return {\n item_name: name,\n item_id: id,\n item_list_name: productLabel(label) || listSources[source] || source,\n item_category_nl: categoriesNL[0] || '',\n item_category2_nl: categoriesNL[1] || '',\n item_category3_nl: categoriesNL[2] || '',\n item_category_translated: categoriesTranslated[0] || '',\n item_category2_translated: categoriesTranslated[1] || '',\n item_category3_translated: categoriesTranslated[2] || '',\n };\n};\n\nconst track = (event, data) => {\n const gtm = tracker();\n\n const handleProductImpression = () => {\n const items = data.products.map((product) =>\n createProductObject(product, data.source)\n );\n\n gtm({\n event: 'view_item_list',\n ecommerce: { items },\n });\n };\n\n const handleProductClickOrView = (eventType) => {\n gtm({\n event: eventType,\n ecommerce: {\n items: [\n createProductObject(data.product, data.source),\n ],\n },\n });\n };\n\n const handleCartBookProduct = () => {\n const line = data.cart.carts_products.find(\n (line) => line.product_id === data.productId\n );\n\n gtm({\n event: 'add_to_cart',\n ecommerce: {\n items: [\n {\n ...createProductObject(line.product, data.source),\n quantity: 1,\n },\n ],\n },\n });\n };\n\n const handleCartChangeProductQuantity = () => {\n const quantityDiff = data.quantity - data.newQuantity;\n const eventType = quantityDiff > 0 ? 'add_to_cart' : 'remove_from_cart';\n\n gtm({\n event: eventType,\n ecommerce: {\n items: [\n {\n ...createProductObject(data.product, data.source),\n quantity: quantityDiff,\n },\n ],\n },\n });\n };\n\n const handleCheckoutSteps = () => {\n const checkoutSteps = {\n 'checkout.cart': 1,\n 'checkout.sign_in': 2,\n 'checkout.information': 3,\n };\n\n const items = data.products.map((product) =>\n createProductObject(product, data.source)\n );\n\n if (event === 'checkout.cart') {\n gtm({\n event: 'begin_checkout',\n ecommerce: { items },\n });\n }\n\n gtm({\n event: 'set_checkout_option',\n checkout_step: checkoutSteps[event],\n checkout_option: event.split('.')[1],\n });\n };\n\n const handleCheckoutConfirmation = () => {\n const items = data.products.map((product) =>\n createProductObject(product, data.source)\n );\n\n gtm({\n event: 'purchase',\n ecommerce: {\n transaction_id: data.id,\n value: parseFloat(data.total),\n currency: 'EUR',\n items,\n },\n });\n };\n\n switch (event) {\n case 'product.impression':\n handleProductImpression();\n break;\n case 'product.click':\n handleProductClickOrView('select_item');\n break;\n case 'product.view':\n handleProductClickOrView('view_item');\n break;\n case 'cart.book_product':\n handleCartBookProduct();\n break;\n case 'cart.change_product_quantity':\n handleCartChangeProductQuantity();\n break;\n case 'checkout.cart':\n case 'checkout.sign_in':\n case 'checkout.information':\n handleCheckoutSteps();\n break;\n case 'checkout.confirmation':\n handleCheckoutConfirmation();\n break;\n default:\n console.warn(`Unhandled event type: ${event}`);\n }\n};\n\nexport default track;\n","const trackingData = (type, data) => {\n switch (type) {\n case 'cart': {\n return {\n currency: 'EUR',\n value: data.total,\n content_ids: data.products.map((product) => product.id),\n num_items: data.products.length,\n ...trackingData('customer')\n }\n }\n case 'customer': {\n const customerData = window.customerData\n\n return {\n customer_segment: customerData.segment,\n customer_category: customerData.category\n }\n }\n case 'book_product': {\n return {\n currency: 'EUR',\n value: (data.line.total_in_cents / 100.0) / data.line.qty,\n content_name: data.line.product.name,\n content_type: 'product',\n content_ids: [data.line.product.id],\n ...trackingData('customer')\n }\n }\n }\n}\n\nconst fakeTracker = (...args) => {\n console.log('fbq', ...args)\n}\n\nconst tracker = () => {\n return window.fbq && typeof window.fbq === 'function' ? window.fbq : fakeTracker\n}\n\nconst track = (event, data) => {\n const fbq = tracker()\n\n switch (event) {\n case 'page.visit': {\n fbq('track', 'PageView')\n\n break\n }\n case 'cart.book_product': {\n const line = data.cart.carts_products.find((line) => line.product_id === data.productId)\n\n fbq('track', 'AddToCart', trackingData('book_product', { ...data, line }))\n\n break\n }\n case 'checkout.cart': {\n fbq('track', 'InitiateCheckout', trackingData('cart', data))\n\n break\n }\n case 'checkout.confirmation': {\n fbq('track', 'Purchase', trackingData('cart', data))\n\n break\n }\n\n }\n}\n\nexport default track\n","import gaTrack from './ga'\nimport gtmTrack from './gtm'\nimport fbqTrack from './fbq'\n\nconst isLocal = () => {\n return window.ENVIRONMENT === 'development' || window.ENVIRONMENT === 'test'\n}\n\nconst analyticsAllowed = () => {\n return window.CookieConsent.acceptedCategory('analytics')\n}\n\nconst marketingAllowed = () => {\n return window.CookieConsent.acceptedCategory('advertisement')\n}\n\nconst trackEventAcrossServices = (event, data) => {\n if (isLocal()) {\n console.log('Tracking functional event', event, data)\n } else {\n gtmTrack(event, data)\n\n if (analyticsAllowed()) {\n gaTrack(event, data)\n }\n }\n\n if (marketingAllowed()) {\n if (isLocal()) {\n console.log('Tracking marketing event', event, data)\n } else {\n fbqTrack(event, data)\n }\n }\n}\n\nconst track = (event, data) => {\n if (document.readyState !== 'complete') {\n if (isLocal()) console.log('Tracking.track', 'page still loading', event)\n\n window.addEventListener('load', () => {\n // Try again when the page has loaded\n track(event, data)\n })\n\n return\n }\n\n trackEventAcrossServices(event, data)\n}\n\nexport { track }\n","import { isEmpty } from 'lodash'\n\nconst TWEAKWISE_PREFIX = '#twn|?';\n\nconst parseTweakwiseHash = (hash) => {\n if (!hash.startsWith(TWEAKWISE_PREFIX)) {\n return null;\n }\n\n return new URLSearchParams(hash.replace(TWEAKWISE_PREFIX, ''));\n};\n\nexport const getQuery = (hash) => {\n const params = parseTweakwiseHash(hash);\n\n if (!params) {\n return null;\n }\n\n return params.get('tn_q');\n}\n\nexport const isQueryPresent = () => {\n const query = getQuery(window.location.hash);\n return !isEmpty(query);\n}\n\nexport const resetQuery = () => {\n const params = parseTweakwiseHash(window.location.hash);\n if (!params) {\n return;\n }\n\n params.delete('tn_q');\n const newHash = params.toString() ? `${TWEAKWISE_PREFIX}${params}` : '';\n const newUrl = `${window.location.origin}${window.location.pathname}${newHash}`;\n window.history.replaceState({}, '', newUrl);\n}\n","var map = {\n\t\"./de\": 77853,\n\t\"./de.js\": 77853,\n\t\"./fr\": 85498,\n\t\"./fr.js\": 85498,\n\t\"./nl\": 92572,\n\t\"./nl.js\": 92572\n};\n\n\nfunction webpackContext(req) {\n\tvar id = webpackContextResolve(req);\n\treturn __webpack_require__(id);\n}\nfunction webpackContextResolve(req) {\n\tif(!__webpack_require__.o(map, req)) {\n\t\tvar e = new Error(\"Cannot find module '\" + req + \"'\");\n\t\te.code = 'MODULE_NOT_FOUND';\n\t\tthrow e;\n\t}\n\treturn map[req];\n}\nwebpackContext.keys = function webpackContextKeys() {\n\treturn Object.keys(map);\n};\nwebpackContext.resolve = webpackContextResolve;\nmodule.exports = webpackContext;\nwebpackContext.id = 80039;","var map = {\n\t\"./AddOptionalAccessories\": 38369,\n\t\"./AddOptionalAccessories/\": 38369,\n\t\"./AddOptionalAccessories/AccessoryList\": 52587,\n\t\"./AddOptionalAccessories/AccessoryList.jsx\": 52587,\n\t\"./AddOptionalAccessories/Button\": 75681,\n\t\"./AddOptionalAccessories/Button.jsx\": 75681,\n\t\"./AddOptionalAccessories/Desktop\": 70879,\n\t\"./AddOptionalAccessories/Desktop.jsx\": 70879,\n\t\"./AddOptionalAccessories/Mobile\": 8223,\n\t\"./AddOptionalAccessories/Mobile.jsx\": 8223,\n\t\"./AddOptionalAccessories/index\": 38369,\n\t\"./AddOptionalAccessories/index.jsx\": 38369,\n\t\"./AddProductToCart\": 89999,\n\t\"./AddProductToCart/\": 89999,\n\t\"./AddProductToCart/Button\": 79811,\n\t\"./AddProductToCart/Button.jsx\": 79811,\n\t\"./AddProductToCart/Icon\": 19936,\n\t\"./AddProductToCart/Icon.jsx\": 19936,\n\t\"./AddProductToCart/Link\": 18867,\n\t\"./AddProductToCart/Link.jsx\": 18867,\n\t\"./AddProductToCart/index\": 89999,\n\t\"./AddProductToCart/index.jsx\": 89999,\n\t\"./AddProductToWishlist\": 80996,\n\t\"./AddProductToWishlist/\": 80996,\n\t\"./AddProductToWishlist/Login\": 48179,\n\t\"./AddProductToWishlist/Login.jsx\": 48179,\n\t\"./AddProductToWishlist/NewList\": 73230,\n\t\"./AddProductToWishlist/NewList.jsx\": 73230,\n\t\"./AddProductToWishlist/SelectList\": 71722,\n\t\"./AddProductToWishlist/SelectList.jsx\": 71722,\n\t\"./AddProductToWishlist/index\": 80996,\n\t\"./AddProductToWishlist/index.jsx\": 80996,\n\t\"./Cart/Coupon\": 35066,\n\t\"./Cart/Coupon.jsx\": 35066,\n\t\"./Cart/Delivery\": 33516,\n\t\"./Cart/Delivery.jsx\": 33516,\n\t\"./Cart/Information\": 32312,\n\t\"./Cart/Information.jsx\": 32312,\n\t\"./Cart/Insurance\": 222,\n\t\"./Cart/Insurance.jsx\": 222,\n\t\"./Cart/ModalWrapper\": 47069,\n\t\"./Cart/ModalWrapper.jsx\": 47069,\n\t\"./Cart/OptionalAccessoryProduct\": 13611,\n\t\"./Cart/OptionalAccessoryProduct.jsx\": 13611,\n\t\"./Cart/Picker\": 23144,\n\t\"./Cart/Picker.jsx\": 23144,\n\t\"./Cart/Prices\": 22502,\n\t\"./Cart/Prices.jsx\": 22502,\n\t\"./Cart/Products\": 45166,\n\t\"./Cart/Products.jsx\": 45166,\n\t\"./Cart/Share\": 78483,\n\t\"./Cart/Share.jsx\": 78483,\n\t\"./Cart/Validation\": 60388,\n\t\"./Cart/Validation.js\": 60388,\n\t\"./CartIcon\": 71452,\n\t\"./CartIcon.jsx\": 71452,\n\t\"./DatePicker\": 98850,\n\t\"./DatePicker.jsx\": 98850,\n\t\"./Essentials\": 79190,\n\t\"./Essentials.jsx\": 79190,\n\t\"./Loader\": 34908,\n\t\"./Loader.jsx\": 34908,\n\t\"./Modal\": 60082,\n\t\"./Modal.jsx\": 60082,\n\t\"./Notification\": 87936,\n\t\"./Notification.jsx\": 87936,\n\t\"./PWAPrompt\": 44994,\n\t\"./PWAPrompt/\": 44994,\n\t\"./PWAPrompt/components/HomeScreenIcon\": 45069,\n\t\"./PWAPrompt/components/HomeScreenIcon.js\": 45069,\n\t\"./PWAPrompt/components/PWAPrompt\": 1509,\n\t\"./PWAPrompt/components/PWAPrompt.js\": 1509,\n\t\"./PWAPrompt/components/ShareIcon\": 81741,\n\t\"./PWAPrompt/components/ShareIcon.js\": 81741,\n\t\"./PWAPrompt/index\": 44994,\n\t\"./PWAPrompt/index.js\": 44994,\n\t\"./Picker\": 25430,\n\t\"./Picker/\": 25430,\n\t\"./Picker/Calendar\": 68670,\n\t\"./Picker/Calendar.jsx\": 68670,\n\t\"./Picker/Desktop\": 99606,\n\t\"./Picker/Desktop.jsx\": 99606,\n\t\"./Picker/Intro\": 47780,\n\t\"./Picker/Intro.jsx\": 47780,\n\t\"./Picker/Mobile\": 86018,\n\t\"./Picker/Mobile.jsx\": 86018,\n\t\"./Picker/OptionSidebar\": 9079,\n\t\"./Picker/OptionSidebar.jsx\": 9079,\n\t\"./Picker/SelectPlaceholder\": 63829,\n\t\"./Picker/SelectPlaceholder.jsx\": 63829,\n\t\"./Picker/index\": 25430,\n\t\"./Picker/index.jsx\": 25430,\n\t\"./Picker/modes\": 72559,\n\t\"./Picker/modes/\": 72559,\n\t\"./Picker/modes/Dates\": 18912,\n\t\"./Picker/modes/Dates.jsx\": 18912,\n\t\"./Picker/modes/Depots\": 11558,\n\t\"./Picker/modes/Depots.jsx\": 11558,\n\t\"./Picker/modes/Timeslots\": 25053,\n\t\"./Picker/modes/Timeslots.jsx\": 25053,\n\t\"./Picker/modes/index\": 72559,\n\t\"./Picker/modes/index.jsx\": 72559,\n\t\"./Poll\": 57795,\n\t\"./Poll/\": 57795,\n\t\"./Poll/Results\": 48877,\n\t\"./Poll/Results.jsx\": 48877,\n\t\"./Poll/Vote\": 49187,\n\t\"./Poll/Vote.jsx\": 49187,\n\t\"./Poll/index\": 57795,\n\t\"./Poll/index.jsx\": 57795,\n\t\"./Popover\": 67302,\n\t\"./Popover.jsx\": 67302,\n\t\"./Product\": 85986,\n\t\"./Product/\": 85986,\n\t\"./Product/AddToCart\": 14821,\n\t\"./Product/AddToCart.jsx\": 14821,\n\t\"./Product/AddToWishlist\": 28090,\n\t\"./Product/AddToWishlist.jsx\": 28090,\n\t\"./Product/ListItem\": 11800,\n\t\"./Product/ListItem.jsx\": 11800,\n\t\"./Product/Price\": 5782,\n\t\"./Product/Price.jsx\": 5782,\n\t\"./Product/Tile\": 67341,\n\t\"./Product/Tile.jsx\": 67341,\n\t\"./Product/index\": 85986,\n\t\"./Product/index.jsx\": 85986,\n\t\"./ProductDetailPrice\": 43744,\n\t\"./ProductDetailPrice.jsx\": 43744,\n\t\"./ProductFilter\": 29263,\n\t\"./ProductFilter/\": 29263,\n\t\"./ProductFilter/Button\": 15747,\n\t\"./ProductFilter/Button.jsx\": 15747,\n\t\"./ProductFilter/Filter\": 83973,\n\t\"./ProductFilter/Filter.jsx\": 83973,\n\t\"./ProductFilter/FilterGroup\": 38048,\n\t\"./ProductFilter/FilterGroup.jsx\": 38048,\n\t\"./ProductFilter/List\": 92311,\n\t\"./ProductFilter/List.jsx\": 92311,\n\t\"./ProductFilter/NoResults\": 26498,\n\t\"./ProductFilter/NoResults.jsx\": 26498,\n\t\"./ProductFilter/Tags\": 42572,\n\t\"./ProductFilter/Tags.jsx\": 42572,\n\t\"./ProductFilter/index\": 29263,\n\t\"./ProductFilter/index.jsx\": 29263,\n\t\"./ProductFilter/utils\": 31144,\n\t\"./ProductFilter/utils.js\": 31144,\n\t\"./ProductGroup\": 19871,\n\t\"./ProductGroup.jsx\": 19871,\n\t\"./ProductList\": 64856,\n\t\"./ProductList.jsx\": 64856,\n\t\"./ProductResults\": 18464,\n\t\"./ProductResults.jsx\": 18464,\n\t\"./ProductSort\": 52834,\n\t\"./ProductSort.jsx\": 52834,\n\t\"./ProductSum\": 17731,\n\t\"./ProductSum.jsx\": 17731,\n\t\"./SearchBar\": 72434,\n\t\"./SearchBar.jsx\": 72434,\n\t\"./SearchIconSlide\": 16007,\n\t\"./SearchIconSlide.jsx\": 16007,\n\t\"./SearchInput\": 25785,\n\t\"./SearchInput.jsx\": 25785,\n\t\"./Select\": 9265,\n\t\"./Select.jsx\": 9265,\n\t\"./Tooltip\": 47772,\n\t\"./Tooltip.jsx\": 47772,\n\t\"./Wishlist\": 12928,\n\t\"./Wishlist.jsx\": 12928\n};\n\n\nfunction webpackContext(req) {\n\tvar id = webpackContextResolve(req);\n\treturn __webpack_require__(id);\n}\nfunction webpackContextResolve(req) {\n\tif(!__webpack_require__.o(map, req)) {\n\t\tvar e = new Error(\"Cannot find module '\" + req + \"'\");\n\t\te.code = 'MODULE_NOT_FOUND';\n\t\tthrow e;\n\t}\n\treturn map[req];\n}\nwebpackContext.keys = function webpackContextKeys() {\n\treturn Object.keys(map);\n};\nwebpackContext.resolve = webpackContextResolve;\nmodule.exports = webpackContext;\nwebpackContext.id = 68851;"],"names":["i18n","I18n","translations","LOCALE","locale","defaultLocale","DEFAULT_LOCALE","ReactRailsUJS","store","originalGetConstructor","getConstructor","className","Component","props","availableLocales","useLocaleInPath","React","Provider","componentRequireContext","require","reduxComponents","fetchProducts","params","axios","method","url","fetchPrices","_ref","ids","fetchEssentials","_ref2","id","concat","searchProducts","_ref3","formats","date","time","default","t","apply","arguments","formatDate","type","allTranslations","format","moment","datetime","capitalize","calendarDate","small","length","undefined","lastDay","sameDay","nextDay","lastWeek","nextWeek","sameElse","day","calendar","getDateFnsLocale","nl","fr","de","enUS","translatePeriodLabel","label","displayLabel","test","_label$split2","_slicedToArray","split","amount","chargeType","count","parseInt","opts","setDefault","instance","applyCaseMiddleware","create","ignoreHeaders","ignoreParams","preservedKeys","input","includes","window","interceptors","request","use","config","_objectSpread","_document$querySelect","token","document","querySelector","content","headers","error","Promise","reject","response","ListItem","name","image","onChange","selected","modifiers","push","map","m","src","loading","e","target","checked","accessories","isSelected","flatMap","accessory","times","qty","i","key","image_thumb","renderList","onClick","disabled","success","Icon","visible","onClickOutside","_ref$theme","theme","children","Tippy","interactive","maxWidth","popperOptions","strategy","options","fallbackPlacements","productName","Modal","Header","Title","SubTitle","product","Body","AccessoryList","Footer","Button","show","onClose","_ref$selectedAccessor","selectedAccessories","_useState2","useState","setSelected","useEffect","countBy","product_id","v","k","isMobile","Mobile","Desktop","accessoryId","indexOf","prev","_toConsumableArray","filter","preventDefault","accessoryIds","x","forwardRef","ref","buttonContent","isDisabled","buttonClasses","role","Link","href","alt","width","onBack","dispatch","useDispatch","setName","_useState4","valid","setValid","onSubmit","createWishlist","productIds","then","minLength","required","value","placeholder","modifierClass","join","iconClass","List","wishlists","wishlist","product_ids","onNewList","useSelector","state","fetchWishlists","wishlistId","addToWishlist","productId","removeFromWishlist","user","step","setStep","Login","SelectList","NewList","coupon","cart","inputNode","useRef","code","setShow","setCode","_useState6","status","setStatus","hasCoupon","isEmpty","trim","current","focus","removeCouponFromCart","track","applyCouponOnCart","endsWith","Loader","company","faq_url","faq_link","paragraphs","price","delivery_service_price_from","border","p","dangerouslySetInnerHTML","__html","_ref$readonly","readonly","dataRequired","return_depot_id","pickup_depot_id","pickup_time","return_time","isValid","pickupDepot","pickup_depot","returnDepot","return_depot","handleEditPickup","openPicker","company_name","name_legal","payload","errors","values","intro","modalVisible","picker","open","backgroundNode","html","classList","add","remove","handleModalVisible","closePicker","Intro","index","message","updateCart","pickupTime","returnTime","products","reduce","acc","item","get","Object","assign","set","Map","opening_times","data","errorData","timeslots","depots","_ref$intro","_objectWithoutProperties","_excluded","openingTimes","currentDomain","domain","pickupDate","returnDate","pickupTimeslot","returnTimeslot","setValues","pickupOpeningTimes","setPickupOpeningTimes","returnOpeningTimes","setReturnOpeningTimes","_useState8","pickupTimeslots","setPickupTimeslots","_useState10","returnTimeslots","setReturnTimeslots","pickupDepots","depot","domain_id","otherDepots","returnDepots","handleReset","toDate","find","getTimeslot","prevValues","fetchOpeningTimes","fetchTimeslots","fetchDepots","timeRules","el","depot_id","isEqual","fromAsTime","tillAsTime","timeslot","from_time_integer","newTimeslots","filterTimeslots","validation","Validation","ModalWrapper","LocationDate","handleSave","isDayAvailable","bind","isTimeslotAvailable","firstAvailableAt","_defineProperty","inFlow","range","from","to","hasError","reservePath","orderButtonMessage","hasInsurance","setHasInsurance","showInsuranceInfo","setShowInsuranceInfo","wantsDelivery","setWantsDelivery","showDeliveryInfo","setShowDeliveryInfo","canReserve","wants_insurance","delivery_service","handleShowInsuranceInfo","handleShowDeliveryInfo","formattedSubtotal","discount_in_cents","formattedDiscount","coupon_discount_in_cents","formattedCouponDiscount","htmlFor","Insurance","formattedInsurance","Delivery","total_in_cents","subtotal_in_cents","formattedTotal","vat_in_cents","percentage","vat_amount","formattedVat","formattedGrandTotal","location","quantity","updateQuantity","accessory_ids","update","CartProduct","component","charge_label","setQuantity","path","productPath","slug","setVisible","hide","setLoading","setSuccess","debouncedChangeQuantity","_","newQuantity","oldQuantity","handleChangeQuantity","imageThumbLarge","OptionalAccessoryProduct","AddOptionalAccessories","_ref3$accessoryIds","updateCartsProduct","_ref4","source","setTimeout","optional_pickup_accessories","_ref5","_ref5$showClearCart","showClearCart","cartsProducts","carts_products","mainProducts","parent_id","accessoryProducts","clearCart","reload","cartsProduct","cartsProductId","copySuccess","setCopySuccess","CopyToClipboard","onCopy","text","diffInDays","date1","date2","Math","abs","clone","startOf","diff","timeAsInteger","hours","minutes","timeStringAsInteger","timeString","_timeString$split2","hourString","minuteString","openingTimesForDepot","openingTime","domainId","isOpeningTimeRelevantToDomain","depotId","isOpeningTimeRelevantToDepot","isDayOpen","depotOpeningTimes","dateOpeningTimes","o","recurringOpeningTimes","weekday","every","entire_day","some","isTimeslotOpen","timeslotFrom","from_time","timeslotTill","till_time","till","until_time","withinOpeningTime","max","min","_classCallCheck","this","first_available_at","pickupAt","returnAt","isBefore","isDayDisabled","isSame","till_time_integer","isTimeslotDisabled","_this","diffDays","pickupAfter","isAfter","forEach","valuesFor","Product","priceLabel","Dropdown","cartPath","Empty","productsPath","cartProduct","imageThumb","dropdown","collapsed","setCollapsed","node","useClickOutside","handleToggle","classNames","cartIcon","dropdownIcon","cartCount","PopoverContainer","style","Popover","handleOutsideClick","contains","parentRef","addEventListener","removeEventListener","useOutsideClick","Calendar","selectedDay","popoverOpen","setPopoverOpen","referenceElement","setReferenceElement","popperElement","setPopperElement","_usePopper","usePopper","placement","offset","styles","attributes","setPopoverPosition","direction","scrollable","clampX","boundingRect","getBoundingClientRect","top","scrollY","bottom","left","transform","height","right","getPopoverPosition","popoverPosition","icon","createPortal","popper","body","Category","ProductList","productClassName","initialize","setInitialize","essentials","setEssentials","categories","meta","categoryData","category","essential_category_id","sortedData","sortBy","_ref6","scrollTo","handleToggleShow","notification","title","_ref$type","Store","addNotification","insert","container","animationIn","animationOut","dismiss","duration","_ref$modern","modern","xmlns","viewBox","d","fill","delay","copyTitle","copyBody","copyAddHomeButtonLabel","copyShareButtonLabel","copyClosePrompt","permanentlyHideOnDismiss","promptData","maxVisits","Boolean","isVisible","setVisibility","activeElement","blur","isiOS13","navigator","userAgent","visibilityClass","dismissPrompt","evt","localStorage","setItem","JSON","stringify","visits","onTransitionOut","currentTarget","display","onTransitionEnd","ShareIcon","HomeScreenIcon","isiOS","isiPadOS","isStandalone","_ref$timesToShow","timesToShow","_ref$promptOnVisit","promptOnVisit","_ref$permanentlyHideO","_ref$copyTitle","_ref$copyBody","_ref$copyShareButtonL","_ref$copyAddHomeButto","_ref$copyClosePrompt","_ref$delay","_ref$debug","debug","_ref$onClose","parse","getItem","toLowerCase","platform","maxTouchPoints","standalone","aboveMinVisits","PWAPrompt","DayPicker","defaultMonth","Date","before","period","active","start","end","modifiersClassNames","onDayClick","unavailable","showOutsideDays","grouped","handleDepotSelect","handleDateSelect","handleTimeslotSelect","returnDepotOptions","groupBy","country_name","Select","menuPortal","provided","zIndex","control","borderColor","usePortal","SelectPlaceholder","getOptionValue","option","getOptionLabel","components","Option","OptionSidebar","DatePicker","isOptionDisabled","formatCalendarDate","Action","noData","Tooltip","hasErrors","wrapperClasses","iconStyle","mode","getValue","setMode","titles","Details","onBackPress","Depots","Dates","Timeslots","modes","Sidebar","store_type","thumb","excerpt","street","street_no","zipcode","city","opening_hours","openingHour","days","entered","setEntered","closest","position","classes","innerRef","innerProps","onMouseEnter","onMouseLeave","WrapperComponent","groupedDepots","groupDepots","renderDepot","storeType","address","group","renderDepotGroup","handleChange","ResultOption","votes","a","poll_votes_count","toFixed","VoteOption","selectedOption","setSelectedOption","handleClickOption","Poll","description","Vote","Results","poll","setPoll","voted","poll_options","subtitle","fixedWidth","optionalPickupAccessories","initializeEssentials","setInitializeEssentials","button","link","hasOptionalPickupAccessories","handleClick","_ref2$accessoryIds","bookProductToCart","Essentials","addedFromState","added","setAdded","isMounted","setIsMounted","AddProductToWishlist","PricesListComponent","prices","PricesListItemComponent","formattedPrice","PriceComponent","_prices","_toArray","currentPrice","futurePrices","slice","AddToWishlist","Price","initialPrice","AddToCart","fetchData","pricesFromState","setPrices","fetchProductPrices","getLabel","isNew","expected","color","Label","ExpectedProduct","BookableProduct","otherProps","tile","Tile","list","priceClass","filterGroups","counters","filterPath","setFilterPath","selectedCount","flatten","ProductFilter","filters","navigate","Turbolinks","visit","handleToggleFilter","selectedFilters","isFilterPresent","reverse","renderedFilters","Filter","filterGroup","FilterGroup","_ref$navigate","_ref$view","view","setSelectedFilters","currentCounters","setCurrentCounters","View","FilterList","tags","FilterTags","filterSlug","filterGroupSlug","newSelectedFilters","toggleFilter","filtersPath","search","restoreScrollPosition","sortedFilters","fromPairs","toPairs","groupSlug","addOnly","isAdded","newFilters","currentFilters","splice","itemsVisible","setItemsVisible","handleStep","easeIn","easeInOut","containerWidth","clientWidth","itemWidth","ceil","maxStep","newStep","easing","mirrored","tween","ease","scrollLeft","_ref$trackImpression","trackImpression","setIds","handleClickProduct","allIds","newIds","difference","renderProduct","_props$getDynamicProp","global","dynamicComponentProps","filterCounters","currentParams","currentProducts","setCurrentProducts","per","page","setParams","hasNextPage","setHasNextPage","hasResults","ProductFilterButton","NoResults","newParams","newProducts","sort","sortOptions","setValue","defaultValue","_option$value$split2","newSort","newDirection","pathname","ret","encodeURIComponent","queryParams","setData","action","vat","formattedVatAmount","searchRef","refresh","modalElement","topPosition","positionModal","SearchInput","expanded","event","reset","setExpanded","showIcon","setShowIcon","showSearch","setShowSearch","handleToggleExpand","handleShowIcon","handleShowSearch","timer","clearTimeout","onIconClick","autoFocus","initialValue","modifier","queryValue","getQuery","hash","query","setQuery","useImperativeHandle","resetQuery","autoComplete","customDropdown","CustomSelectValue","selectProps","CustomGroupHeading","parentElement","nextElementSibling","GroupHeading","_ref3$components","ReactSelect","classNamePrefix","DropdownIndicator","SingleValue","menuPortalTarget","menuPosition","ReactTooltip","delayShow","place","fetchWishlist","ProductSum","callback","handleClickOutside","cartParams","getState","_ref4$quantity","_ref2$quantity","book","customerId","customer_id","applyCoupon","fetchAll","fetch","producten","innerWidth","documentElement","filterdTimeslots","opening_time_id","timeInteger","hour","minute","asTime","fakeTracker","_console","_len","args","Array","_key","console","log","listSources","quicksearch","ga","CookieConsent","acceptedCategory","segment","line","revenue","total","goog_report_conversion","splitAndTrimCategories","productLabel","createProductObject","categories_nl","categories_translated","categoriesNL","categoriesTranslated","item_name","item_id","item_list_name","item_category_nl","item_category2_nl","item_category3_nl","item_category_translated","item_category2_translated","item_category3_translated","quantityDiff","items","gtm","dataLayer","handleProductClickOrView","eventType","ecommerce","checkout_step","checkout_option","handleCheckoutSteps","transaction_id","parseFloat","currency","handleCheckoutConfirmation","warn","trackingData","content_ids","num_items","customerData","customer_segment","customer_category","content_name","content_type","fbq","isLocal","ENVIRONMENT","trackEventAcrossServices","gtmTrack","gaTrack","fbqTrack","readyState","TWEAKWISE_PREFIX","parseTweakwiseHash","startsWith","URLSearchParams","replace","isQueryPresent","newHash","toString","newUrl","origin","history","replaceState","webpackContext","req","webpackContextResolve","__webpack_require__","Error","keys","resolve","module","exports"],"sourceRoot":""}