first upload all files
This commit is contained in:
48
Themes/Storefront/resources/assets/public/js/Errors.js
Normal file
48
Themes/Storefront/resources/assets/public/js/Errors.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
export default class {
|
||||
constructor() {
|
||||
this.errors = {};
|
||||
}
|
||||
|
||||
record(errors) {
|
||||
this.errors = errors;
|
||||
}
|
||||
|
||||
any() {
|
||||
return Object.keys(this.errors).length > 0;
|
||||
}
|
||||
|
||||
has(key) {
|
||||
return this.errors.hasOwnProperty(key);
|
||||
}
|
||||
|
||||
get(key) {
|
||||
if (this.errors[key]) {
|
||||
return this.errors[key][0];
|
||||
}
|
||||
}
|
||||
|
||||
clear(key) {
|
||||
if (key === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vue.delete(this.errors, this.normalizeKey(key));
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.errors = {};
|
||||
}
|
||||
|
||||
normalizeKey(key) {
|
||||
let keyParts = key.replace('[]', '').split('[');
|
||||
|
||||
// No need to normalize the key.
|
||||
if (keyParts.length === 1) {
|
||||
return key;
|
||||
}
|
||||
|
||||
return keyParts.join('.').slice(0, -1);
|
||||
}
|
||||
}
|
||||
98
Themes/Storefront/resources/assets/public/js/app.js
Normal file
98
Themes/Storefront/resources/assets/public/js/app.js
Normal file
@@ -0,0 +1,98 @@
|
||||
require("./storefront");
|
||||
|
||||
import Vue from "vue";
|
||||
import store from "./store";
|
||||
import { notify, trans, chunk } from "./functions";
|
||||
import VueToast from "vue-toast-notification";
|
||||
import vClickOutside from "v-click-outside";
|
||||
import VPagination from "./components/VPagination.vue";
|
||||
import HeaderSearch from "./components/layout/HeaderSearch.vue";
|
||||
import SidebarCart from "./components/layout/SidebarCart";
|
||||
import NewsletterPopup from "./components/layout/NewsletterPopup";
|
||||
import NewsletterSubscription from "./components/layout/NewsletterSubscription";
|
||||
import CookieBar from "./components/layout/CookieBar";
|
||||
import LandscapeProducts from "./components/LandscapeProducts.vue";
|
||||
import DynamicTab from "./components/home/DynamicTab";
|
||||
import HomeFeatures from "./components/home/HomeFeatures.vue";
|
||||
import FeaturedCategories from "./components/home/FeaturedCategories.vue";
|
||||
import BannerThreeColumnFullWidth from "./components/home/BannerThreeColumnFullWidth.vue";
|
||||
import ProductTabsOne from "./components/home/ProductTabsOne.vue";
|
||||
import TopBrands from "./components/home/TopBrands.vue";
|
||||
import FlashSaleAndVerticalProducts from "./components/home/FlashSaleAndVerticalProducts.vue";
|
||||
import FlashSale from "./components/home/FlashSale.vue";
|
||||
import BannerTwoColumn from "./components/home/BannerTwoColumn.vue";
|
||||
import VerticalProducts from "./components/home/VerticalProducts.vue";
|
||||
import ProductGrid from "./components/home/ProductGrid.vue";
|
||||
import BannerThreeColumn from "./components/home/BannerThreeColumn.vue";
|
||||
import ProductTabsTwo from "./components/home/ProductTabsTwo.vue";
|
||||
import BannerOneColumn from "./components/home/BannerOneColumn.vue";
|
||||
import ProductIndex from "./components/products/Index";
|
||||
import ProductCardGridView from "./components/products/index/ProductCardGridView.vue";
|
||||
import ProductCardListView from "./components/products/index/ProductCardListView.vue";
|
||||
import ProductCardVertical from "./components/ProductCardVertical.vue";
|
||||
import ProductShow from "./components/products/Show";
|
||||
import CartIndex from "./components/cart/Index";
|
||||
import CheckoutCreate from "./components/checkout/Create";
|
||||
import CompareIndex from "./components/compare/Index";
|
||||
import MyWishlist from "./components/account/wishlist/Index";
|
||||
import MyAddresses from "./components/account/addresses/Index";
|
||||
|
||||
Vue.prototype.route = route;
|
||||
Vue.prototype.$notify = notify;
|
||||
Vue.prototype.$trans = trans;
|
||||
Vue.prototype.$chunk = chunk;
|
||||
|
||||
Vue.use(VueToast);
|
||||
Vue.use(vClickOutside);
|
||||
|
||||
Vue.component("v-pagination", VPagination);
|
||||
Vue.component("header-search", HeaderSearch);
|
||||
Vue.component("sidebar-cart", SidebarCart);
|
||||
Vue.component("newsletter-popup", NewsletterPopup);
|
||||
Vue.component("newsletter-subscription", NewsletterSubscription);
|
||||
Vue.component("cookie-bar", CookieBar);
|
||||
Vue.component("landscape-products", LandscapeProducts);
|
||||
Vue.component("dynamic-tab", DynamicTab);
|
||||
Vue.component("home-features", HomeFeatures);
|
||||
Vue.component("featured-categories", FeaturedCategories);
|
||||
Vue.component("banner-three-column-full-width", BannerThreeColumnFullWidth);
|
||||
Vue.component("product-tabs-one", ProductTabsOne);
|
||||
Vue.component("top-brands", TopBrands);
|
||||
Vue.component("flash-sale-and-vertical-products", FlashSaleAndVerticalProducts);
|
||||
Vue.component("flash-sale", FlashSale);
|
||||
Vue.component("banner-two-column", BannerTwoColumn);
|
||||
Vue.component("vertical-products", VerticalProducts);
|
||||
Vue.component("product-grid", ProductGrid);
|
||||
Vue.component("banner-three-column", BannerThreeColumn);
|
||||
Vue.component("product-tabs-two", ProductTabsTwo);
|
||||
Vue.component("banner-one-column", BannerOneColumn);
|
||||
Vue.component("product-index", ProductIndex);
|
||||
Vue.component("product-card-grid-view", ProductCardGridView);
|
||||
Vue.component("product-card-list-view", ProductCardListView);
|
||||
Vue.component("product-card-vertical", ProductCardVertical);
|
||||
Vue.component("product-show", ProductShow);
|
||||
Vue.component("cart-index", CartIndex);
|
||||
Vue.component("checkout-create", CheckoutCreate);
|
||||
Vue.component("compare-index", CompareIndex);
|
||||
Vue.component("my-wishlist", MyWishlist);
|
||||
Vue.component("my-addresses", MyAddresses);
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
|
||||
computed: {
|
||||
cart() {
|
||||
return store.state.cart;
|
||||
},
|
||||
|
||||
wishlistCount() {
|
||||
return store.wishlistCount();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
$.ajaxSetup({
|
||||
headers: {
|
||||
"X-CSRF-TOKEN": FleetCart.csrfToken,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<section class="landscape-products-wrap" v-if="hasAnyProduct">
|
||||
<div class="container">
|
||||
<div class="products-header">
|
||||
<h5 class="section-title">{{ title }}</h5>
|
||||
</div>
|
||||
|
||||
<div class="landscape-products" ref="productsPlaceholder">
|
||||
<ProductCard v-for="(product, index) in products" :key="index" :product="product"></ProductCard>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProductCard from './ProductCard.vue';
|
||||
import { slickPrevArrow, slickNextArrow } from '../functions';
|
||||
|
||||
export default {
|
||||
components: { ProductCard },
|
||||
|
||||
props: ['title', 'products'],
|
||||
|
||||
computed: {
|
||||
hasAnyProduct() {
|
||||
return this.products.length !== 0;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
$(this.$refs.productsPlaceholder).slick({
|
||||
rows: 0,
|
||||
dots: false,
|
||||
arrows: true,
|
||||
infinite: true,
|
||||
slidesToShow: 6,
|
||||
slidesToScroll: 6,
|
||||
rtl: window.FleetCart.rtl,
|
||||
prevArrow: slickPrevArrow(),
|
||||
nextArrow: slickNextArrow(),
|
||||
responsive: [
|
||||
{
|
||||
breakpoint: 1761,
|
||||
settings: {
|
||||
slidesToShow: 5,
|
||||
slidesToScroll: 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 1301,
|
||||
settings: {
|
||||
slidesToShow: 4,
|
||||
slidesToScroll: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 1051,
|
||||
settings: {
|
||||
slidesToShow: 3,
|
||||
slidesToScroll: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 992,
|
||||
settings: {
|
||||
slidesToShow: 4,
|
||||
slidesToScroll: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 881,
|
||||
settings: {
|
||||
slidesToShow: 3,
|
||||
slidesToScroll: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 768,
|
||||
settings: {
|
||||
dots: true,
|
||||
arrows: false,
|
||||
slidesToShow: 3,
|
||||
slidesToScroll: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 641,
|
||||
settings: {
|
||||
dots: true,
|
||||
arrows: false,
|
||||
slidesToShow: 2,
|
||||
slidesToScroll: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div class="product-card">
|
||||
<div class="product-card-top">
|
||||
<a :href="productUrl" class="product-image">
|
||||
<img :src="baseImage" :class="{ 'image-placeholder': ! hasBaseImage }" alt="product image">
|
||||
</a>
|
||||
|
||||
<div class="product-card-actions">
|
||||
<button
|
||||
class="btn btn-wishlist"
|
||||
:class="{ 'added': inWishlist }"
|
||||
:title="$trans('storefront::product_card.wishlist')"
|
||||
@click="syncWishlist"
|
||||
>
|
||||
<i class="la-heart" :class="inWishlist ? 'las' : 'lar'"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-compare"
|
||||
:class="{ 'added': inCompareList }"
|
||||
:title="$trans('storefront::product_card.compare')"
|
||||
@click="syncCompareList"
|
||||
>
|
||||
<i class="las la-random"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ul class="list-inline product-badge">
|
||||
<li class="badge badge-danger" v-if="product.is_out_of_stock">
|
||||
{{ $trans('storefront::product_card.out_of_stock') }}
|
||||
</li>
|
||||
|
||||
<li class="badge badge-primary" v-else-if="product.is_new">
|
||||
{{ $trans('storefront::product_card.new') }}
|
||||
</li>
|
||||
|
||||
<li class="badge badge-success" v-if="product.has_percentage_special_price">
|
||||
-{{ product.special_price_percent }}%
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="product-card-middle">
|
||||
<ProductRating :ratingPercent="product.rating_percent" :reviewCount="product.reviews.length"/>
|
||||
|
||||
<a :href="productUrl" class="product-name">
|
||||
<h6>{{ product.name }}</h6>
|
||||
</a>
|
||||
|
||||
<div class="product-price product-price-clone" v-html="product.formatted_price"></div>
|
||||
</div>
|
||||
|
||||
<div class="product-card-bottom">
|
||||
<div class="product-price" v-html="product.formatted_price"></div>
|
||||
|
||||
<button
|
||||
v-if="hasNoOption || product.is_out_of_stock"
|
||||
class="btn btn-primary btn-add-to-cart"
|
||||
:class="{ 'btn-loading': addingToCart }"
|
||||
:disabled="product.is_out_of_stock"
|
||||
@click="addToCart"
|
||||
>
|
||||
<i class="las la-cart-arrow-down"></i>
|
||||
{{ $trans('storefront::product_card.add_to_cart') }}
|
||||
</button>
|
||||
|
||||
<a
|
||||
v-else
|
||||
:href="productUrl"
|
||||
class="btn btn-primary btn-add-to-cart"
|
||||
>
|
||||
<i class="las la-eye"></i>
|
||||
{{ $trans('storefront::product_card.view_options') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProductRating from './ProductRating.vue';
|
||||
import ProductCardMixin from '../mixins/ProductCardMixin';
|
||||
|
||||
export default {
|
||||
components: { ProductRating },
|
||||
|
||||
mixins: [
|
||||
ProductCardMixin,
|
||||
],
|
||||
|
||||
props: ['product'],
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div :href="productUrl" class="vertical-product-card">
|
||||
<a :href="productUrl" class="product-image">
|
||||
<img :src="baseImage" :class="{ 'image-placeholder': ! hasBaseImage }" alt="product-image">
|
||||
</a>
|
||||
|
||||
<div class="product-info">
|
||||
<a :href="productUrl" class="product-name">
|
||||
<h6>{{ product.name }}</h6>
|
||||
</a>
|
||||
|
||||
<div class="product-price" v-html="product.formatted_price"></div>
|
||||
|
||||
<ProductRating :ratingPercent="product.rating_percent" :reviewCount="product.reviews.length"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProductRating from './ProductRating.vue';
|
||||
import ProductCardMixin from '../mixins/ProductCardMixin';
|
||||
|
||||
export default {
|
||||
components: { ProductRating },
|
||||
|
||||
mixins: [
|
||||
ProductCardMixin,
|
||||
],
|
||||
|
||||
props: ['product'],
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div class="product-rating">
|
||||
<div class="back-stars">
|
||||
<i class="las la-star"></i>
|
||||
<i class="las la-star"></i>
|
||||
<i class="las la-star"></i>
|
||||
<i class="las la-star"></i>
|
||||
<i class="las la-star"></i>
|
||||
|
||||
<div class="front-stars" :style="{ width: ratingPercent + '%' }">
|
||||
<i class="las la-star"></i>
|
||||
<i class="las la-star"></i>
|
||||
<i class="las la-star"></i>
|
||||
<i class="las la-star"></i>
|
||||
<i class="las la-star"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="rating-count" v-if="hasReviewCount">({{ this.reviewCount }})</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['ratingPercent', 'reviewCount'],
|
||||
|
||||
computed: {
|
||||
hasReviewCount() {
|
||||
return this.reviewCount !== undefined;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<ul class="pagination">
|
||||
<li class="page-item" :class="{ disabled: hasFirst }">
|
||||
<button class="page-link" :disabled="hasFirst" @click="prev">
|
||||
<i class="las la-angle-left"></i>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<li v-show="rangeFirstPage !== 1" class="page-item">
|
||||
<button class="page-link" @click="goto(1)">
|
||||
1
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<li v-show="rangeFirstPage === 3" class="page-item">
|
||||
<button class="page-link" @click="goto(2)">
|
||||
2
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<li
|
||||
v-show="rangeFirstPage !== 1 && rangeFirstPage !== 2 && rangeFirstPage !== 3"
|
||||
class="page-item disabled"
|
||||
>
|
||||
<span class="page-link">...</span>
|
||||
</li>
|
||||
|
||||
<li
|
||||
v-for="page in range"
|
||||
:key="page"
|
||||
class="page-item"
|
||||
:class="{ active: hasActive(page) }"
|
||||
>
|
||||
<button class="page-link" @click="goto(page)">
|
||||
{{ page }}
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<li
|
||||
v-show="rangeLastPage !== totalPage && rangeLastPage !== (totalPage - 1) && rangeLastPage !== (totalPage - 2)"
|
||||
class="page-item disabled"
|
||||
>
|
||||
<span class="page-link">...</span>
|
||||
</li>
|
||||
|
||||
<li v-show="rangeLastPage === (totalPage - 2)" class="page-item">
|
||||
<button class="page-link" @click="goto(totalPage - 1)">
|
||||
{{ totalPage - 1 }}
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<li v-if="rangeLastPage !== totalPage" class="page-item">
|
||||
<button class="page-link" @click="goto(totalPage)">
|
||||
{{ totalPage }}
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<li class="page-item" :class="{ disabled: hasLast }">
|
||||
<button class="page-link" :class="{ disabled: hasLast }" @click="next">
|
||||
<i class="las la-angle-right"></i>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
totalPage: Number,
|
||||
currentPage: Number,
|
||||
rangeMax: {
|
||||
type: Number,
|
||||
default: 3,
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.currentPage > this.totalPage) {
|
||||
this.$emit('page-changed', this.totalPage);
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
rangeFirstPage() {
|
||||
if (this.currentPage === 1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (this.currentPage === this.totalPage) {
|
||||
if ((this.totalPage - this.rangeMax) < 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return this.totalPage - this.rangeMax + 1;
|
||||
}
|
||||
|
||||
return this.currentPage - 1;
|
||||
},
|
||||
|
||||
rangeLastPage() {
|
||||
return Math.min(this.rangeFirstPage + this.rangeMax - 1, this.totalPage);
|
||||
},
|
||||
|
||||
range() {
|
||||
let rangeList = [];
|
||||
|
||||
for (let page = this.rangeFirstPage; page <= this.rangeLastPage; page += 1) {
|
||||
rangeList.push(page);
|
||||
}
|
||||
|
||||
return rangeList;
|
||||
},
|
||||
|
||||
hasFirst() {
|
||||
return this.currentPage === 1;
|
||||
},
|
||||
|
||||
hasLast() {
|
||||
return this.currentPage === this.totalPage;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
prev() {
|
||||
this.$emit('page-changed', this.currentPage - 1);
|
||||
},
|
||||
|
||||
next() {
|
||||
this.$emit('page-changed', this.currentPage + 1);
|
||||
},
|
||||
|
||||
goto(page) {
|
||||
if (this.currentPage !== page) {
|
||||
this.$emit('page-changed', page);
|
||||
}
|
||||
},
|
||||
|
||||
hasActive(page) {
|
||||
return page === this.currentPage;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,163 @@
|
||||
import Errors from '../../../Errors';
|
||||
|
||||
export default {
|
||||
props: ['initialAddresses', 'initialDefaultAddress', 'countries'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
addresses: this.initialAddresses,
|
||||
defaultAddress: this.initialDefaultAddress,
|
||||
form: { state: '' },
|
||||
states: {},
|
||||
errors: new Errors(),
|
||||
formOpen: false,
|
||||
editing: false,
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
firstCountry() {
|
||||
return Object.keys(this.countries)[0];
|
||||
},
|
||||
|
||||
hasAddress() {
|
||||
return Object.keys(this.addresses).length !== 0;
|
||||
},
|
||||
|
||||
hasNoStates() {
|
||||
return Object.keys(this.states).length === 0;
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.changeCountry(this.firstCountry);
|
||||
},
|
||||
|
||||
methods: {
|
||||
changeDefaultAddress(address) {
|
||||
this.$set(this.defaultAddress, 'address_id', address.id);
|
||||
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: route('account.change_default_address'),
|
||||
data: { address_id: address.id },
|
||||
}).then((message) => {
|
||||
this.$notify(message);
|
||||
}).catch((xhr) => {
|
||||
this.$notify(xhr.responseJSON.message);
|
||||
});
|
||||
},
|
||||
|
||||
changeCountry(country) {
|
||||
this.form.country = country;
|
||||
this.form.state = '';
|
||||
|
||||
this.fetchStates(country);
|
||||
},
|
||||
|
||||
fetchStates(country) {
|
||||
$.ajax({
|
||||
method: 'GET',
|
||||
url: route('countries.states.index', { code: country }),
|
||||
}).then((states) => {
|
||||
this.$set(this, 'states', states);
|
||||
});
|
||||
},
|
||||
|
||||
edit(address) {
|
||||
this.formOpen = true;
|
||||
this.editing = true;
|
||||
this.form = address;
|
||||
|
||||
this.fetchStates(address.country);
|
||||
},
|
||||
|
||||
remove(address) {
|
||||
if (! confirm(this.$trans('storefront::account.addresses.confirm'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
method: 'DELETE',
|
||||
url: route('account.addresses.destroy', address.id),
|
||||
}).then((response) => {
|
||||
this.$delete(this.addresses, address.id);
|
||||
this.$notify(response.message);
|
||||
}).catch((xhr) => {
|
||||
this.$notify(xhr.responseJSON.message);
|
||||
});
|
||||
},
|
||||
|
||||
cancel() {
|
||||
this.editing = false;
|
||||
this.formOpen = false;
|
||||
|
||||
this.errors.reset();
|
||||
this.resetForm();
|
||||
},
|
||||
|
||||
save() {
|
||||
this.loading = true;
|
||||
|
||||
if (this.editing) {
|
||||
this.update();
|
||||
} else {
|
||||
this.create();
|
||||
}
|
||||
},
|
||||
|
||||
update() {
|
||||
$.ajax({
|
||||
method: 'PUT',
|
||||
url: route('account.addresses.update', { id: this.form.id }),
|
||||
data: this.form,
|
||||
}).then((response) => {
|
||||
this.formOpen = false;
|
||||
this.editing = false;
|
||||
|
||||
this.addresses[this.form.id] = response.address;
|
||||
|
||||
this.resetForm();
|
||||
this.$notify(response.message);
|
||||
}).catch((xhr) => {
|
||||
if (xhr.status === 422) {
|
||||
this.errors.record(xhr.responseJSON.errors);
|
||||
}
|
||||
|
||||
this.$notify(xhr.responseJSON.message);
|
||||
}).always(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
|
||||
create() {
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: route('account.addresses.store'),
|
||||
data: this.form,
|
||||
}).then((response) => {
|
||||
this.formOpen = false;
|
||||
|
||||
let address = { [response.address.id]: response.address };
|
||||
|
||||
this.$set(this, 'addresses', { ...this.addresses, ...address });
|
||||
|
||||
this.resetForm();
|
||||
this.$notify(response.message);
|
||||
}).catch((xhr) => {
|
||||
if (xhr.status === 422) {
|
||||
this.errors.record(xhr.responseJSON.errors);
|
||||
}
|
||||
|
||||
this.$notify(xhr.responseJSON.message);
|
||||
}).always(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
|
||||
resetForm() {
|
||||
this.form = { state: '' };
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
import store from '../../../store';
|
||||
import VPagination from '../../VPagination.vue';
|
||||
import ProductHelpersMixin from '../../../mixins/ProductHelpersMixin';
|
||||
|
||||
export default {
|
||||
components: { VPagination },
|
||||
|
||||
mixins: [
|
||||
ProductHelpersMixin,
|
||||
],
|
||||
|
||||
data() {
|
||||
return {
|
||||
fetchingWishlist: false,
|
||||
products: { data: [] },
|
||||
currentPage: 1,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
wishlistIsEmpty() {
|
||||
return this.products.data.length === 0;
|
||||
},
|
||||
|
||||
totalPage() {
|
||||
return Math.ceil(this.products.total / 20);
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.fetchWishlist();
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchWishlist() {
|
||||
this.fetchingWishlist = true;
|
||||
|
||||
$.ajax({
|
||||
method: 'GET',
|
||||
url: route('wishlist.products.index', {
|
||||
page: this.currentPage,
|
||||
}),
|
||||
}).then((products) => {
|
||||
this.products = products;
|
||||
}).catch((xhr) => {
|
||||
this.$notify(xhr.responseJSON.message);
|
||||
}).always(() => {
|
||||
this.fetchingWishlist = false;
|
||||
});
|
||||
},
|
||||
|
||||
remove(product) {
|
||||
this.products.data.splice(this.products.data.indexOf(product), 1);
|
||||
this.products.total--;
|
||||
|
||||
store.removeFromWishlist(product.id);
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,130 @@
|
||||
import store from '../../store';
|
||||
import CartHelpersMixin from '../../mixins/CartHelpersMixin';
|
||||
import ProductHelpersMixin from '../../mixins/ProductHelpersMixin';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
CartHelpersMixin,
|
||||
ProductHelpersMixin,
|
||||
],
|
||||
|
||||
data() {
|
||||
return {
|
||||
shippingMethodName: null,
|
||||
crossSellProducts: [],
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
hasAnyCrossSellProduct() {
|
||||
return this.crossSellProducts.length !== 0;
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$nextTick(() => {
|
||||
if (store.state.cart.shippingMethodName) {
|
||||
this.changeShippingMethod(store.state.cart.shippingMethodName);
|
||||
} else {
|
||||
this.updateShippingMethod(this.firstShippingMethod);
|
||||
}
|
||||
|
||||
this.fetchCrossSellProducts();
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
optionValues(option) {
|
||||
let values = [];
|
||||
|
||||
for (let value of option.values) {
|
||||
values.push(value.label);
|
||||
}
|
||||
|
||||
return values.join(', ');
|
||||
},
|
||||
|
||||
updateQuantity(cartItem, qty) {
|
||||
if (qty < 1 || this.exceedsMaxStock(cartItem, qty)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNaN(qty)) {
|
||||
qty = 1;
|
||||
}
|
||||
|
||||
this.loadingOrderSummary = true;
|
||||
|
||||
cartItem.qty = qty;
|
||||
|
||||
$.ajax({
|
||||
method: 'PUT',
|
||||
url: route('cart.items.update', { cartItemId: cartItem.id }),
|
||||
data: { qty: qty || 1 },
|
||||
}).then((cart) => {
|
||||
store.updateCart(cart);
|
||||
}).catch((xhr) => {
|
||||
this.$notify(xhr.responseJSON.message);
|
||||
}).always(() => {
|
||||
this.loadingOrderSummary = false;
|
||||
});
|
||||
},
|
||||
|
||||
exceedsMaxStock(cartItem, qty) {
|
||||
return cartItem.product.manage_stock && cartItem.product.qty < qty;
|
||||
},
|
||||
|
||||
remove(cartItem) {
|
||||
this.loadingOrderSummary = true;
|
||||
|
||||
store.removeCartItem(cartItem);
|
||||
|
||||
if (store.cartIsEmpty()) {
|
||||
this.crossSellProducts = [];
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
method: 'DELETE',
|
||||
url: route('cart.items.destroy', { cartItemId: cartItem.id }),
|
||||
}).then((cart) => {
|
||||
store.updateCart(cart);
|
||||
}).catch((xhr) => {
|
||||
this.$notify(xhr.responseJSON.message);
|
||||
}).always(() => {
|
||||
this.loadingOrderSummary = false;
|
||||
});
|
||||
},
|
||||
|
||||
clearCart() {
|
||||
store.clearCart();
|
||||
|
||||
if (store.cartIsEmpty()) {
|
||||
this.crossSellProducts = [];
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: route('cart.clear.store'),
|
||||
}).then((cart) => {
|
||||
store.updateCart(cart);
|
||||
}).catch((xhr) => {
|
||||
this.$notify(xhr.responseJSON.message);
|
||||
});
|
||||
},
|
||||
|
||||
changeShippingMethod(shippingMethodName) {
|
||||
this.shippingMethodName = shippingMethodName;
|
||||
},
|
||||
|
||||
fetchCrossSellProducts() {
|
||||
$.ajax({
|
||||
method: 'GET',
|
||||
url: route('cart.cross_sell_products.index'),
|
||||
}).then((crossSellProducts) => {
|
||||
this.crossSellProducts = crossSellProducts;
|
||||
}).catch((xhr) => {
|
||||
this.$notify(xhr.responseJSON.message);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,671 @@
|
||||
/* eslint-disable */
|
||||
import store from "../../store";
|
||||
import Errors from "../../Errors";
|
||||
import CartHelpersMixin from "../../mixins/CartHelpersMixin";
|
||||
import ProductHelpersMixin from "../../mixins/ProductHelpersMixin";
|
||||
|
||||
export default {
|
||||
mixins: [CartHelpersMixin, ProductHelpersMixin],
|
||||
|
||||
props: [
|
||||
"customerEmail",
|
||||
"customerPhone",
|
||||
"gateways",
|
||||
"defaultAddress",
|
||||
"addresses",
|
||||
"countries",
|
||||
],
|
||||
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
customer_email: this.customerEmail,
|
||||
customer_phone: this.customerPhone,
|
||||
billing: {},
|
||||
shipping: {},
|
||||
billingAddressId: null,
|
||||
shippingAddressId: null,
|
||||
newBillingAddress: false,
|
||||
newShippingAddress: false,
|
||||
ship_to_a_different_address: false,
|
||||
},
|
||||
states: {
|
||||
billing: {},
|
||||
shipping: {},
|
||||
},
|
||||
placingOrder: false,
|
||||
errors: new Errors(),
|
||||
stripe: null,
|
||||
stripeCardElement: null,
|
||||
stripeError: null,
|
||||
authorizeNetToken: null,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
hasAddress() {
|
||||
return Object.keys(this.addresses).length !== 0;
|
||||
},
|
||||
|
||||
firstCountry() {
|
||||
return Object.keys(this.countries)[0];
|
||||
},
|
||||
|
||||
hasBillingStates() {
|
||||
return Object.keys(this.states.billing).length !== 0;
|
||||
},
|
||||
|
||||
hasShippingStates() {
|
||||
return Object.keys(this.states.shipping).length !== 0;
|
||||
},
|
||||
|
||||
hasNoPaymentMethod() {
|
||||
return Object.keys(this.gateways).length === 0;
|
||||
},
|
||||
|
||||
firstPaymentMethod() {
|
||||
return Object.keys(this.gateways)[0];
|
||||
},
|
||||
|
||||
shouldShowPaymentInstructions() {
|
||||
return ["bank_transfer", "check_payment"].includes(
|
||||
this.form.payment_method
|
||||
);
|
||||
},
|
||||
|
||||
paymentInstructions() {
|
||||
if (this.shouldShowPaymentInstructions) {
|
||||
return this.gateways[this.form.payment_method].instructions;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
"form.billingAddressId": function () {
|
||||
this.mergeSavedBillingAddress();
|
||||
},
|
||||
|
||||
"form.shippingAddressId": function () {
|
||||
this.mergeSavedShippingAddress();
|
||||
},
|
||||
|
||||
"form.billing.city": function (newCity) {
|
||||
if (newCity) {
|
||||
this.addTaxes();
|
||||
}
|
||||
},
|
||||
|
||||
"form.shipping.city": function (newCity) {
|
||||
if (newCity) {
|
||||
this.addTaxes();
|
||||
}
|
||||
},
|
||||
|
||||
"form.billing.zip": function (newZip) {
|
||||
if (newZip) {
|
||||
this.addTaxes();
|
||||
}
|
||||
},
|
||||
|
||||
"form.shipping.zip": function (newZip) {
|
||||
if (newZip) {
|
||||
this.addTaxes();
|
||||
}
|
||||
},
|
||||
|
||||
"form.billing.state": function (newState) {
|
||||
if (newState) {
|
||||
this.addTaxes();
|
||||
}
|
||||
},
|
||||
|
||||
"form.shipping.state": function (newState) {
|
||||
if (newState) {
|
||||
this.addTaxes();
|
||||
}
|
||||
},
|
||||
|
||||
"form.ship_to_a_different_address": function (newValue) {
|
||||
if (newValue && this.form.shippingAddressId) {
|
||||
this.form.shipping =
|
||||
this.addresses[this.form.shippingAddressId];
|
||||
} else {
|
||||
this.form.shipping = {};
|
||||
this.resetAddressErrors("shipping");
|
||||
}
|
||||
|
||||
this.addTaxes();
|
||||
},
|
||||
|
||||
"form.terms_and_conditions": function () {
|
||||
this.errors.clear("terms_and_conditions");
|
||||
},
|
||||
|
||||
"form.payment_method": function (newPaymentMethod) {
|
||||
if (newPaymentMethod === "paypal") {
|
||||
this.$nextTick(this.renderPayPalButton);
|
||||
}
|
||||
|
||||
if (newPaymentMethod !== "stripe") {
|
||||
this.stripeError = "";
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.defaultAddress.address_id) {
|
||||
this.form.billingAddressId = this.defaultAddress.address_id;
|
||||
this.form.shippingAddressId = this.defaultAddress.address_id;
|
||||
}
|
||||
|
||||
if (!this.hasAddress) {
|
||||
this.form.newBillingAddress = true;
|
||||
this.form.newShippingAddress = true;
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.changePaymentMethod(this.firstPaymentMethod);
|
||||
|
||||
if (store.state.cart.shippingMethodName) {
|
||||
this.changeShippingMethod(store.state.cart.shippingMethodName);
|
||||
} else {
|
||||
this.updateShippingMethod(this.firstShippingMethod);
|
||||
}
|
||||
|
||||
if (window.Stripe) {
|
||||
this.stripe = window.Stripe(FleetCart.stripePublishableKey);
|
||||
|
||||
this.renderStripeElements();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
addNewBillingAddress() {
|
||||
this.resetAddressErrors("billing");
|
||||
|
||||
this.form.billing = {};
|
||||
this.form.newBillingAddress = !this.form.newBillingAddress;
|
||||
|
||||
if (!this.form.newBillingAddress) {
|
||||
this.mergeSavedBillingAddress();
|
||||
}
|
||||
},
|
||||
|
||||
addNewShippingAddress() {
|
||||
this.resetAddressErrors("shipping");
|
||||
|
||||
this.form.shipping = {};
|
||||
this.form.newShippingAddress = !this.form.newShippingAddress;
|
||||
|
||||
if (!this.form.newShippingAddress) {
|
||||
this.mergeSavedShippingAddress();
|
||||
}
|
||||
},
|
||||
|
||||
// reset address errors based on address type
|
||||
resetAddressErrors(addressType) {
|
||||
Object.keys(this.errors.errors).map((key) => {
|
||||
key.indexOf(addressType) !== -1 && this.errors.clear(key);
|
||||
});
|
||||
},
|
||||
|
||||
mergeSavedBillingAddress() {
|
||||
this.resetAddressErrors("billing");
|
||||
|
||||
if (!this.form.newBillingAddress && this.form.billingAddressId) {
|
||||
this.form.billing = this.addresses[this.form.billingAddressId];
|
||||
}
|
||||
},
|
||||
|
||||
mergeSavedShippingAddress() {
|
||||
this.resetAddressErrors("shipping");
|
||||
|
||||
if (
|
||||
this.form.ship_to_a_different_address &&
|
||||
!this.form.newShippingAddress &&
|
||||
this.form.shippingAddressId
|
||||
) {
|
||||
this.form.shipping =
|
||||
this.addresses[this.form.shippingAddressId];
|
||||
}
|
||||
},
|
||||
|
||||
changeBillingCity(city) {
|
||||
this.$set(this.form.billing, "city", city);
|
||||
},
|
||||
|
||||
changeShippingCity(city) {
|
||||
this.$set(this.form.shipping, "city", city);
|
||||
},
|
||||
|
||||
changeBillingZip(zip) {
|
||||
this.$set(this.form.billing, "zip", zip);
|
||||
},
|
||||
|
||||
changeShippingZip(zip) {
|
||||
this.$set(this.form.shipping, "zip", zip);
|
||||
},
|
||||
|
||||
changeBillingCountry(country) {
|
||||
this.$set(this.form.billing, "country", country);
|
||||
|
||||
if (country === "") {
|
||||
this.form.billing.state = "";
|
||||
this.states.billing = {};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.fetchStates(country, (states) => {
|
||||
this.$set(this.states, "billing", states);
|
||||
this.$set(this.form.billing, "state", "");
|
||||
});
|
||||
},
|
||||
|
||||
changeShippingCountry(country) {
|
||||
this.$set(this.form.shipping, "country", country);
|
||||
|
||||
if (country === "") {
|
||||
this.form.shipping.state = "";
|
||||
this.states.shipping = {};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.fetchStates(country, (states) => {
|
||||
this.$set(this.states, "shipping", states);
|
||||
this.$set(this.form.shipping, "state", "");
|
||||
});
|
||||
},
|
||||
|
||||
fetchStates(country, callback) {
|
||||
$.ajax({
|
||||
method: "GET",
|
||||
url: route("countries.states.index", { code: country }),
|
||||
}).then(callback);
|
||||
},
|
||||
|
||||
changeBillingState(state) {
|
||||
this.$set(this.form.billing, "state", state);
|
||||
},
|
||||
|
||||
changeShippingState(state) {
|
||||
this.$set(this.form.shipping, "state", state);
|
||||
},
|
||||
|
||||
changePaymentMethod(paymentMethod) {
|
||||
this.$set(this.form, "payment_method", paymentMethod);
|
||||
},
|
||||
|
||||
changeShippingMethod(shippingMethodName) {
|
||||
this.$set(this.form, "shipping_method", shippingMethodName);
|
||||
},
|
||||
|
||||
addTaxes() {
|
||||
this.loadingOrderSummary = true;
|
||||
|
||||
$.ajax({
|
||||
method: "POST",
|
||||
url: route("cart.taxes.store"),
|
||||
data: this.form,
|
||||
})
|
||||
.then((cart) => {
|
||||
store.updateCart(cart);
|
||||
})
|
||||
.catch((xhr) => {
|
||||
this.$notify(xhr.responseJSON.message);
|
||||
})
|
||||
.always(() => {
|
||||
this.loadingOrderSummary = false;
|
||||
});
|
||||
},
|
||||
|
||||
placeOrder() {
|
||||
if (!this.form.terms_and_conditions || this.placingOrder) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.placingOrder = true;
|
||||
|
||||
$.ajax({
|
||||
method: "POST",
|
||||
url: route("checkout.create"),
|
||||
data: {
|
||||
...this.form,
|
||||
ship_to_a_different_address:
|
||||
+this.form.ship_to_a_different_address,
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.redirectUrl) {
|
||||
window.location.href = response.redirectUrl;
|
||||
} else if (this.form.payment_method === "stripe") {
|
||||
this.confirmStripePayment(response);
|
||||
} else if (this.form.payment_method === "paytm") {
|
||||
this.confirmPaytmPayment(response);
|
||||
} else if (this.form.payment_method === "razorpay") {
|
||||
this.confirmRazorpayPayment(response);
|
||||
} else if (this.form.payment_method === "paystack") {
|
||||
this.confirmPaystackPayment(response);
|
||||
} else if (this.form.payment_method === "authorizenet") {
|
||||
this.confirmAuthorizeNetPayment(response);
|
||||
} else if (this.form.payment_method === "flutterwave") {
|
||||
this.confirmFlutterWavePayment(response);
|
||||
} else if (this.form.payment_method === "mercadopago") {
|
||||
this.confirmMercadoPagoPayment(response);
|
||||
} else {
|
||||
this.confirmOrder(
|
||||
response.orderId,
|
||||
this.form.payment_method
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((xhr) => {
|
||||
if (xhr.status === 422) {
|
||||
this.errors.record(xhr.responseJSON.errors);
|
||||
}
|
||||
|
||||
this.$notify(xhr.responseJSON.message);
|
||||
|
||||
this.placingOrder = false;
|
||||
});
|
||||
},
|
||||
|
||||
confirmOrder(orderId, paymentMethod, params = {}) {
|
||||
$.ajax({
|
||||
method: "GET",
|
||||
url: route("checkout.complete.store", {
|
||||
orderId,
|
||||
paymentMethod,
|
||||
...params,
|
||||
}),
|
||||
})
|
||||
.then(() => {
|
||||
window.location.href = route("checkout.complete.show");
|
||||
})
|
||||
.catch((xhr) => {
|
||||
this.placingOrder = false;
|
||||
this.loadingOrderSummary = false;
|
||||
|
||||
this.deleteOrder(orderId);
|
||||
this.$notify(xhr.responseJSON.message);
|
||||
});
|
||||
},
|
||||
|
||||
deleteOrder(orderId) {
|
||||
if (!orderId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
method: "GET",
|
||||
url: route("checkout.payment_canceled.store", { orderId }),
|
||||
}).then((xhr) => {
|
||||
this.$notify(xhr.message);
|
||||
});
|
||||
},
|
||||
|
||||
renderPayPalButton() {
|
||||
let vm = this;
|
||||
let response;
|
||||
|
||||
window.paypal
|
||||
.Buttons({
|
||||
async createOrder() {
|
||||
try {
|
||||
response = await $.ajax({
|
||||
method: "POST",
|
||||
url: route("checkout.create"),
|
||||
data: vm.form,
|
||||
});
|
||||
|
||||
return response.resourceId;
|
||||
} catch (xhr) {
|
||||
if (xhr.status === 422) {
|
||||
vm.errors.record(xhr.responseJSON.errors);
|
||||
} else {
|
||||
vm.$notify(xhr.responseJSON.message);
|
||||
}
|
||||
}
|
||||
},
|
||||
onApprove() {
|
||||
vm.loadingOrderSummary = true;
|
||||
|
||||
vm.confirmOrder(response.orderId, "paypal", response);
|
||||
},
|
||||
onError() {
|
||||
vm.deleteOrder(response.orderId);
|
||||
},
|
||||
onCancel() {
|
||||
vm.deleteOrder(response.orderId);
|
||||
},
|
||||
})
|
||||
.render("#paypal-button-container");
|
||||
},
|
||||
|
||||
renderStripeElements() {
|
||||
this.stripeCardElement = this.stripe.elements().create("card", {
|
||||
hidePostalCode: true,
|
||||
});
|
||||
|
||||
this.stripeCardElement.mount("#stripe-card-element");
|
||||
},
|
||||
|
||||
async confirmStripePayment({ orderId, clientSecret }) {
|
||||
let result = await this.stripe.confirmCardPayment(clientSecret, {
|
||||
payment_method: {
|
||||
card: this.stripeCardElement,
|
||||
billing_details: {
|
||||
email: this.form.customer_email,
|
||||
name: `${this.form.billing.first_name} ${this.form.billing.last_name}`,
|
||||
address: {
|
||||
city: this.form.billing.city,
|
||||
country: this.form.billing.country,
|
||||
line1: this.form.billing.address_1,
|
||||
line2: this.form.billing.address_2,
|
||||
postal_code: this.form.billing.zip,
|
||||
state: this.form.billing.state,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
this.placingOrder = false;
|
||||
this.stripeError = result.error.message;
|
||||
|
||||
this.deleteOrder(orderId);
|
||||
} else {
|
||||
this.confirmOrder(orderId, "stripe", result);
|
||||
}
|
||||
},
|
||||
|
||||
confirmPaytmPayment({ orderId, amount, txnToken }) {
|
||||
let config = {
|
||||
root: "",
|
||||
flow: "DEFAULT",
|
||||
data: {
|
||||
orderId: orderId,
|
||||
token: txnToken,
|
||||
tokenType: "TXN_TOKEN",
|
||||
amount: amount,
|
||||
},
|
||||
merchant: {
|
||||
name: FleetCart.storeName,
|
||||
redirect: false,
|
||||
},
|
||||
handler: {
|
||||
transactionStatus: (response) => {
|
||||
if (response.STATUS === "TXN_SUCCESS") {
|
||||
this.confirmOrder(orderId, "paytm", response);
|
||||
} else if (response.STATUS === "TXN_FAILURE") {
|
||||
this.placingOrder = false;
|
||||
|
||||
this.deleteOrder(orderId);
|
||||
}
|
||||
|
||||
window.Paytm.CheckoutJS.close();
|
||||
},
|
||||
notifyMerchant: (eventName) => {
|
||||
if (eventName === "APP_CLOSED") {
|
||||
this.placingOrder = false;
|
||||
|
||||
this.deleteOrder(orderId);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
window.Paytm.CheckoutJS.init(config)
|
||||
.then(() => {
|
||||
window.Paytm.CheckoutJS.invoke();
|
||||
})
|
||||
.catch(() => {
|
||||
this.deleteOrder(orderId);
|
||||
});
|
||||
},
|
||||
|
||||
confirmRazorpayPayment(razorpayOrder) {
|
||||
this.placingOrder = false;
|
||||
|
||||
let vm = this;
|
||||
|
||||
new window.Razorpay({
|
||||
key: FleetCart.razorpayKeyId,
|
||||
name: FleetCart.storeName,
|
||||
description: `Payment for order #${razorpayOrder.receipt}`,
|
||||
image: FleetCart.storeLogo,
|
||||
order_id: razorpayOrder.id,
|
||||
handler(response) {
|
||||
vm.placingOrder = true;
|
||||
|
||||
vm.confirmOrder(
|
||||
razorpayOrder.receipt,
|
||||
"razorpay",
|
||||
response
|
||||
);
|
||||
},
|
||||
modal: {
|
||||
ondismiss() {
|
||||
vm.deleteOrder(razorpayOrder.receipt);
|
||||
},
|
||||
},
|
||||
prefill: {
|
||||
name: `${vm.form.billing.first_name} ${vm.form.billing.last_name}`,
|
||||
email: vm.form.customer_email,
|
||||
contact: vm.form.customer_phone,
|
||||
},
|
||||
}).open();
|
||||
},
|
||||
|
||||
confirmPaystackPayment({
|
||||
key,
|
||||
email,
|
||||
amount,
|
||||
ref,
|
||||
currency,
|
||||
order_id,
|
||||
}) {
|
||||
let vm = this;
|
||||
|
||||
PaystackPop.setup({
|
||||
key,
|
||||
email,
|
||||
amount,
|
||||
ref,
|
||||
currency,
|
||||
onClose() {
|
||||
vm.placingOrder = false;
|
||||
|
||||
vm.deleteOrder(order_id);
|
||||
},
|
||||
callback(response) {
|
||||
vm.placingOrder = false;
|
||||
|
||||
vm.confirmOrder(order_id, "paystack", response);
|
||||
},
|
||||
onBankTransferConfirmationPending(response) {
|
||||
vm.placingOrder = false;
|
||||
|
||||
vm.confirmOrder(order_id, "paystack", response);
|
||||
},
|
||||
}).openIframe();
|
||||
},
|
||||
|
||||
confirmAuthorizeNetPayment(authorizeNetOrder) {
|
||||
this.authorizeNetToken = authorizeNetOrder.token;
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$refs.authorizeNetForm.submit();
|
||||
|
||||
this.authorizeNetToken = null;
|
||||
});
|
||||
},
|
||||
|
||||
confirmFlutterWavePayment({
|
||||
public_key,
|
||||
tx_ref,
|
||||
order_id,
|
||||
amount,
|
||||
currency,
|
||||
payment_options,
|
||||
redirect_url,
|
||||
}) {
|
||||
let vm = this;
|
||||
|
||||
FlutterwaveCheckout({
|
||||
public_key,
|
||||
tx_ref,
|
||||
amount,
|
||||
currency,
|
||||
payment_options: payment_options.join(", "),
|
||||
redirect_url,
|
||||
customer: {
|
||||
email: this.form.customer_email,
|
||||
phone_number: this.form.customer_phone,
|
||||
name: this.form.billing.full_name,
|
||||
},
|
||||
customizations: {
|
||||
title: FleetCart.storeName,
|
||||
logo: FleetCart.storeLogo,
|
||||
},
|
||||
onclose(incomplete) {
|
||||
vm.placingOrder = false;
|
||||
|
||||
if (incomplete) {
|
||||
vm.deleteOrder(order_id);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
confirmMercadoPagoPayment(mercadoPagoOrder) {
|
||||
this.placingOrder = false;
|
||||
|
||||
const supportedLocales = {
|
||||
en_US: "en-US",
|
||||
es_AR: "es-AR",
|
||||
es_CL: "es-CL",
|
||||
es_CO: "es-CO",
|
||||
es_MX: "es-MX",
|
||||
es_VE: "es-VE",
|
||||
es_UY: "es-UY",
|
||||
es_PE: "es-PE",
|
||||
pt_BR: "pt-BR",
|
||||
};
|
||||
|
||||
const mercadoPago = new MercadoPago(mercadoPagoOrder.publicKey, {
|
||||
locale:
|
||||
supportedLocales[mercadoPagoOrder.currentLocale] || "en-US",
|
||||
});
|
||||
|
||||
mercadoPago.checkout({
|
||||
preference: {
|
||||
id: mercadoPagoOrder.preferenceId,
|
||||
},
|
||||
autoOpen: true,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,119 @@
|
||||
import store from '../../store';
|
||||
import ProductRating from '../ProductRating.vue';
|
||||
import ProductHelpersMixin from '../../mixins/ProductHelpersMixin';
|
||||
|
||||
export default {
|
||||
components: { ProductRating },
|
||||
|
||||
mixins: [
|
||||
ProductHelpersMixin,
|
||||
],
|
||||
|
||||
props: ['compare'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
products: this.compare.products,
|
||||
attributes: this.compare.attributes,
|
||||
fetchingRelatedProducts: false,
|
||||
relatedProducts: [],
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
hasAnyProduct() {
|
||||
return Object.keys(this.products).length !== 0;
|
||||
},
|
||||
|
||||
hasAnyRelatedProduct() {
|
||||
return this.relatedProducts.length !== 0;
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.hasAnyProduct) {
|
||||
this.fetchRelatedProducts();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
badgeClass(product) {
|
||||
if (product.is_in_stock) {
|
||||
return 'badge-success';
|
||||
}
|
||||
|
||||
return 'badge-danger';
|
||||
},
|
||||
|
||||
hasAttribute(product, attribute) {
|
||||
for (let productAttribute of product.attributes) {
|
||||
if (productAttribute.name === attribute.name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
attributeValues(product, attribute) {
|
||||
for (let productAttribute of product.attributes) {
|
||||
if (productAttribute.name === attribute.name) {
|
||||
return productAttribute.values.map((productAttributeValue) => {
|
||||
return productAttributeValue.value;
|
||||
}).join(', ');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
remove(product) {
|
||||
this.$delete(this.products, product.id);
|
||||
|
||||
if (! this.hasAnyProduct) {
|
||||
this.relatedProducts = [];
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
method: 'DELETE',
|
||||
url: route('compare.destroy', { id: product.id }),
|
||||
});
|
||||
},
|
||||
|
||||
inWishlist(product) {
|
||||
return store.inWishlist(product.id);
|
||||
},
|
||||
|
||||
syncWishlist(product) {
|
||||
store.syncWishlist(product.id);
|
||||
},
|
||||
|
||||
addToCart(product) {
|
||||
if (product.options_count !== 0) {
|
||||
return window.location.href = this.productUrl(product);
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: route('cart.items.store', { product_id: product.id, qty: 1 }),
|
||||
}).then((cart) => {
|
||||
store.updateCart(cart);
|
||||
|
||||
$('.header-cart').trigger('click');
|
||||
}).catch((xhr) => {
|
||||
this.$notify(xhr.responseJSON.message);
|
||||
});
|
||||
},
|
||||
|
||||
fetchRelatedProducts() {
|
||||
this.fetchingRelatedProducts = true;
|
||||
|
||||
$.ajax({
|
||||
method: 'GET',
|
||||
url: route('compare.related_products.index'),
|
||||
}).then((relatedProducts) => {
|
||||
this.relatedProducts = relatedProducts;
|
||||
}).catch((xhr) => {
|
||||
this.$notify(xhr.responseJSON.message);
|
||||
}).always(() => {
|
||||
this.fetchingRelatedProducts = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<section class="banner-wrap one-column-banner">
|
||||
<div class="container">
|
||||
<a
|
||||
:href="banner.call_to_action_url"
|
||||
class="banner"
|
||||
:target="banner.open_in_new_window ? '_blank' : '_self'"
|
||||
>
|
||||
<img :src="banner.image.path" alt="banner">
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['banner'],
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<section class="banner-wrap three-column-banner">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<a
|
||||
:href="data.banner_1.call_to_action_url"
|
||||
class="banner"
|
||||
:target="data.banner_1.open_in_new_window ? '_blank' : '_self'"
|
||||
>
|
||||
<img :src="data.banner_1.image.path" alt="banner">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<a
|
||||
:href="data.banner_2.call_to_action_url"
|
||||
class="banner"
|
||||
:target="data.banner_2.open_in_new_window ? '_blank' : '_self'"
|
||||
>
|
||||
<img :src="data.banner_2.image.path" alt="banner">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<a
|
||||
:href="data.banner_3.call_to_action_url"
|
||||
class="banner"
|
||||
:target="data.banner_3.open_in_new_window ? '_blank' : '_self'"
|
||||
>
|
||||
<img :src="data.banner_3.image.path" alt="banner">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['data'],
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<section
|
||||
class="banner-wrap three-column-full-width-banner padding-tb-75"
|
||||
:style="'background-image: url(' + data['background'].image.path + ')'"
|
||||
>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<a
|
||||
:href="data.banner_1.call_to_action_url"
|
||||
class="banner"
|
||||
:target="data.banner_1.open_in_new_window ? '_blank' : '_self'"
|
||||
>
|
||||
<img :src="data.banner_1.image.path" alt="banner">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-md-10">
|
||||
<a
|
||||
:href="data.banner_2.call_to_action_url"
|
||||
class="banner"
|
||||
:target="data.banner_2.open_in_new_window ? '_blank' : '_self'"
|
||||
>
|
||||
<img :src="data.banner_2.image.path" alt="banner">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<a
|
||||
:href="data.banner_3.call_to_action_url"
|
||||
class="banner"
|
||||
:target="data.banner_3.open_in_new_window ? '_blank' : '_self'"
|
||||
>
|
||||
<img :src="data.banner_3.image.path" alt="banner">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['data'],
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<section class="banner-wrap two-column-banner">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
<a
|
||||
:href="data.banner_1.call_to_action_url"
|
||||
class="banner"
|
||||
:target="data.banner_1.open_in_new_window ? '_blank' : '_self'"
|
||||
>
|
||||
<img :src="data.banner_1.image.path" alt="banner">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-md-9">
|
||||
<a
|
||||
:href="data.banner_2.call_to_action_url"
|
||||
class="banner"
|
||||
:target="data.banner_2.open_in_new_window ? '_blank' : '_self'"
|
||||
>
|
||||
<img :src="data.banner_2.image.path" alt="banner">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['data'],
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,27 @@
|
||||
export default {
|
||||
name: "DynamicTab",
|
||||
|
||||
props: ["label", "initialLogo", "url"],
|
||||
|
||||
data() {
|
||||
return {
|
||||
isActive: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
hasLogo() {
|
||||
return !Array.isArray(this.initialLogo);
|
||||
},
|
||||
|
||||
logo() {
|
||||
if (this.hasLogo) {
|
||||
return this.initialLogo.path;
|
||||
}
|
||||
|
||||
return `${window.FleetCart.baseUrl}/themes/storefront/public/images/image-placeholder.png`;
|
||||
},
|
||||
},
|
||||
|
||||
template: "<div></div>",
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<section class="featured-categories-wrap">
|
||||
<div class="container">
|
||||
<div class="featured-categories-header">
|
||||
<div class="featured-categories-text">
|
||||
<h2 class="title">{{ data.title }}</h2>
|
||||
<span class="excerpt">{{ data.subtitle }}</span>
|
||||
</div>
|
||||
|
||||
<ul class="tabs featured-categories-tabs">
|
||||
<li
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="index"
|
||||
:class="classes(tab)"
|
||||
@click="change(tab)"
|
||||
>
|
||||
<div class="featured-category-image">
|
||||
<img :src="tab.logo" :class="{ 'image-placeholder': ! tab.hasLogo }" alt="category logo">
|
||||
</div>
|
||||
|
||||
<span class="featured-category-name">{{ tab.label }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="tab-content featured-category-products">
|
||||
<ProductCard v-for="product in products" :key="product.id" :product="product"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dynamic-tab
|
||||
v-for="(category, index) in data.categories"
|
||||
:key="index"
|
||||
:label="category.name"
|
||||
:initial-logo="category.logo"
|
||||
:url="route('storefront.featured_category_products.index', { categoryNumber: index + 1 })"
|
||||
>
|
||||
</dynamic-tab>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProductCard from '../ProductCard.vue';
|
||||
import DynamicTabsMixin from '../../mixins/DynamicTabsMixin';
|
||||
import { slickPrevArrow, slickNextArrow } from '../../functions';
|
||||
|
||||
export default {
|
||||
components: { ProductCard },
|
||||
|
||||
mixins: [
|
||||
DynamicTabsMixin,
|
||||
],
|
||||
|
||||
props: ['data'],
|
||||
|
||||
methods: {
|
||||
selector() {
|
||||
return $('.featured-category-products');
|
||||
},
|
||||
|
||||
slickOptions() {
|
||||
return {
|
||||
rows: 0,
|
||||
dots: true,
|
||||
arrows: false,
|
||||
infinite: true,
|
||||
slidesToShow: 6,
|
||||
slidesToScroll: 6,
|
||||
rtl: window.FleetCart.rtl,
|
||||
prevArrow: slickPrevArrow(),
|
||||
nextArrow: slickNextArrow(),
|
||||
responsive: [
|
||||
{
|
||||
breakpoint: 1761,
|
||||
settings: {
|
||||
slidesToShow: 5,
|
||||
slidesToScroll: 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 1301,
|
||||
settings: {
|
||||
slidesToShow: 4,
|
||||
slidesToScroll: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 1051,
|
||||
settings: {
|
||||
slidesToShow: 3,
|
||||
slidesToScroll: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 992,
|
||||
settings: {
|
||||
slidesToShow: 4,
|
||||
slidesToScroll: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 881,
|
||||
settings: {
|
||||
slidesToShow: 3,
|
||||
slidesToScroll: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 641,
|
||||
settings: {
|
||||
slidesToShow: 2,
|
||||
slidesToScroll: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div class="col-xl-6 col-lg-18">
|
||||
<div class="daily-deals-wrap">
|
||||
<div class="daily-deals-header clearfix">
|
||||
<h4 class="section-title" v-html="title"></h4>
|
||||
</div>
|
||||
|
||||
<div class="daily-deals" ref="productsPlaceholder">
|
||||
<FlashSaleProductCard v-for="product in products" :key="product.id" :product="product"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FlashSaleProductCard from './FlashSaleProductCard.vue';
|
||||
import { slickPrevArrow, slickNextArrow } from '../../functions';
|
||||
|
||||
export default {
|
||||
components: { FlashSaleProductCard },
|
||||
|
||||
props: ['title', 'url'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
products: [],
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
$.ajax({
|
||||
method: 'GET',
|
||||
url: this.url,
|
||||
}).then((products) => {
|
||||
this.products = products;
|
||||
|
||||
this.$nextTick(() => {
|
||||
$(this.$refs.productsPlaceholder).slick(this.slickOptions());
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
slickOptions() {
|
||||
return {
|
||||
rows: 0,
|
||||
dots: false,
|
||||
arrows: true,
|
||||
infinite: true,
|
||||
slidesToShow: 1,
|
||||
slidesToScroll: 1,
|
||||
rtl: window.FleetCart.rtl,
|
||||
prevArrow: slickPrevArrow(),
|
||||
nextArrow: slickNextArrow(),
|
||||
responsive: [
|
||||
{
|
||||
breakpoint: 1200,
|
||||
settings: {
|
||||
slidesToShow: 2,
|
||||
slidesToScroll: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 768,
|
||||
settings: {
|
||||
slidesToShow: 1,
|
||||
slidesToScroll: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<section class="vertical-products-wrap">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<flash-sale
|
||||
:title="data.flash_sale_title"
|
||||
:url="route('storefront.flash_sale_products.index')"
|
||||
>
|
||||
</flash-sale>
|
||||
|
||||
<vertical-products
|
||||
:title="data.vertical_products_1_title"
|
||||
:url="route('storefront.vertical_products.index', { columnNumber: 1 })"
|
||||
>
|
||||
</vertical-products>
|
||||
|
||||
<vertical-products
|
||||
:title="data.vertical_products_2_title"
|
||||
:url="route('storefront.vertical_products.index', { columnNumber: 2 })"
|
||||
>
|
||||
</vertical-products>
|
||||
|
||||
<vertical-products
|
||||
:title="data.vertical_products_3_title"
|
||||
:url="route('storefront.vertical_products.index', { columnNumber: 3 })"
|
||||
>
|
||||
</vertical-products>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['data'],
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div class="daily-deals-inner">
|
||||
<div class="daily-deals-top">
|
||||
<a :href="productUrl" class="product-image">
|
||||
<img :src="baseImage" :class="{ 'image-placeholder': ! hasBaseImage }" alt="product-image">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<a :href="productUrl" class="product-name">
|
||||
<h6>{{ product.name }}</h6>
|
||||
</a>
|
||||
|
||||
<div class="product-info">
|
||||
<div class="product-price" v-html="product.formatted_price"></div>
|
||||
|
||||
<ProductRating :ratingPercent="product.rating_percent" :reviewCount="product.reviews.length"/>
|
||||
</div>
|
||||
|
||||
<div class="daily-deals-countdown clearfix"></div>
|
||||
|
||||
<div class="deal-progress">
|
||||
<div class="deal-stock">
|
||||
<div class="stock-available">
|
||||
{{ $trans('storefront::product_card.available') }}
|
||||
<span>{{ product.pivot.qty }}</span>
|
||||
</div>
|
||||
|
||||
<div class="stock-sold">
|
||||
{{ $trans('storefront::product_card.sold') }}
|
||||
<span>{{ product.pivot.sold }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="progress">
|
||||
<div class="progress-bar" :style="{ width: progress }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProductRating from '../ProductRating.vue';
|
||||
import ProductCardMixin from '../../mixins/ProductCardMixin';
|
||||
|
||||
export default {
|
||||
components: { ProductRating },
|
||||
|
||||
mixins: [
|
||||
ProductCardMixin,
|
||||
],
|
||||
|
||||
props: ['product'],
|
||||
|
||||
computed: {
|
||||
progress() {
|
||||
return (this.product.pivot.sold / this.product.pivot.qty * 100) + '%';
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
$(this.$el).find('.daily-deals-countdown').countdown({
|
||||
until: new Date(this.product.pivot.end_date),
|
||||
format: 'DHMS',
|
||||
labels: [
|
||||
this.$trans('storefront::product_card.years'),
|
||||
this.$trans('storefront::product_card.months'),
|
||||
this.$trans('storefront::product_card.weeks'),
|
||||
this.$trans('storefront::product_card.days'),
|
||||
this.$trans('storefront::product_card.hours'),
|
||||
this.$trans('storefront::product_card.minutes'),
|
||||
this.$trans('storefront::product_card.seconds'),
|
||||
],
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<section class="features-wrap">
|
||||
<div class="container">
|
||||
<div class="features">
|
||||
<div class="feature-list">
|
||||
<div class="single-feature" v-for="(feature, index) in features" :key="index">
|
||||
<div class="feature-icon">
|
||||
<i :class="feature.icon"></i>
|
||||
</div>
|
||||
|
||||
<div class="feature-details">
|
||||
<h6>{{ feature.title }}</h6>
|
||||
<span>{{ feature.subtitle }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['features'],
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<section class="grid-products-wrap clearfix">
|
||||
<div class="container">
|
||||
<div class="tab-products-header clearfix">
|
||||
<ul class="tabs float-left">
|
||||
<li
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="index"
|
||||
:class="classes(tab)"
|
||||
@click="change(tab)"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="tab-content grid-products">
|
||||
<div v-for="(productChunks, index) in $chunk(products, 12)" :key="index" class="grid-products-inner">
|
||||
<ProductCard v-for="product in productChunks" :key="product.id" :product="product"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dynamic-tab
|
||||
v-for="(tabLabel, index) in data"
|
||||
:key="index"
|
||||
:label="tabLabel"
|
||||
:url="route('storefront.product_grid.index', { tabNumber: index + 1 })"
|
||||
>
|
||||
</dynamic-tab>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProductCard from '../ProductCard.vue';
|
||||
import DynamicTabsMixin from '../../mixins/DynamicTabsMixin';
|
||||
import { slickPrevArrow, slickNextArrow } from '../../functions';
|
||||
|
||||
export default {
|
||||
components: { ProductCard },
|
||||
|
||||
mixins: [
|
||||
DynamicTabsMixin,
|
||||
],
|
||||
|
||||
props: ['data'],
|
||||
|
||||
methods: {
|
||||
selector() {
|
||||
return $('.grid-products');
|
||||
},
|
||||
|
||||
slickOptions() {
|
||||
return {
|
||||
rows: 0,
|
||||
dots: false,
|
||||
arrows: true,
|
||||
infinite: true,
|
||||
slidesToShow: 1,
|
||||
slidesToScroll: 1,
|
||||
rtl: window.FleetCart.rtl,
|
||||
prevArrow: slickPrevArrow(),
|
||||
nextArrow: slickNextArrow(),
|
||||
responsive: [
|
||||
{
|
||||
breakpoint: 768,
|
||||
settings: {
|
||||
dots: true,
|
||||
arrows: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<section class="landscape-tab-products-wrap clearfix">
|
||||
<div class="container">
|
||||
<div class="tab-products-header clearfix">
|
||||
<ul class="tabs float-left">
|
||||
<li
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="index"
|
||||
:class="classes(tab)"
|
||||
@click="change(tab)"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="tab-content landscape-left-tab-products">
|
||||
<ProductCard
|
||||
v-for="product in products"
|
||||
:key="product.id"
|
||||
:product="product"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<dynamic-tab
|
||||
v-for="(tabLabel, index) in data"
|
||||
:key="index"
|
||||
:label="tabLabel"
|
||||
:url="
|
||||
route('storefront.tab_products.index', {
|
||||
sectionNumber: 1,
|
||||
tabNumber: index + 1,
|
||||
})
|
||||
"
|
||||
>
|
||||
</dynamic-tab>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProductCard from "../ProductCard.vue";
|
||||
import DynamicTabsMixin from "../../mixins/DynamicTabsMixin";
|
||||
import { slickPrevArrow, slickNextArrow } from "../../functions";
|
||||
|
||||
export default {
|
||||
components: { ProductCard },
|
||||
|
||||
mixins: [DynamicTabsMixin],
|
||||
|
||||
props: ["data"],
|
||||
|
||||
methods: {
|
||||
selector() {
|
||||
return $(".landscape-left-tab-products");
|
||||
},
|
||||
|
||||
slickOptions() {
|
||||
return {
|
||||
rows: 0,
|
||||
dots: false,
|
||||
arrows: true,
|
||||
infinite: true,
|
||||
slidesToShow: 6,
|
||||
slidesToScroll: 6,
|
||||
rtl: window.FleetCart.rtl,
|
||||
prevArrow: slickPrevArrow(),
|
||||
nextArrow: slickNextArrow(),
|
||||
responsive: [
|
||||
{
|
||||
breakpoint: 1761,
|
||||
settings: {
|
||||
slidesToShow: 5,
|
||||
slidesToScroll: 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 1301,
|
||||
settings: {
|
||||
slidesToShow: 4,
|
||||
slidesToScroll: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 1051,
|
||||
settings: {
|
||||
slidesToShow: 3,
|
||||
slidesToScroll: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 992,
|
||||
settings: {
|
||||
slidesToShow: 4,
|
||||
slidesToScroll: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 881,
|
||||
settings: {
|
||||
slidesToShow: 3,
|
||||
slidesToScroll: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 661,
|
||||
settings: {
|
||||
dots: true,
|
||||
arrows: false,
|
||||
slidesToShow: 3,
|
||||
slidesToScroll: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 641,
|
||||
settings: {
|
||||
dots: true,
|
||||
arrows: false,
|
||||
slidesToShow: 2,
|
||||
slidesToScroll: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<section class="landscape-tab-products-wrap clearfix">
|
||||
<div class="container">
|
||||
<div class="tab-products-header clearfix">
|
||||
<h5 class="section-title float-left">{{ data.title }}</h5>
|
||||
|
||||
<ul class="tabs float-right">
|
||||
<li
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="index"
|
||||
:class="classes(tab)"
|
||||
@click="change(tab)"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="tab-content landscape-right-tab-products">
|
||||
<ProductCard v-for="product in products" :key="product.id" :product="product"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dynamic-tab
|
||||
v-for="(tabLabel, index) in data.tabs"
|
||||
:key="index"
|
||||
:label="tabLabel"
|
||||
:url="route('storefront.tab_products.index', {sectionNumber: 2, tabNumber: index + 1 })"
|
||||
>
|
||||
</dynamic-tab>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProductCard from '../ProductCard.vue';
|
||||
import DynamicTabsMixin from '../../mixins/DynamicTabsMixin';
|
||||
|
||||
export default {
|
||||
components: { ProductCard },
|
||||
|
||||
mixins: [DynamicTabsMixin],
|
||||
|
||||
props: ['data'],
|
||||
|
||||
methods: {
|
||||
selector() {
|
||||
return $('.landscape-right-tab-products');
|
||||
},
|
||||
|
||||
slickOptions() {
|
||||
return {
|
||||
rows: 0,
|
||||
dots: true,
|
||||
arrows: false,
|
||||
infinite: true,
|
||||
slidesToShow: 6,
|
||||
slidesToScroll: 6,
|
||||
rtl: window.FleetCart.rtl,
|
||||
responsive: [
|
||||
{
|
||||
breakpoint: 1761,
|
||||
settings: {
|
||||
slidesToShow: 5,
|
||||
slidesToScroll: 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 1301,
|
||||
settings: {
|
||||
slidesToShow: 4,
|
||||
slidesToScroll: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 1051,
|
||||
settings: {
|
||||
slidesToShow: 3,
|
||||
slidesToScroll: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 992,
|
||||
settings: {
|
||||
slidesToShow: 4,
|
||||
slidesToScroll: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 881,
|
||||
settings: {
|
||||
slidesToShow: 3,
|
||||
slidesToScroll: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 641,
|
||||
settings: {
|
||||
slidesToShow: 2,
|
||||
slidesToScroll: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<section class="top-brands-wrap clearfix">
|
||||
<div class="container">
|
||||
<div class="top-brands clearfix">
|
||||
<a
|
||||
v-for="(topBrand, index) in topBrands"
|
||||
:key="index"
|
||||
:href="topBrand.url"
|
||||
class="top-brand-image"
|
||||
>
|
||||
<img :src="topBrand.logo.path" alt="brand logo">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['topBrands'],
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="col-xl-4 col-lg-6">
|
||||
<div class="vertical-products">
|
||||
<div class="vertical-products-header">
|
||||
<h4 class="section-title">{{ title }}</h4>
|
||||
</div>
|
||||
|
||||
<div class="vertical-products-slider" ref="productsPlaceholder">
|
||||
<div v-for="(productChunks, index) in $chunk(products, 5)" :key="index" class="vertical-products-slide">
|
||||
<ProductCardVertical v-for="product in productChunks" :key="product.id" :product="product"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProductCardVertical from '../ProductCardVertical.vue';
|
||||
|
||||
export default {
|
||||
components: { ProductCardVertical },
|
||||
|
||||
props: ['title', 'url'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
products: [],
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
$.ajax({
|
||||
method: 'GET',
|
||||
url: this.url,
|
||||
}).then((products) => {
|
||||
this.products = products;
|
||||
|
||||
this.$nextTick(() => {
|
||||
$(this.$refs.productsPlaceholder).slick(this.slickOptions());
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
slickOptions() {
|
||||
return {
|
||||
rows: 0,
|
||||
dots: false,
|
||||
arrows: true,
|
||||
infinite: true,
|
||||
slidesToShow: 1,
|
||||
slidesToScroll: 1,
|
||||
rtl: window.FleetCart.rtl,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,24 @@
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
setTimeout(() => {
|
||||
this.show = true;
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
accept() {
|
||||
this.show = false;
|
||||
|
||||
$.ajax({
|
||||
method: 'DELETE',
|
||||
url: route('storefront.cookie_bar.destroy'),
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,452 @@
|
||||
<template>
|
||||
<div class="header-search-wrap" v-click-outside="hideSuggestions">
|
||||
<div class="header-search">
|
||||
<form class="search-form" @submit.prevent="search">
|
||||
<div class="header-search-lg">
|
||||
<input
|
||||
type="text"
|
||||
name="query"
|
||||
class="form-control search-input"
|
||||
autocomplete="off"
|
||||
v-model="form.query"
|
||||
:placeholder="
|
||||
$trans('storefront::layout.search_for_products')
|
||||
"
|
||||
@keydown.esc="hideSuggestions"
|
||||
@keydown.down="nextSuggestion"
|
||||
@keydown.up="prevSuggestion"
|
||||
/>
|
||||
|
||||
<div class="header-search-right" @focusin="hideSuggestions">
|
||||
<select
|
||||
name="category"
|
||||
class="header-search-select custom-select-option arrow-black"
|
||||
@nice-select-updated="
|
||||
changeCategory($event.target.value)
|
||||
"
|
||||
>
|
||||
<option
|
||||
value=""
|
||||
:selected="initialCategoryIsNotInCategoryList"
|
||||
>
|
||||
{{
|
||||
$trans("storefront::layout.all_categories")
|
||||
}}
|
||||
</option>
|
||||
|
||||
<option
|
||||
v-for="category in categories"
|
||||
:key="category.slug"
|
||||
:value="category.slug"
|
||||
:selected="category.slug === initialCategory"
|
||||
>
|
||||
{{ category.name }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-search"
|
||||
>
|
||||
<i class="las la-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-search-sm">
|
||||
<i class="las la-search"></i>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div
|
||||
v-if="
|
||||
Boolean(isMostSearchedKeywordsEnabled) &&
|
||||
mostSearchedKeywords.length !== 0
|
||||
"
|
||||
class="searched-keywords"
|
||||
>
|
||||
<label>
|
||||
{{ $trans("storefront::layout.most_searched") }}
|
||||
</label>
|
||||
|
||||
<ul class="list-inline searched-keywords-list">
|
||||
<li
|
||||
v-for="(
|
||||
mostSearchedKeyword, index
|
||||
) in mostSearchedKeywords"
|
||||
:key="index"
|
||||
>
|
||||
<a
|
||||
:href="
|
||||
route('products.index', {
|
||||
query: mostSearchedKeyword,
|
||||
})
|
||||
"
|
||||
>
|
||||
{{ mostSearchedKeyword }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-search-sm-form">
|
||||
<form class="search-form" @submit.prevent="search">
|
||||
<div class="btn-close">
|
||||
<i class="las la-arrow-left"></i>
|
||||
</div>
|
||||
|
||||
<!-- Cannot use v-model due to a bug. See https://github.com/vuejs/vue/issues/8231 -->
|
||||
<input
|
||||
type="text"
|
||||
name="query"
|
||||
class="form-control search-input-sm"
|
||||
autocomplete="off"
|
||||
:placeholder="
|
||||
$trans('storefront::layout.search_for_products')
|
||||
"
|
||||
:value="form.query"
|
||||
@input="form.query = $event.target.value"
|
||||
@keydown.esc="hideSuggestions"
|
||||
@keydown.down="nextSuggestion"
|
||||
@keydown.up="prevSuggestion"
|
||||
/>
|
||||
|
||||
<button type="submit" class="btn btn-search">
|
||||
<i class="las la-search"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="search-suggestions" v-if="shouldShowSuggestions">
|
||||
<div
|
||||
class="search-suggestions-inner custom-scrollbar"
|
||||
ref="searchSuggestionsInner"
|
||||
>
|
||||
<div
|
||||
class="category-suggestion"
|
||||
v-if="suggestions.categories.length !== 0"
|
||||
>
|
||||
<h6 class="title">
|
||||
{{ $trans("storefront::layout.category_suggestions") }}
|
||||
</h6>
|
||||
|
||||
<ul class="list-inline category-suggestion-list">
|
||||
<li
|
||||
v-for="category in suggestions.categories"
|
||||
:key="category.slug"
|
||||
class="list-item"
|
||||
:class="{ active: isActiveSuggestion(category) }"
|
||||
:ref="category.slug"
|
||||
@mouseover="changeActiveSuggestion(category)"
|
||||
@mouseleave="clearActiveSuggestion"
|
||||
>
|
||||
<a
|
||||
:href="category.url"
|
||||
class="single-item"
|
||||
v-text="category.name"
|
||||
@click="hideSuggestions"
|
||||
>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="product-suggestion">
|
||||
<h6 class="title">
|
||||
{{ $trans("storefront::layout.product_suggestions") }}
|
||||
</h6>
|
||||
|
||||
<ul class="list-inline product-suggestion-list">
|
||||
<li
|
||||
v-for="product in suggestions.products"
|
||||
:key="product.slug"
|
||||
class="list-item"
|
||||
:class="{ active: isActiveSuggestion(product) }"
|
||||
:ref="product.slug"
|
||||
@mouseover="changeActiveSuggestion(product)"
|
||||
@mouseleave="clearActiveSuggestion"
|
||||
>
|
||||
<a
|
||||
:href="product.url"
|
||||
class="single-item"
|
||||
@click="hideSuggestions"
|
||||
>
|
||||
<div class="product-image">
|
||||
<img
|
||||
:src="baseImage(product)"
|
||||
:class="{
|
||||
'image-placeholder':
|
||||
!hasBaseImage(product),
|
||||
}"
|
||||
alt="product image"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="product-info">
|
||||
<div class="product-info-top">
|
||||
<h6
|
||||
class="product-name"
|
||||
v-html="product.name"
|
||||
></h6>
|
||||
|
||||
<ul
|
||||
class="list-inline product-badge"
|
||||
v-if="product.is_out_of_stock"
|
||||
>
|
||||
<li class="badge badge-danger">
|
||||
{{
|
||||
$trans(
|
||||
"storefront::product_card.out_of_stock"
|
||||
)
|
||||
}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="product-price"
|
||||
v-html="product.formatted_price"
|
||||
></div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<a
|
||||
v-if="suggestions.remaining !== 0"
|
||||
:href="moreResultsUrl"
|
||||
@click="hideSuggestions"
|
||||
class="more-results"
|
||||
>
|
||||
{{
|
||||
$trans("storefront::layout.more_results", {
|
||||
count: this.suggestions.remaining,
|
||||
})
|
||||
}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProductHelpersMixin from "../../mixins/ProductHelpersMixin";
|
||||
|
||||
export default {
|
||||
mixins: [ProductHelpersMixin],
|
||||
|
||||
props: [
|
||||
"categories",
|
||||
"mostSearchedKeywords",
|
||||
"isMostSearchedKeywordsEnabled",
|
||||
"initialQuery",
|
||||
"initialCategory",
|
||||
],
|
||||
|
||||
data() {
|
||||
return {
|
||||
activeSuggestion: null,
|
||||
showSuggestions: false,
|
||||
form: {
|
||||
query: this.initialQuery,
|
||||
category: this.initialCategory,
|
||||
},
|
||||
suggestions: {
|
||||
categories: [],
|
||||
products: [],
|
||||
remaining: 0,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
initialCategoryIsNotInCategoryList() {
|
||||
return !this.categories.includes(this.initialCategory);
|
||||
},
|
||||
|
||||
shouldShowSuggestions() {
|
||||
if (!this.showSuggestions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.hasAnySuggestion;
|
||||
},
|
||||
|
||||
moreResultsUrl() {
|
||||
if (this.form.category) {
|
||||
return route("categories.products.index", this.form);
|
||||
}
|
||||
|
||||
return route("products.index", { query: this.form.query });
|
||||
},
|
||||
|
||||
hasAnySuggestion() {
|
||||
return this.suggestions.products.length !== 0;
|
||||
},
|
||||
|
||||
allSuggestions() {
|
||||
return [
|
||||
...this.suggestions.categories,
|
||||
...this.suggestions.products,
|
||||
];
|
||||
},
|
||||
|
||||
firstSuggestion() {
|
||||
return this.allSuggestions[0];
|
||||
},
|
||||
|
||||
lastSuggestion() {
|
||||
return this.allSuggestions[this.allSuggestions.length - 1];
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
"form.query": function (newQuery) {
|
||||
if (newQuery === "") {
|
||||
this.clearSuggestions();
|
||||
} else {
|
||||
this.showSuggestions = true;
|
||||
|
||||
this.fetchSuggestions();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
changeCategory(category) {
|
||||
this.form.category = category;
|
||||
|
||||
this.fetchSuggestions();
|
||||
},
|
||||
|
||||
fetchSuggestions() {
|
||||
$.ajax({
|
||||
method: "GET",
|
||||
url: route("suggestions.index", this.form),
|
||||
}).then((suggestions) => {
|
||||
this.suggestions.categories = suggestions.categories;
|
||||
this.suggestions.products = suggestions.products;
|
||||
this.suggestions.remaining = suggestions.remaining;
|
||||
|
||||
this.clearActiveSuggestion();
|
||||
this.resetSuggestionScrollBar();
|
||||
});
|
||||
},
|
||||
|
||||
search() {
|
||||
if (!this.form.query) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.activeSuggestion) {
|
||||
window.location.href = this.activeSuggestion.url;
|
||||
|
||||
this.hideSuggestions();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.form.category) {
|
||||
window.location.href = route(
|
||||
"categories.products.index",
|
||||
this.form
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = route("products.index", {
|
||||
query: this.form.query,
|
||||
});
|
||||
},
|
||||
|
||||
clearSuggestions() {
|
||||
this.suggestions.categories = [];
|
||||
this.suggestions.products = [];
|
||||
},
|
||||
|
||||
hideSuggestions(e) {
|
||||
this.showSuggestions = false;
|
||||
|
||||
this.clearActiveSuggestion();
|
||||
},
|
||||
|
||||
isActiveSuggestion(suggestion) {
|
||||
if (!this.activeSuggestion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.activeSuggestion.slug === suggestion.slug;
|
||||
},
|
||||
|
||||
changeActiveSuggestion(suggestion) {
|
||||
this.activeSuggestion = suggestion;
|
||||
},
|
||||
|
||||
clearActiveSuggestion() {
|
||||
this.activeSuggestion = null;
|
||||
},
|
||||
|
||||
nextSuggestion() {
|
||||
if (!this.hasAnySuggestion) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.activeSuggestion =
|
||||
this.allSuggestions[this.nextSuggestionIndex()];
|
||||
|
||||
if (!this.activeSuggestion) {
|
||||
this.activeSuggestion = this.firstSuggestion;
|
||||
}
|
||||
|
||||
this.adjustSuggestionScrollBar();
|
||||
},
|
||||
|
||||
prevSuggestion() {
|
||||
if (!this.hasAnySuggestion) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.prevSuggestionIndex() === -1) {
|
||||
this.clearActiveSuggestion();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.activeSuggestion =
|
||||
this.allSuggestions[this.prevSuggestionIndex()];
|
||||
|
||||
if (!this.activeSuggestion) {
|
||||
this.activeSuggestion = this.lastSuggestion;
|
||||
}
|
||||
|
||||
this.adjustSuggestionScrollBar();
|
||||
},
|
||||
|
||||
nextSuggestionIndex() {
|
||||
return this.currentSuggestionIndex() + 1;
|
||||
},
|
||||
|
||||
prevSuggestionIndex() {
|
||||
return this.currentSuggestionIndex() - 1;
|
||||
},
|
||||
|
||||
currentSuggestionIndex() {
|
||||
return this.allSuggestions.indexOf(this.activeSuggestion);
|
||||
},
|
||||
|
||||
adjustSuggestionScrollBar() {
|
||||
this.$refs.searchSuggestionsInner.scrollTop =
|
||||
this.$refs[this.activeSuggestion.slug][0].offsetTop - 200;
|
||||
},
|
||||
|
||||
resetSuggestionScrollBar() {
|
||||
if (this.$refs.searchSuggestionsInner !== undefined) {
|
||||
this.$refs.searchSuggestionsInner.scrollTop = 0;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,72 @@
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
email: '',
|
||||
subscribed: false,
|
||||
subscribing: false,
|
||||
error: '',
|
||||
disable_popup: false,
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
email() {
|
||||
this.error = '';
|
||||
},
|
||||
|
||||
disable_popup(bool) {
|
||||
if (bool) {
|
||||
this.disableNewsletterPopup();
|
||||
} else {
|
||||
this.enableNewsletterPopup();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
setTimeout(() => {
|
||||
$('.newsletter-wrap').modal('show');
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
methods: {
|
||||
enableNewsletterPopup() {
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: route('storefront.newsletter_popup.store'),
|
||||
});
|
||||
},
|
||||
|
||||
disableNewsletterPopup() {
|
||||
$.ajax({
|
||||
method: 'DELETE',
|
||||
url: route('storefront.newsletter_popup.destroy'),
|
||||
});
|
||||
},
|
||||
|
||||
subscribe() {
|
||||
if (! this.email || this.subscribed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.subscribing = true;
|
||||
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: route('subscribers.store'),
|
||||
data: { email: this.email },
|
||||
}).then(() => {
|
||||
this.email = '';
|
||||
this.subscribed = true;
|
||||
}).catch((response) => {
|
||||
if (response.status === 422) {
|
||||
this.error = response.responseJSON.errors.email[0];
|
||||
} else {
|
||||
this.error = response.responseJSON.message;
|
||||
}
|
||||
}).always(() => {
|
||||
this.subscribing = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
email: '',
|
||||
subscribed: false,
|
||||
subscribing: false,
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
subscribe() {
|
||||
if (! this.email || this.subscribed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.subscribing = true;
|
||||
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: route('subscribers.store'),
|
||||
data: { email: this.email },
|
||||
}).then(() => {
|
||||
this.email = '';
|
||||
this.subscribed = true;
|
||||
}).catch((response) => {
|
||||
if (response.status === 422) {
|
||||
this.$notify(response.responseJSON.errors.email[0]);
|
||||
} else {
|
||||
this.$notify(response.responseJSON.message);
|
||||
}
|
||||
}).always(() => {
|
||||
this.subscribing = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import store from '../../store';
|
||||
import SidebarCartItem from './SidebarCartItem.vue';
|
||||
|
||||
export default {
|
||||
components: { SidebarCartItem },
|
||||
|
||||
computed: {
|
||||
cart() {
|
||||
return store.state.cart;
|
||||
},
|
||||
|
||||
cartIsEmpty() {
|
||||
return store.cartIsEmpty();
|
||||
},
|
||||
|
||||
cartIsNotEmpty() {
|
||||
return ! store.cartIsEmpty();
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div class="sidebar-cart-item">
|
||||
<a :href="productUrl(cartItem.product)" class="product-image">
|
||||
<img
|
||||
:src="baseImage(cartItem.product)"
|
||||
:class="{ 'image-placeholder': ! hasBaseImage(cartItem.product) }"
|
||||
alt="product image"
|
||||
>
|
||||
</a>
|
||||
|
||||
<div class="product-info">
|
||||
<a :href="productUrl(cartItem.product)" class="product-name" :title="cartItem.product.name">
|
||||
{{ cartItem.product.name }}
|
||||
</a>
|
||||
|
||||
<ul class="list-inline product-options">
|
||||
<li v-for="option in cartItem.options" :key="option.id">
|
||||
<label>{{ option.name }}:</label> {{ optionValues(option) }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="product-quantity">
|
||||
{{ cartItem.qty }} x <span v-html="cartItem.unitPrice.inCurrentCurrency.formatted"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="remove-cart-item">
|
||||
<button class="btn-remove" @click="remove">
|
||||
<i class="las la-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import store from '../../store';
|
||||
import ProductHelpersMixin from '../../mixins/ProductHelpersMixin';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
ProductHelpersMixin,
|
||||
],
|
||||
|
||||
props: ['cartItem'],
|
||||
|
||||
methods: {
|
||||
optionValues(option) {
|
||||
let values = [];
|
||||
|
||||
for (let value of option.values) {
|
||||
values.push(value.label);
|
||||
}
|
||||
|
||||
return values.join(', ');
|
||||
},
|
||||
|
||||
remove() {
|
||||
store.removeCartItem(this.cartItem);
|
||||
|
||||
$.ajax({
|
||||
method: 'DELETE',
|
||||
url: route('cart.items.destroy', { cartItemId: this.cartItem.id }),
|
||||
}).then((cart) => {
|
||||
store.updateCart(cart);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,188 @@
|
||||
import noUiSlider from "nouislider";
|
||||
import { trans } from "../../functions";
|
||||
import { collapseFilters } from "./index/helpers";
|
||||
|
||||
export default {
|
||||
props: [
|
||||
"initialQuery",
|
||||
"initialBrandName",
|
||||
"initialBrandBanner",
|
||||
"initialBrandSlug",
|
||||
"initialCategoryName",
|
||||
"initialCategoryBanner",
|
||||
"initialCategorySlug",
|
||||
"initialTagName",
|
||||
"initialTagSlug",
|
||||
"initialAttribute",
|
||||
"maxPrice",
|
||||
"initialSort",
|
||||
"initialPerPage",
|
||||
"initialPage",
|
||||
"initialViewMode"
|
||||
],
|
||||
|
||||
data() {
|
||||
return {
|
||||
fetchingProducts: false,
|
||||
products: { data: [] },
|
||||
attributeFilters: [],
|
||||
brandBanner: this.initialBrandBanner,
|
||||
categoryName: this.initialCategoryName,
|
||||
categoryBanner: this.initialCategoryBanner,
|
||||
viewMode: this.initialViewMode,
|
||||
queryParams: {
|
||||
query: this.initialQuery,
|
||||
brand: this.initialBrandSlug,
|
||||
category: this.initialCategorySlug,
|
||||
tag: this.initialTagSlug,
|
||||
attribute: this.initialAttribute,
|
||||
fromPrice: 0,
|
||||
toPrice: this.maxPrice,
|
||||
sort: this.initialSort,
|
||||
perPage: this.initialPerPage,
|
||||
page: this.initialPage
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
emptyProducts() {
|
||||
return this.products.data.length === 0;
|
||||
},
|
||||
|
||||
totalPage() {
|
||||
return Math.ceil(this.products.total / this.queryParams.perPage);
|
||||
},
|
||||
|
||||
showingResults() {
|
||||
if (this.emptyProducts) {
|
||||
return;
|
||||
}
|
||||
|
||||
return trans("storefront::products.showing_results", {
|
||||
from: this.products.from,
|
||||
to: this.products.to,
|
||||
total: this.products.total
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.addEventListeners();
|
||||
this.initPriceFilter();
|
||||
this.fetchProducts();
|
||||
},
|
||||
|
||||
methods: {
|
||||
addEventListeners() {
|
||||
$(this.$refs.sortSelect).on("change", e => {
|
||||
this.queryParams.sort = e.currentTarget.value;
|
||||
|
||||
this.fetchProducts();
|
||||
});
|
||||
|
||||
$(this.$refs.perPageSelect).on("change", e => {
|
||||
this.queryParams.perPage = e.currentTarget.value;
|
||||
|
||||
this.fetchProducts();
|
||||
});
|
||||
},
|
||||
|
||||
initPriceFilter() {
|
||||
noUiSlider.create(this.$refs.priceRange, {
|
||||
connect: true,
|
||||
direction: window.FleetCart.rtl ? "rtl" : "ltr",
|
||||
start: [0, this.maxPrice],
|
||||
range: {
|
||||
min: [0],
|
||||
max: [this.maxPrice]
|
||||
}
|
||||
});
|
||||
|
||||
this.$refs.priceRange.noUiSlider.on("update", (values, handle) => {
|
||||
let value = Math.round(values[handle]);
|
||||
|
||||
if (handle === 0) {
|
||||
this.queryParams.fromPrice = value;
|
||||
} else {
|
||||
this.queryParams.toPrice = value;
|
||||
}
|
||||
});
|
||||
|
||||
this.$refs.priceRange.noUiSlider.on("change", this.fetchProducts);
|
||||
},
|
||||
|
||||
updatePriceRange(fromPrice, toPrice) {
|
||||
this.$refs.priceRange.noUiSlider.set([fromPrice, toPrice]);
|
||||
|
||||
this.fetchProducts();
|
||||
},
|
||||
|
||||
toggleAttributeFilter(slug, value) {
|
||||
if (!this.queryParams.attribute.hasOwnProperty(slug)) {
|
||||
this.queryParams.attribute[slug] = [];
|
||||
}
|
||||
|
||||
if (this.queryParams.attribute[slug].includes(value)) {
|
||||
this.queryParams.attribute[slug].splice(
|
||||
this.queryParams.attribute[slug].indexOf(value),
|
||||
1
|
||||
);
|
||||
} else {
|
||||
this.queryParams.attribute[slug].push(value);
|
||||
}
|
||||
|
||||
this.fetchProducts({ updateAttributeFilters: false });
|
||||
},
|
||||
|
||||
isFilteredByAttribute(slug, value) {
|
||||
if (!this.queryParams.attribute.hasOwnProperty(slug)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.queryParams.attribute[slug].includes(value);
|
||||
},
|
||||
|
||||
changeCategory(category) {
|
||||
this.categoryName = category.name;
|
||||
this.categoryBanner = category.banner.path;
|
||||
this.queryParams.query = null;
|
||||
this.queryParams.category = category.slug;
|
||||
this.queryParams.attribute = {};
|
||||
this.queryParams.page = 1;
|
||||
|
||||
this.fetchProducts();
|
||||
},
|
||||
|
||||
changePage(page) {
|
||||
this.queryParams.page = page;
|
||||
|
||||
this.fetchProducts();
|
||||
},
|
||||
|
||||
fetchProducts(options = { updateAttributeFilters: true }) {
|
||||
this.fetchingProducts = true;
|
||||
|
||||
$.ajax({
|
||||
url: route("products.index", this.queryParams)
|
||||
})
|
||||
.then(response => {
|
||||
this.products = response.products;
|
||||
|
||||
if (options.updateAttributeFilters) {
|
||||
this.attributeFilters = response.attributes;
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
collapseFilters();
|
||||
});
|
||||
})
|
||||
.catch(xhr => {
|
||||
this.$notify(xhr.responseJSON.message);
|
||||
})
|
||||
.always(() => {
|
||||
this.fetchingProducts = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,234 @@
|
||||
import store from '../../store';
|
||||
import Errors from '../../Errors';
|
||||
import ProductRating from '../ProductRating.vue';
|
||||
import ProductCardMixin from '../../mixins/ProductCardMixin';
|
||||
import RelatedProducts from './show/RelatedProducts.vue';
|
||||
|
||||
export default {
|
||||
components: { ProductRating, RelatedProducts },
|
||||
|
||||
mixins: [
|
||||
ProductCardMixin,
|
||||
],
|
||||
|
||||
props: ['product', 'reviewCount', 'avgRating'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
price: this.product.formatted_price,
|
||||
activeTab: 'description',
|
||||
currentReviewPage: 1,
|
||||
fetchingReviews: false,
|
||||
reviews: {},
|
||||
addingNewReview: false,
|
||||
reviewForm: {},
|
||||
cartItemForm: {
|
||||
product_id: this.product.id,
|
||||
qty: 1,
|
||||
options: {},
|
||||
},
|
||||
errors: new Errors(),
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
totalReviews() {
|
||||
if (! this.reviews.total) {
|
||||
return this.reviewCount;
|
||||
}
|
||||
|
||||
return this.reviews.total;
|
||||
},
|
||||
|
||||
ratingPercent() {
|
||||
return (this.avgRating / 5) * 100;
|
||||
},
|
||||
|
||||
emptyReviews() {
|
||||
return this.totalReviews === 0;
|
||||
},
|
||||
|
||||
totalReviewPage() {
|
||||
return Math.ceil(this.reviews.total / 4);
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.fetchReviews();
|
||||
},
|
||||
|
||||
mounted() {
|
||||
$(this.$refs.upSellProducts).slick({
|
||||
rows: 0,
|
||||
dots: false,
|
||||
arrows: true,
|
||||
infinite: true,
|
||||
slidesToShow: 1,
|
||||
slidesToScroll: 1,
|
||||
rtl: window.FleetCart.rtl,
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateQuantity(qty) {
|
||||
if (qty < 1 || this.exceedsMaxStock(qty)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNaN(qty)) {
|
||||
qty = 1;
|
||||
}
|
||||
|
||||
this.cartItemForm.qty = qty;
|
||||
},
|
||||
|
||||
exceedsMaxStock(qty) {
|
||||
return this.product.manage_stock && this.product.qty < qty;
|
||||
},
|
||||
|
||||
changeReviewPage(page) {
|
||||
this.currentReviewPage = page;
|
||||
|
||||
this.fetchReviews();
|
||||
},
|
||||
|
||||
updatePrice() {
|
||||
this.$nextTick(() => {
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: route('products.price.show', { id: this.product.id }),
|
||||
data: { options: this.cartItemForm.options },
|
||||
}).then((price) => {
|
||||
this.price = price;
|
||||
}).catch((xhr) => {
|
||||
this.$notify(xhr.responseJSON.message);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
updateSelectTypeOptionValue(optionId, e) {
|
||||
this.$set(this.cartItemForm.options, optionId, $(e.target).val());
|
||||
|
||||
this.errors.clear(`options.${optionId}`);
|
||||
},
|
||||
|
||||
updateCheckboxTypeOptionValue(optionId, e) {
|
||||
let values = $(e.target)
|
||||
.parents('.variant-check')
|
||||
.find('input[type="checkbox"]:checked')
|
||||
.map((_, el) => {
|
||||
return el.value;
|
||||
});
|
||||
|
||||
this.$set(this.cartItemForm.options, optionId, values.get());
|
||||
},
|
||||
|
||||
customRadioTypeOptionValueIsActive(optionId, valueId) {
|
||||
if (! this.cartItemForm.options.hasOwnProperty(optionId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.cartItemForm.options[optionId] === valueId;
|
||||
},
|
||||
|
||||
syncCustomRadioTypeOptionValue(optionId, valueId) {
|
||||
if (this.customRadioTypeOptionValueIsActive(optionId, valueId)) {
|
||||
this.$delete(this.cartItemForm.options, optionId);
|
||||
} else {
|
||||
this.$set(this.cartItemForm.options, optionId, valueId);
|
||||
|
||||
this.errors.clear(`options.${optionId}`);
|
||||
}
|
||||
|
||||
this.updatePrice();
|
||||
},
|
||||
|
||||
customCheckboxTypeOptionValueIsActive(optionId, valueId) {
|
||||
if (! this.cartItemForm.options.hasOwnProperty(optionId)) {
|
||||
this.$set(this.cartItemForm.options, optionId, []);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.cartItemForm.options[optionId].includes(valueId);
|
||||
},
|
||||
|
||||
syncCustomCheckboxTypeOptionValue(optionId, valueId) {
|
||||
if (this.customCheckboxTypeOptionValueIsActive(optionId, valueId)) {
|
||||
this.cartItemForm.options[optionId].splice(
|
||||
this.cartItemForm.options[optionId].indexOf(valueId),
|
||||
1
|
||||
);
|
||||
} else {
|
||||
this.cartItemForm.options[optionId].push(valueId);
|
||||
|
||||
this.errors.clear(`options.${optionId}`);
|
||||
}
|
||||
|
||||
this.updatePrice();
|
||||
},
|
||||
|
||||
fetchReviews() {
|
||||
this.fetchingReviews = true;
|
||||
|
||||
$.ajax({
|
||||
method: 'GET',
|
||||
url: route('products.reviews.index', {
|
||||
productId: this.product.id,
|
||||
page: this.currentReviewPage,
|
||||
}),
|
||||
}).then((reviews) => {
|
||||
this.reviews = reviews;
|
||||
}).catch((xhr) => {
|
||||
this.$notify(xhr.responseJSON.message);
|
||||
}).always(() => {
|
||||
this.fetchingReviews = false;
|
||||
});
|
||||
},
|
||||
|
||||
addNewReview() {
|
||||
this.addingNewReview = true;
|
||||
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: route('products.reviews.store', { productId: this.product.id }),
|
||||
data: this.reviewForm,
|
||||
}).then((review) => {
|
||||
this.reviewForm = {};
|
||||
this.reviews.total++;
|
||||
this.reviews.data.unshift(review);
|
||||
|
||||
$('.captcha-input').prev('img').trigger('click');
|
||||
}).catch((xhr) => {
|
||||
if (xhr.status === 422) {
|
||||
this.errors.record(xhr.responseJSON.errors);
|
||||
} else {
|
||||
this.$notify(xhr.responseJSON.message);
|
||||
}
|
||||
}).always(() => {
|
||||
this.addingNewReview = false;
|
||||
});
|
||||
},
|
||||
|
||||
addToCart() {
|
||||
this.addingToCart = true;
|
||||
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: route('cart.items.store', this.cartItemForm),
|
||||
}).then((cart) => {
|
||||
store.updateCart(cart);
|
||||
|
||||
$('.header-cart').trigger('click');
|
||||
}).catch((xhr) => {
|
||||
if (xhr.status === 422) {
|
||||
this.errors.record(xhr.responseJSON.errors);
|
||||
} else {
|
||||
this.$notify(xhr.responseJSON.message);
|
||||
}
|
||||
}).always(() => {
|
||||
this.addingToCart = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div class="col">
|
||||
<ProductCard :product="product"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProductCard from './../../ProductCard.vue';
|
||||
|
||||
export default {
|
||||
components: { ProductCard },
|
||||
|
||||
props: ['product'],
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div class="list-product-card">
|
||||
<div class="list-product-card-inner">
|
||||
<div class="product-card-left">
|
||||
<a :href="productUrl" class="product-image">
|
||||
<img :src="baseImage" :class="{ 'image-placeholder': ! hasBaseImage }" alt="product-image">
|
||||
|
||||
<ul class="list-inline product-badge">
|
||||
<li class="badge badge-danger" v-if="product.is_out_of_stock">
|
||||
{{ $trans('storefront::product_card.out_of_stock') }}
|
||||
</li>
|
||||
|
||||
<li class="badge badge-primary" v-else-if="product.is_new">
|
||||
{{ $trans('storefront::product_card.new') }}
|
||||
</li>
|
||||
|
||||
<li class="badge badge-success" v-if="product.has_percentage_special_price">
|
||||
-{{ product.special_price_percent }}%
|
||||
</li>
|
||||
</ul>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="product-card-right">
|
||||
<a :href="productUrl" class="product-name">
|
||||
<h6>{{ product.name }}</h6>
|
||||
</a>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<ProductRating :ratingPercent="product.rating_percent" :reviewCount="product.reviews.length"/>
|
||||
|
||||
<div class="product-price" v-html="product.formatted_price"></div>
|
||||
|
||||
<button
|
||||
v-if="hasNoOption || product.is_out_of_stock"
|
||||
class="btn btn-default btn-add-to-cart"
|
||||
:class="{ 'btn-loading': addingToCart }"
|
||||
:disabled="product.is_out_of_stock"
|
||||
@click="addToCart"
|
||||
>
|
||||
<i class="las la-cart-arrow-down"></i>
|
||||
{{ $trans('storefront::product_card.add_to_cart') }}
|
||||
</button>
|
||||
|
||||
<a
|
||||
v-else
|
||||
:href="productUrl"
|
||||
class="btn btn-default btn-add-to-cart"
|
||||
>
|
||||
<i class="las la-eye"></i>
|
||||
{{ $trans('storefront::product_card.view_options') }}
|
||||
</a>
|
||||
|
||||
<div class="product-card-actions">
|
||||
<button
|
||||
class="btn btn-wishlist"
|
||||
:class="{ 'added': inWishlist }"
|
||||
@click="syncWishlist"
|
||||
>
|
||||
<i class="la-heart" :class="inWishlist ? 'las' : 'lar'"></i>
|
||||
{{ $trans('storefront::product_card.wishlist') }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-compare"
|
||||
:class="{ 'added': inCompareList }"
|
||||
@click="syncCompareList"
|
||||
>
|
||||
<i class="las la-random"></i>
|
||||
{{ $trans('storefront::product_card.compare') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProductRating from './../../ProductRating.vue';
|
||||
import ProductCardMixin from '../../../mixins/ProductCardMixin';
|
||||
|
||||
export default {
|
||||
components: { ProductRating },
|
||||
|
||||
mixins: [
|
||||
ProductCardMixin,
|
||||
],
|
||||
|
||||
props: ['product'],
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { trans } from '../../../functions';
|
||||
|
||||
export function collapseFilters() {
|
||||
$('.filter-checkbox').each(function () {
|
||||
let self = $(this);
|
||||
let filterCollapseCheckbox = self.children().eq(4).nextAll('.form-check');
|
||||
|
||||
filterCollapseCheckbox.hide();
|
||||
self.next().remove();
|
||||
|
||||
if (self.children().length > 5) {
|
||||
self.after(`<span class="btn-show-more">${trans('storefront::products.show_more')}</span>`);
|
||||
|
||||
self.next().on('click', (e) => {
|
||||
let target = $(e.currentTarget);
|
||||
let showMoreText = trans('storefront::products.show_more');
|
||||
let showLessText = trans('storefront::products.show_less');
|
||||
|
||||
target.text(target.text() === showMoreText ? showLessText : showMoreText);
|
||||
|
||||
filterCollapseCheckbox.slideToggle(200);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<section class="landscape-products-wrap" v-if="hasAnyProduct">
|
||||
<div class="products-header">
|
||||
<h5 class="section-title">{{ $trans('storefront::product.related_products') }}</h5>
|
||||
</div>
|
||||
|
||||
<div class="landscape-products" ref="productsPlaceholder">
|
||||
<ProductCard v-for="(product, index) in products" :key="index" :product="product"></ProductCard>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProductCard from './../../ProductCard.vue';
|
||||
import { slickPrevArrow, slickNextArrow } from '../../../functions';
|
||||
|
||||
export default {
|
||||
components: { ProductCard },
|
||||
|
||||
props: ['products'],
|
||||
|
||||
computed: {
|
||||
hasAnyProduct() {
|
||||
return this.products.length !== 0;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
$(this.$refs.productsPlaceholder).slick({
|
||||
rows: 0,
|
||||
dots: false,
|
||||
arrows: true,
|
||||
infinite: true,
|
||||
slidesToShow: 5,
|
||||
slidesToScroll: 5,
|
||||
rtl: window.FleetCart.rtl,
|
||||
prevArrow: slickPrevArrow(),
|
||||
nextArrow: slickNextArrow(),
|
||||
responsive: [
|
||||
{
|
||||
breakpoint: 1761,
|
||||
settings: {
|
||||
slidesToShow: 4,
|
||||
slidesToScroll: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 1341,
|
||||
settings: {
|
||||
slidesToShow: 3,
|
||||
slidesToScroll: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 1081,
|
||||
settings: {
|
||||
slidesToShow: 2,
|
||||
slidesToScroll: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 992,
|
||||
settings: {
|
||||
slidesToShow: 4,
|
||||
slidesToScroll: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 881,
|
||||
settings: {
|
||||
slidesToShow: 3,
|
||||
slidesToScroll: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 661,
|
||||
settings: {
|
||||
dots: true,
|
||||
arrows: false,
|
||||
slidesToShow: 3,
|
||||
slidesToScroll: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 641,
|
||||
settings: {
|
||||
dots: true,
|
||||
arrows: false,
|
||||
slidesToShow: 2,
|
||||
slidesToScroll: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
61
Themes/Storefront/resources/assets/public/js/functions.js
Normal file
61
Themes/Storefront/resources/assets/public/js/functions.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
export function notify(message, options = {}) {
|
||||
Vue.$toast.open({
|
||||
message,
|
||||
type: 'default',
|
||||
duration: 3000,
|
||||
position: (screen.width < 992) ? 'bottom' : 'bottom-right',
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export function trans(langKey, replace = {}) {
|
||||
let line = window.FleetCart.langs[langKey];
|
||||
|
||||
for (let key in replace) {
|
||||
line = line.replace(`:${key}`, replace[key]);
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
export function isEmpty(value) {
|
||||
return $.isEmptyObject(value);
|
||||
}
|
||||
|
||||
export function chunk(array, size) {
|
||||
let chunkedArray = [];
|
||||
let index = 0;
|
||||
|
||||
while (index < array.length) {
|
||||
chunkedArray.push(array.slice(index, size + index));
|
||||
index += size;
|
||||
}
|
||||
|
||||
return chunkedArray;
|
||||
}
|
||||
|
||||
export function slickPrevArrow() {
|
||||
if (window.FleetCart.rtl) {
|
||||
return `<div class="arrow-prev">
|
||||
<i class="las la-angle-right"></i> ${trans('storefront::layout.prev')}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
return `<div class="arrow-prev">
|
||||
<i class="las la-angle-left"></i> ${trans('storefront::layout.prev')}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
export function slickNextArrow() {
|
||||
if (window.FleetCart.rtl) {
|
||||
return `<div class="arrow-next">
|
||||
${trans('storefront::layout.next')} <i class="las la-angle-left"></i>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
return `<div class="arrow-next">
|
||||
${trans('storefront::layout.next')} <i class="las la-angle-right"></i>
|
||||
</div>`;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import store from '../store';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loadingOrderSummary: false,
|
||||
shippingMethodName: null,
|
||||
applyingCoupon: false,
|
||||
couponCode: null,
|
||||
couponError: null,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
cart() {
|
||||
return store.state.cart;
|
||||
},
|
||||
|
||||
cartIsEmpty() {
|
||||
return store.cartIsEmpty();
|
||||
},
|
||||
|
||||
cartIsNotEmpty() {
|
||||
return ! store.cartIsEmpty();
|
||||
},
|
||||
|
||||
hasShippingMethod() {
|
||||
return store.hasShippingMethod();
|
||||
},
|
||||
|
||||
firstShippingMethod() {
|
||||
return Object.keys(store.state.cart.availableShippingMethods)[0];
|
||||
},
|
||||
|
||||
hasCoupon() {
|
||||
return store.state.cart.coupon.code !== undefined;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
applyCoupon() {
|
||||
if (! this.couponCode) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadingOrderSummary = true;
|
||||
this.applyingCoupon = true;
|
||||
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: route('cart.coupon.store', { coupon: this.couponCode }),
|
||||
}).then((cart) => {
|
||||
this.couponCode = null;
|
||||
|
||||
store.updateCart(cart);
|
||||
}).catch((xhr) => {
|
||||
this.couponError = xhr.responseJSON.message;
|
||||
}).always(() => {
|
||||
this.loadingOrderSummary = false;
|
||||
this.applyingCoupon = false;
|
||||
});
|
||||
},
|
||||
|
||||
removeCoupon() {
|
||||
this.loadingOrderSummary = true;
|
||||
|
||||
$.ajax({
|
||||
method: 'DELETE',
|
||||
url: route('cart.coupon.destroy'),
|
||||
}).then((cart) => {
|
||||
store.updateCart(cart);
|
||||
}).catch((xhr) => {
|
||||
this.$notify(xhr.responseJSON.message);
|
||||
}).always(() => {
|
||||
this.loadingOrderSummary = false;
|
||||
});
|
||||
},
|
||||
|
||||
updateShippingMethod(shippingMethodName) {
|
||||
if (! shippingMethodName) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadingOrderSummary = true;
|
||||
|
||||
this.changeShippingMethod(shippingMethodName);
|
||||
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: route('cart.shipping_method.store', { shipping_method: shippingMethodName }),
|
||||
}).then((cart) => {
|
||||
store.updateCart(cart);
|
||||
}).catch((xhr) => {
|
||||
this.$notify(xhr.responseJSON.message);
|
||||
}).always(() => {
|
||||
this.loadingOrderSummary = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tabs: [],
|
||||
activeTab: null,
|
||||
loading: false,
|
||||
products: [],
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.tabs = this.$children.filter((component) => {
|
||||
return component.$options.name === "DynamicTab";
|
||||
});
|
||||
|
||||
// Show the first tab by default on page load.
|
||||
this.change(this.tabs[0]);
|
||||
},
|
||||
|
||||
methods: {
|
||||
classes(tab) {
|
||||
return {
|
||||
"tab-item": true,
|
||||
loading: this.activeTab === tab && this.loading,
|
||||
active: this.activeTab === tab && !this.loading,
|
||||
};
|
||||
},
|
||||
|
||||
change(activeTab) {
|
||||
if (this.activeTab === activeTab || activeTab === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
this.activeTab = activeTab;
|
||||
|
||||
$.ajax({
|
||||
method: "GET",
|
||||
url: activeTab.url,
|
||||
}).then((products) => {
|
||||
if (this.selector().hasClass("slick-initialized")) {
|
||||
this.selector().slick("unslick");
|
||||
}
|
||||
|
||||
this.products = products;
|
||||
this.loading = false;
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.selector().slick(this.slickOptions());
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
import store from "../store";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
addingToCart: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
productUrl() {
|
||||
return route("products.show", this.product.slug);
|
||||
},
|
||||
|
||||
hasAnyOption() {
|
||||
return this.product.options_count > 0;
|
||||
},
|
||||
|
||||
hasNoOption() {
|
||||
return !this.hasAnyOption;
|
||||
},
|
||||
|
||||
hasBaseImage() {
|
||||
return this.product.base_image.length !== 0;
|
||||
},
|
||||
|
||||
baseImage() {
|
||||
if (this.hasBaseImage) {
|
||||
return this.product.base_image.path;
|
||||
}
|
||||
|
||||
return `${window.FleetCart.baseUrl}/themes/storefront/public/images/image-placeholder.png`;
|
||||
},
|
||||
|
||||
inWishlist() {
|
||||
return store.inWishlist(this.product.id);
|
||||
},
|
||||
|
||||
inCompareList() {
|
||||
return store.inCompareList(this.product.id);
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
syncWishlist() {
|
||||
store.syncWishlist(this.product.id);
|
||||
},
|
||||
|
||||
syncCompareList() {
|
||||
store.syncCompareList(this.product.id);
|
||||
},
|
||||
|
||||
addToCart() {
|
||||
this.addingToCart = true;
|
||||
|
||||
$.ajax({
|
||||
method: "POST",
|
||||
url: route("cart.items.store", {
|
||||
product_id: this.product.id,
|
||||
qty: 1,
|
||||
}),
|
||||
})
|
||||
.then((cart) => {
|
||||
store.updateCart(cart);
|
||||
|
||||
if (document.location.href !== route("cart.index")) {
|
||||
$(".header-cart").trigger("click");
|
||||
}
|
||||
})
|
||||
.catch((xhr) => {
|
||||
this.$notify(xhr.responseJSON.message);
|
||||
})
|
||||
.always(() => {
|
||||
this.addingToCart = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
export default {
|
||||
methods: {
|
||||
productUrl(product) {
|
||||
return route('products.show', product.slug);
|
||||
},
|
||||
|
||||
hasBaseImage(product) {
|
||||
return product.base_image.length !== 0;
|
||||
},
|
||||
|
||||
baseImage(product) {
|
||||
if (this.hasBaseImage(product)) {
|
||||
return product.base_image.path;
|
||||
}
|
||||
|
||||
return `${window.FleetCart.baseUrl}/themes/storefront/public/images/image-placeholder.png`;
|
||||
},
|
||||
},
|
||||
};
|
||||
105
Themes/Storefront/resources/assets/public/js/store.js
Normal file
105
Themes/Storefront/resources/assets/public/js/store.js
Normal file
@@ -0,0 +1,105 @@
|
||||
import Vue from "vue";
|
||||
import { isEmpty } from "./functions";
|
||||
|
||||
export default {
|
||||
state: Vue.observable({
|
||||
cart: FleetCart.cart,
|
||||
wishlist: FleetCart.wishlist,
|
||||
compareList: FleetCart.compareList,
|
||||
}),
|
||||
|
||||
cartIsEmpty() {
|
||||
return isEmpty(this.state.cart.items);
|
||||
},
|
||||
|
||||
updateCart(cart) {
|
||||
this.state.cart = cart;
|
||||
},
|
||||
|
||||
removeCartItem(cartItem) {
|
||||
Vue.delete(this.state.cart.items, cartItem.id);
|
||||
},
|
||||
|
||||
clearCart() {
|
||||
this.state.cart.items = {};
|
||||
},
|
||||
|
||||
hasShippingMethod() {
|
||||
return (
|
||||
Object.keys(this.state.cart.availableShippingMethods).length !== 0
|
||||
);
|
||||
},
|
||||
|
||||
wishlistCount() {
|
||||
return this.state.wishlist.length;
|
||||
},
|
||||
|
||||
inWishlist(productId) {
|
||||
return this.state.wishlist.includes(productId);
|
||||
},
|
||||
|
||||
syncWishlist(productId) {
|
||||
if (this.inWishlist(productId)) {
|
||||
this.removeFromWishlist(productId);
|
||||
} else {
|
||||
this.addToWishlist(productId);
|
||||
}
|
||||
},
|
||||
|
||||
addToWishlist(productId) {
|
||||
if (FleetCart.loggedIn) {
|
||||
this.state.wishlist.push(productId);
|
||||
} else {
|
||||
return (window.location.href = route("login"));
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
method: "POST",
|
||||
url: route("wishlist.store"),
|
||||
data: { productId },
|
||||
});
|
||||
},
|
||||
|
||||
removeFromWishlist(productId) {
|
||||
this.state.wishlist.splice(this.state.wishlist.indexOf(productId), 1);
|
||||
|
||||
$.ajax({
|
||||
method: "DELETE",
|
||||
url: route("wishlist.destroy", { productId }),
|
||||
});
|
||||
},
|
||||
|
||||
inCompareList(productId) {
|
||||
return this.state.compareList.includes(productId);
|
||||
},
|
||||
|
||||
syncCompareList(productId) {
|
||||
if (this.inCompareList(productId)) {
|
||||
this.removeFromCompareList(productId);
|
||||
} else {
|
||||
this.addToCompareList(productId);
|
||||
}
|
||||
},
|
||||
|
||||
addToCompareList(productId) {
|
||||
this.state.compareList.push(productId);
|
||||
|
||||
$.ajax({
|
||||
method: "POST",
|
||||
url: route("compare.store"),
|
||||
data: { productId },
|
||||
});
|
||||
},
|
||||
|
||||
removeFromCompareList(productId) {
|
||||
this.state.compareList.splice(
|
||||
this.state.compareList.indexOf(productId),
|
||||
1
|
||||
);
|
||||
|
||||
$.ajax({
|
||||
method: "DELETE",
|
||||
url: route("compare.destroy", { productId }),
|
||||
});
|
||||
},
|
||||
};
|
||||
531
Themes/Storefront/resources/assets/public/js/storefront.js
Normal file
531
Themes/Storefront/resources/assets/public/js/storefront.js
Normal file
@@ -0,0 +1,531 @@
|
||||
require('./vendors/vendors');
|
||||
|
||||
$(() => {
|
||||
|
||||
/* variables
|
||||
/*----------------------------------------*/
|
||||
|
||||
let _window = $(window),
|
||||
body = $('body');
|
||||
|
||||
/* button loading
|
||||
/*----------------------------------------*/
|
||||
|
||||
$('[data-loading]').on('click', (e) => {
|
||||
e.currentTarget.classList.add('btn-loading');
|
||||
});
|
||||
|
||||
/* select option
|
||||
/*----------------------------------------*/
|
||||
|
||||
let select = $('.custom-select-option');
|
||||
|
||||
select.niceSelect();
|
||||
|
||||
select.on('change', (e) => {
|
||||
e.target.dispatchEvent(new Event('nice-select-updated', { bubbles: true }));
|
||||
});
|
||||
|
||||
/* overlay
|
||||
/*----------------------------------------*/
|
||||
|
||||
let overlay = $('.overlay');
|
||||
|
||||
/* sidebar cart
|
||||
/*----------------------------------------*/
|
||||
|
||||
let headerCart = $('.header-column-right .header-cart'),
|
||||
sidebarCart = $('.sidebar-cart-wrap'),
|
||||
sidebarCartClose = $('.sidebar-cart-close');
|
||||
|
||||
headerCart.on('click', (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
overlay.addClass('active');
|
||||
sidebarCart.addClass('active');
|
||||
});
|
||||
|
||||
sidebarCartClose.on('click', () => {
|
||||
overlay.removeClass('active');
|
||||
sidebarCart.removeClass('active');
|
||||
});
|
||||
|
||||
sidebarCart.on('click', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
/* header
|
||||
/*----------------------------------------*/
|
||||
|
||||
let headerWrap = $('.header-wrap'),
|
||||
headerWrapInner = $('.header-wrap-inner'),
|
||||
headerWrapInnerHeight = headerWrapInner.outerHeight(),
|
||||
headerSearchSm = $('.header-search-sm'),
|
||||
searchInputSm = $('.search-input-sm'),
|
||||
headerSearchSmClose = $('.header-search-sm-form .btn-close');
|
||||
|
||||
headerSearchSm.on('click', (e) => {
|
||||
let target = $(e.currentTarget);
|
||||
|
||||
target.parents('.header-search').next().toggleClass('active');
|
||||
searchInputSm.focus();
|
||||
});
|
||||
|
||||
headerSearchSmClose.on('click', (e) => {
|
||||
let target = $(e.currentTarget);
|
||||
|
||||
target.parents('.header-search-sm-form').removeClass('active');
|
||||
});
|
||||
|
||||
_window.on('resize', () => {
|
||||
headerWrapInnerHeight = headerWrapInner.outerHeight();
|
||||
});
|
||||
|
||||
_window.on('load scroll resize', () => {
|
||||
let headerWrapHeight = headerWrap.outerHeight(),
|
||||
headerWrapOffsetTop = headerWrap.offset().top + headerWrapHeight;
|
||||
|
||||
function stickyHeader() {
|
||||
let scrollTop = _window.scrollTop();
|
||||
|
||||
if (scrollTop > headerWrapOffsetTop) {
|
||||
headerWrap.css('padding-top', `${headerWrapInnerHeight}px`);
|
||||
headerWrapInner.addClass('sticky');
|
||||
|
||||
setTimeout(() => {
|
||||
headerWrapInner.addClass('show');
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
headerWrap.css('padding-top', 0);
|
||||
headerWrapInner.removeClass('sticky show');
|
||||
}
|
||||
|
||||
stickyHeader();
|
||||
});
|
||||
|
||||
/* menu dropdown arrow
|
||||
/*----------------------------------------*/
|
||||
|
||||
let megaMenuItem = $('.mega-menu > li'),
|
||||
subMenuDropdown = $('.sub-menu > .dropdown'),
|
||||
sidebarMenuSubMenu = $('.sidebar-menu .sub-menu');
|
||||
|
||||
function menuDropdownArrow(parentSelector, childSelector) {
|
||||
parentSelector.each(function () {
|
||||
let self = $(this);
|
||||
|
||||
if (self.children().length > 1) {
|
||||
if (window.FleetCart.rtl) {
|
||||
self.children(`${childSelector}`).append('<i class="las la-angle-left"></i>');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
self.children(`${childSelector}`).append('<i class="las la-angle-right"></i>');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
menuDropdownArrow(subMenuDropdown, 'a');
|
||||
menuDropdownArrow(megaMenuItem, '.menu-item');
|
||||
|
||||
/* navigation
|
||||
/*----------------------------------------*/
|
||||
|
||||
let moreCategories = $('.more-categories'),
|
||||
categoryDropdown = $('.category-dropdown'),
|
||||
categoryNavInner = $('.category-nav-inner'),
|
||||
categoryDropdownWrap = $('.category-dropdown-wrap'),
|
||||
verticalMegaMenuList = $('.vertical-megamenu > li');
|
||||
|
||||
categoryNavInner.on('click', () => {
|
||||
categoryDropdownWrap.toggleClass('show');
|
||||
});
|
||||
|
||||
_window.on('load resize', () => {
|
||||
let verticalMegaMenuListHeight = 0,
|
||||
homeSliderHeight = homeSlider.height(),
|
||||
categoryDropdownHeight = homeSliderHeight;
|
||||
|
||||
categoryDropdown.css('height', `${categoryDropdownHeight}px`);
|
||||
|
||||
verticalMegaMenuList.each(function () {
|
||||
let self = $(this);
|
||||
|
||||
verticalMegaMenuListHeight += self.height();
|
||||
|
||||
if (verticalMegaMenuListHeight + 78 > categoryDropdownHeight) {
|
||||
self.addClass('hide');
|
||||
moreCategories.removeClass('hide');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
self.removeClass('hide');
|
||||
moreCategories.addClass('hide');
|
||||
});
|
||||
});
|
||||
|
||||
/* sidebar menu
|
||||
/*----------------------------------------*/
|
||||
|
||||
let sidebarMenuIcon = $('.sidebar-menu-icon'),
|
||||
sidebarMenuWrap = $('.sidebar-menu-wrap'),
|
||||
sidebarMenuClose = $('.sidebar-menu-close'),
|
||||
sidebarMenuTab = $('.sidebar-menu-tab a'),
|
||||
sidebarMenuList = $('.sidebar-menu li'),
|
||||
sidebarMenuLink = $('.sidebar-menu > li > a'),
|
||||
sidebarMenuListUl = $('.sidebar-menu > li > ul'),
|
||||
sidebarMenuDropdown = $('.sidebar-menu > .dropdown'),
|
||||
sidebarMenuSubMenuUl = $('.sidebar-menu .sub-menu ul'),
|
||||
sidebarMenuSubMenuLink = $('.sidebar-menu .sub-menu > a');
|
||||
|
||||
sidebarMenuIcon.on('click', (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
overlay.addClass('active');
|
||||
sidebarMenuWrap.addClass('active');
|
||||
});
|
||||
|
||||
sidebarMenuClose.on('click', (e) => {
|
||||
overlay.removeClass('active');
|
||||
sidebarMenuWrap.removeClass('active');
|
||||
});
|
||||
|
||||
sidebarMenuWrap.on('click', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
sidebarMenuTab.on('click', (e) => {
|
||||
let target = $(e.currentTarget);
|
||||
|
||||
e.preventDefault();
|
||||
target.tab('show');
|
||||
});
|
||||
|
||||
sidebarMenuList.each(function () {
|
||||
let self = $(this);
|
||||
|
||||
if (self.children().length > 1) {
|
||||
if (window.FleetCart.rtl) {
|
||||
self.children('a').after('<i class="las la-angle-left"></i>');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
self.children('a').after('<i class="las la-angle-right"></i>');
|
||||
}
|
||||
});
|
||||
|
||||
sidebarMenuDropdown.on('click', (e) => {
|
||||
let target = $(e.currentTarget);
|
||||
|
||||
if (! target.hasClass('active')) {
|
||||
$('.sidebar-menu > li').removeClass('active');
|
||||
target.addClass('active');
|
||||
} else {
|
||||
$('.sidebar-menu > li').removeClass('active');
|
||||
}
|
||||
|
||||
if (! target.children('ul').hasClass('open')) {
|
||||
$('.sidebar-menu .open').removeClass('open').slideUp(300);
|
||||
target.children('ul').addClass('open').slideDown(300);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$('.sidebar-menu .open').removeClass('open').slideUp(300);
|
||||
});
|
||||
|
||||
sidebarMenuLink.on('click', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
sidebarMenuListUl.on('click', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
sidebarMenuSubMenu.on('click', (e) => {
|
||||
let target = $(e.currentTarget);
|
||||
|
||||
if (! target.hasClass('active')) {
|
||||
target.addClass('active');
|
||||
} else {
|
||||
target.removeClass('active');
|
||||
}
|
||||
|
||||
target.children('ul').slideToggle(300);
|
||||
});
|
||||
|
||||
sidebarMenuSubMenuUl.on('click', function (e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
sidebarMenuSubMenuLink.on('click', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
/* slider
|
||||
/*----------------------------------------*/
|
||||
|
||||
let homeSlider = $('.home-slider');
|
||||
|
||||
if (homeSlider.length !== 0) {
|
||||
homeSlider.slick({
|
||||
rows: 0,
|
||||
rtl: window.FleetCart.rtl,
|
||||
cssEase: 'ease',
|
||||
speed: Number(homeSlider.data('speed')),
|
||||
fade: !! JSON.parse(homeSlider.data('fade')),
|
||||
dots: !! JSON.parse(homeSlider.data('dots')),
|
||||
arrows: !! JSON.parse(homeSlider.data('arrows')),
|
||||
autoplay: !! JSON.parse(homeSlider.data('autoplay')),
|
||||
autoplaySpeed: Number(homeSlider.data('autoplay-speed')),
|
||||
responsive: [
|
||||
{
|
||||
breakpoint: 768,
|
||||
settings: {
|
||||
dots: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
}).slickAnimation();
|
||||
}
|
||||
|
||||
/* tooltip
|
||||
/*----------------------------------------*/
|
||||
|
||||
$('[data-toggle="tooltip"]').tooltip({
|
||||
trigger: 'hover',
|
||||
selector: '[data-toggle="tooltip"]',
|
||||
});
|
||||
|
||||
/* top brands
|
||||
/*----------------------------------------*/
|
||||
|
||||
let topBrands = $('.top-brands');
|
||||
|
||||
topBrands.slick({
|
||||
rows: 0,
|
||||
dots: false,
|
||||
arrows: true,
|
||||
infinite: true,
|
||||
slidesToShow: 7,
|
||||
slidesToScroll: 7,
|
||||
rtl: window.FleetCart.rtl,
|
||||
responsive: [
|
||||
{
|
||||
breakpoint: 1200,
|
||||
settings: {
|
||||
slidesToShow: 6,
|
||||
slidesToScroll: 6,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 1050,
|
||||
settings: {
|
||||
slidesToShow: 5,
|
||||
slidesToScroll: 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 900,
|
||||
settings: {
|
||||
slidesToShow: 4,
|
||||
slidesToScroll: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 750,
|
||||
settings: {
|
||||
slidesToShow: 3,
|
||||
slidesToScroll: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 600,
|
||||
settings: {
|
||||
slidesToShow: 2,
|
||||
slidesToScroll: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
/* sidebar filter
|
||||
/*----------------------------------------*/
|
||||
|
||||
let mobileViewFilter = $('.mobile-view-filter');
|
||||
let filterSectionWrap = $('.filter-section-wrap');
|
||||
let sidebarFilterClose = $('.sidebar-filter-close');
|
||||
|
||||
mobileViewFilter.on('click', (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
filterSectionWrap.addClass('active');
|
||||
overlay.addClass('active');
|
||||
});
|
||||
|
||||
sidebarFilterClose.on('click', () => {
|
||||
filterSectionWrap.removeClass('active');
|
||||
overlay.removeClass('active');
|
||||
});
|
||||
|
||||
filterSectionWrap.on('click', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
body.on('click', () => {
|
||||
overlay.removeClass('active');
|
||||
sidebarCart.removeClass('active');
|
||||
sidebarMenuWrap.removeClass('active');
|
||||
filterSectionWrap.removeClass('active');
|
||||
});
|
||||
|
||||
/* browse categories
|
||||
/*----------------------------------------*/
|
||||
|
||||
$('.browse-categories li').each((i, li) => {
|
||||
if ($(li).children('ul').length > 0) {
|
||||
$(li).addClass('parent');
|
||||
}
|
||||
});
|
||||
|
||||
let filterCategoriesLink = $('.browse-categories li.parent > a');
|
||||
let parentUls = $('.browse-categories li.active').parentsUntil('.browse-categories', 'ul');
|
||||
|
||||
if (window.FleetCart.rtl) {
|
||||
filterCategoriesLink.before('<i class="las la-angle-left"></i>');
|
||||
} else {
|
||||
filterCategoriesLink.before('<i class="las la-angle-right"></i>');
|
||||
}
|
||||
|
||||
parentUls.show().siblings('i').addClass('open');
|
||||
|
||||
$('.browse-categories li i').on('click', (e) => {
|
||||
$(e.currentTarget).toggleClass('open').siblings('ul').slideToggle(300);
|
||||
});
|
||||
|
||||
/* image gallery
|
||||
/*----------------------------------------*/
|
||||
|
||||
let baseImage = $('.base-image');
|
||||
|
||||
$('.additional-image-wrap').slick({
|
||||
rows: 0,
|
||||
dots: false,
|
||||
arrows: true,
|
||||
vertical: true,
|
||||
infinite: false,
|
||||
slidesToShow: 4,
|
||||
slideToScroll: 1,
|
||||
asNavFor: baseImage,
|
||||
focusOnSelect: true,
|
||||
adaptiveHeight: true,
|
||||
verticalSwiping: true,
|
||||
responsive: [
|
||||
{
|
||||
breakpoint: 577,
|
||||
settings: {
|
||||
vertical: false,
|
||||
variableWidth: true,
|
||||
verticalSwiping: false,
|
||||
rtl: window.FleetCart.rtl,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
baseImage.slick({
|
||||
rows: 0,
|
||||
fade: true,
|
||||
dots: false,
|
||||
swipe: false,
|
||||
arrows: false,
|
||||
infinite: false,
|
||||
draggable: false,
|
||||
slidesToShow: 1,
|
||||
slidesToScroll: 1,
|
||||
rtl: window.FleetCart.rtl,
|
||||
});
|
||||
|
||||
baseImage.slickLightbox({
|
||||
src: 'data-image',
|
||||
itemSelector: '.base-image-slide',
|
||||
slick: {
|
||||
fade: true,
|
||||
infinite: false,
|
||||
rtl: window.FleetCart.rtl,
|
||||
},
|
||||
});
|
||||
|
||||
$('.base-image-slide').zoom({
|
||||
magnify: 1.2,
|
||||
touch: false,
|
||||
});
|
||||
|
||||
/* number picker
|
||||
/*----------------------------------------*/
|
||||
|
||||
$('.btn-number').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
let type = $(this).attr('data-type');
|
||||
let input = $(this).closest('.input-group-quantity').find('input.input-quantity');
|
||||
let minValue = input.attr('min');
|
||||
let maxValue = input.attr('max');
|
||||
let currentValue = parseInt(input.val());
|
||||
|
||||
if (! $.isNumeric(currentValue)) {
|
||||
input.val(minValue);
|
||||
input[0].dispatchEvent(new Event('input'), { bubbles: true });
|
||||
}
|
||||
|
||||
if (type === 'minus') {
|
||||
if (currentValue > minValue) {
|
||||
input.val(currentValue - 1);
|
||||
input[0].dispatchEvent(new Event('input'), { bubbles: true });
|
||||
$('.btn-number.btn-plus').removeAttr('disabled');
|
||||
}
|
||||
|
||||
if (input.val() === minValue) {
|
||||
$(this).attr('disabled', true);
|
||||
}
|
||||
} else if (type === 'plus') {
|
||||
if (! maxValue || currentValue < maxValue) {
|
||||
input.val(currentValue + 1);
|
||||
input[0].dispatchEvent(new Event('input'), { bubbles: true });
|
||||
$('.btn-number.btn-minus').removeAttr('disabled');
|
||||
}
|
||||
|
||||
if (input.val() === maxValue) {
|
||||
$(this).attr('disabled', true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('.input-number').on('input', function () {
|
||||
let self = $(this);
|
||||
let minValue = parseInt(self.attr('min'));
|
||||
let maxValue = parseInt(self.attr('max'));
|
||||
let currentValue = parseInt(self.val());
|
||||
|
||||
if (! $.isNumeric(self.val())) {
|
||||
self.val(minValue);
|
||||
$('.btn-number.btn-minus').attr('disabled', true);
|
||||
}
|
||||
|
||||
if (currentValue < minValue) {
|
||||
self.val(minValue);
|
||||
$('.btn-number.btn-minus').attr('disabled', true);
|
||||
}
|
||||
|
||||
if (maxValue && currentValue > maxValue) {
|
||||
self.val(maxValue);
|
||||
$('.btn-number.btn-plus').attr('disabled', true);
|
||||
}
|
||||
});
|
||||
});
|
||||
10
Themes/Storefront/resources/assets/public/js/vendors/flatpickr.js
vendored
Normal file
10
Themes/Storefront/resources/assets/public/js/vendors/flatpickr.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
require('flatpickr');
|
||||
|
||||
for (let el of $('.datetime-picker')) {
|
||||
$(el).flatpickr({
|
||||
mode: el.hasAttribute('data-range') ? 'range' : 'single',
|
||||
enableTime: el.hasAttribute('data-time'),
|
||||
noCalendar: el.hasAttribute('data-no-calender'),
|
||||
altInput: true,
|
||||
});
|
||||
}
|
||||
230
Themes/Storefront/resources/assets/public/js/vendors/slick-animation.js
vendored
Normal file
230
Themes/Storefront/resources/assets/public/js/vendors/slick-animation.js
vendored
Normal file
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
slick-animation.js
|
||||
|
||||
Version: 0.3.3 Beta
|
||||
Author: Marvin Hübner
|
||||
Docs: https://github.com/marvinhuebner/slick-animation
|
||||
Repo: https://github.com/marvinhuebner/slick-animation
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
$.fn.slickAnimation = function () {
|
||||
var currentSlickSlider = $(this);
|
||||
|
||||
var slickItems = currentSlickSlider.find('.slick-list .slick-track > div');
|
||||
var firstSlickItem = currentSlickSlider.find('[data-slick-index="0"]');
|
||||
|
||||
var animatedClass = 'animated';
|
||||
var visible = {opacity: '1'};
|
||||
var hidden = {opacity: '0'};
|
||||
|
||||
/**
|
||||
* function for setting animationIn and animationOut class
|
||||
* @param obj
|
||||
* @param type
|
||||
* @param animationIn
|
||||
* @param animatedClass
|
||||
* @param visibility
|
||||
*/
|
||||
|
||||
function slickSetAnimationDefault(obj, type, animationIn, animatedClass, visibility) {
|
||||
visibility = typeof visibility !== 'undefined' ? visibility : false;
|
||||
|
||||
slickRemoveAnimation(obj, 'delay');
|
||||
slickRemoveAnimation(obj, 'duration');
|
||||
|
||||
if (type['opacity'] == 1) {
|
||||
obj.addClass(animationIn);
|
||||
obj.addClass(animatedClass);
|
||||
} else {
|
||||
obj.removeClass(animationIn);
|
||||
obj.removeClass(animatedClass);
|
||||
}
|
||||
|
||||
if (visibility) obj.css(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* get timeout when delay, duration, delay and duration is set
|
||||
* @param delayIn
|
||||
* @param durationIn
|
||||
* @returns {number}
|
||||
*/
|
||||
|
||||
function getTimeout(delayIn, durationIn) {
|
||||
if (delayIn) {
|
||||
return delayIn * 1000 + 1000;
|
||||
|
||||
} else if (durationIn) {
|
||||
return durationIn * 1000;
|
||||
|
||||
} else if ((delayIn) || (durationIn)) {
|
||||
return (delayIn * 1000) + (durationIn * 1000);
|
||||
}
|
||||
return 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* add css animations for delay and duration
|
||||
* @param obj
|
||||
* @param animation
|
||||
* @param value
|
||||
*/
|
||||
function slickAddAnimation(obj, animation, value) {
|
||||
var delayInAttr = [
|
||||
'animation-' + animation,
|
||||
'-webkit-animation-' + animation,
|
||||
'-moz-animation-' + animation,
|
||||
'-o-animation-' + animation,
|
||||
'-ms-animation-' + animation
|
||||
];
|
||||
var delayInAttributes = {};
|
||||
delayInAttr.forEach(function (entry) {
|
||||
|
||||
delayInAttributes[entry] = value + 's';
|
||||
});
|
||||
obj.css(delayInAttributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* remove css animations for delay and duration
|
||||
* @param obj
|
||||
* @param animation
|
||||
*/
|
||||
function slickRemoveAnimation(obj, animation) {
|
||||
var delayInAttr = [
|
||||
'animation-' + animation,
|
||||
'-webkit-animation-' + animation,
|
||||
'-moz-animation-' + animation,
|
||||
'-o-animation-' + animation,
|
||||
'-ms-animation-' + animation
|
||||
];
|
||||
var delayInAttributes = {};
|
||||
delayInAttr.forEach(function (entry) {
|
||||
|
||||
delayInAttributes[entry] = '';
|
||||
});
|
||||
obj.css(delayInAttributes);
|
||||
}
|
||||
|
||||
slickItems.each(function () {
|
||||
var slickItem = $(this);
|
||||
|
||||
slickItem.find('[data-animation-in]').each(function () {
|
||||
var self = $(this);
|
||||
|
||||
self.css(hidden);
|
||||
|
||||
var animationIn = self.attr('data-animation-in');
|
||||
var animationOut = self.attr('data-animation-out');
|
||||
var delayIn = self.attr('data-delay-in');
|
||||
var durationIn = self.attr('data-duration-in');
|
||||
var delayOut = self.attr('data-delay-out');
|
||||
var durationOut = self.attr('data-duration-out');
|
||||
|
||||
if (animationOut) {
|
||||
if (firstSlickItem.length > 0) {
|
||||
if (slickItem.hasClass('slick-current')) {
|
||||
|
||||
slickSetAnimationDefault(self, visible, animationIn, animatedClass, true);
|
||||
|
||||
if (delayIn) {
|
||||
slickAddAnimation(self, 'delay', delayIn);
|
||||
}
|
||||
if (durationIn) {
|
||||
slickAddAnimation(self, 'duration', durationIn);
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
slickSetAnimationDefault(self, hidden, animationIn, animatedClass);
|
||||
slickSetAnimationDefault(self, visible, animationOut, animatedClass);
|
||||
if (delayOut) {
|
||||
slickAddAnimation(self, 'delay', delayOut);
|
||||
}
|
||||
if (durationOut) {
|
||||
slickAddAnimation(self, 'duration', durationOut);
|
||||
}
|
||||
setTimeout(function() {
|
||||
slickRemoveAnimation(self, 'delay');
|
||||
slickRemoveAnimation(self, 'duration');
|
||||
}, getTimeout(delayOut, durationOut));
|
||||
|
||||
}, getTimeout(delayIn, durationIn));
|
||||
}
|
||||
}
|
||||
|
||||
currentSlickSlider.on('afterChange', function (event, slick, currentSlider) {
|
||||
if (slickItem.hasClass('slick-current')) {
|
||||
|
||||
slickSetAnimationDefault(self, visible, animationIn, animatedClass, true);
|
||||
|
||||
if (delayIn) {
|
||||
slickAddAnimation(self, 'delay', delayIn);
|
||||
}
|
||||
if (durationIn) {
|
||||
slickAddAnimation(self, 'duration', durationIn);
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
slickSetAnimationDefault(self, hidden, animationIn, animatedClass);
|
||||
slickSetAnimationDefault(self, visible, animationOut, animatedClass);
|
||||
|
||||
if (delayOut) {
|
||||
slickAddAnimation(self, 'delay', delayOut);
|
||||
}
|
||||
if (durationOut) {
|
||||
slickAddAnimation(self, 'duration', durationOut);
|
||||
}
|
||||
setTimeout(function() {
|
||||
slickRemoveAnimation(self, 'delay');
|
||||
slickRemoveAnimation(self, 'duration');
|
||||
}, getTimeout(delayOut, durationOut));
|
||||
|
||||
}, getTimeout(delayIn, durationIn));
|
||||
}
|
||||
});
|
||||
|
||||
currentSlickSlider.on('beforeChange', function (event, slick, currentSlider) {
|
||||
slickSetAnimationDefault(self, hidden, animationOut, animatedClass, true);
|
||||
|
||||
});
|
||||
} else {
|
||||
|
||||
if (firstSlickItem.length > 0) {
|
||||
if (slickItem.hasClass('slick-current')) {
|
||||
slickSetAnimationDefault(self, visible, animationIn, animatedClass, true);
|
||||
|
||||
if (delayIn) {
|
||||
slickAddAnimation(self, 'delay', delayIn);
|
||||
}
|
||||
if (durationIn) {
|
||||
slickAddAnimation(self, 'duration', durationIn);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentSlickSlider.on('afterChange', function (event, slick, currentSlider) {
|
||||
if (slickItem.hasClass('slick-current')) {
|
||||
slickSetAnimationDefault(self, visible, animationIn, animatedClass, true);
|
||||
|
||||
if (delayIn) {
|
||||
slickAddAnimation(self, 'delay', delayIn);
|
||||
}
|
||||
if (durationIn) {
|
||||
slickAddAnimation(self, 'duration', durationIn);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
currentSlickSlider.on('beforeChange', function (event, slick, currentSlider) {
|
||||
slickSetAnimationDefault(self, hidden, animationIn, animatedClass, true);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
return this;
|
||||
}
|
||||
})(jQuery);
|
||||
11
Themes/Storefront/resources/assets/public/js/vendors/vendors.js
vendored
Normal file
11
Themes/Storefront/resources/assets/public/js/vendors/vendors.js
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
window.$ = window.jQuery = require('jquery');
|
||||
|
||||
require('popper.js');
|
||||
require('bootstrap');
|
||||
require('jquery-nice-select');
|
||||
require('jquery-zoom');
|
||||
require('slick-carousel');
|
||||
require('slick-lightbox');
|
||||
require('./slick-animation.js');
|
||||
require('../../../../../node_modules/kbw-countdown/dist/js/jquery.plugin.js');
|
||||
require('../../../../../node_modules/kbw-countdown/src/js/jquery.countdown.js');
|
||||
Reference in New Issue
Block a user