¨4.0.1¨

This commit is contained in:
¨NW¨
2023-12-03 14:07:47 +00:00
parent c08b36d1b6
commit f35052522d
1112 changed files with 43019 additions and 24987 deletions

View File

@@ -1,4 +1,4 @@
import Vue from 'vue';
import Vue from "vue";
export default class {
constructor() {
@@ -36,13 +36,13 @@ export default class {
}
normalizeKey(key) {
let keyParts = key.replace('[]', '').split('[');
let keyParts = key.replace("[]", "").split("[");
// No need to normalize the key.
if (keyParts.length === 1) {
return key;
}
return keyParts.join('.').slice(0, -1);
return keyParts.join(".").slice(0, -1);
}
}

View File

@@ -0,0 +1,6 @@
import axios from "axios";
window.axios = axios;
axios.defaults.headers.common["X-CSRF-TOKEN"] = FleetCart.csrfToken;
axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";

View File

@@ -0,0 +1,100 @@
<template>
<div class="daily-deals-countdown countdown clearfix">
<span class="countdown-row">
<span class="countdown-section">
<span class="countdown-amount">{{ date.days }}</span>
<span class="countdown-period">
{{ $trans("storefront::product_card.days") }}
</span>
</span>
<span class="countdown-section">
<span class="countdown-amount">{{ date.hours }}</span>
<span class="countdown-period">
{{ $trans("storefront::product_card.hours") }}
</span>
</span>
<span class="countdown-section">
<span class="countdown-amount">{{ date.minutes }}</span>
<span class="countdown-period">
{{ $trans("storefront::product_card.minutes") }}
</span>
</span>
<span class="countdown-section">
<span class="countdown-amount">{{ date.seconds }}</span>
<span class="countdown-period">
{{ $trans("storefront::product_card.seconds") }}
</span>
</span>
</span>
</div>
</template>
<script>
export default {
props: ["endDate"],
data() {
return {
date: {},
countdown: null,
};
},
mounted() {
if (new Date() > new Date(this.endDate)) {
this.setInitialDate();
return;
}
this.countdown = this.initCountdown();
},
methods: {
initCountdown() {
return countdown(
new Date(this.endDate),
({ days, hours, minutes, seconds }) => {
if (new Date() > new Date(this.endDate)) {
this.setInitialDate();
window.clearInterval(this.countdown);
return;
}
this.date = Object.assign({}, this.date, {
days: this.leadingZero(days),
hours: this.leadingZero(hours),
minutes: this.leadingZero(minutes),
seconds: this.leadingZero(seconds),
});
},
countdown.DAYS |
countdown.HOURS |
countdown.MINUTES |
countdown.SECONDS
);
},
setInitialDate() {
this.date = Object.assign({}, this.date, {
days: "00",
hours: "00",
minutes: "00",
seconds: "00",
});
},
leadingZero(number) {
return number < 10 ? "0" + number : number;
},
},
};
</script>

View File

@@ -2,13 +2,17 @@
<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">
<img
:src="baseImage"
:class="{ 'image-placeholder': !hasBaseImage }"
:alt="product.name"
/>
</a>
<div class="product-card-actions">
<button
class="btn btn-wishlist"
:class="{ 'added': inWishlist }"
:class="{ added: inWishlist }"
:title="$trans('storefront::product_card.wishlist')"
@click="syncWishlist"
>
@@ -17,7 +21,7 @@
<button
class="btn btn-compare"
:class="{ 'added': inCompareList }"
:class="{ added: inCompareList }"
:title="$trans('storefront::product_card.compare')"
@click="syncCompareList"
>
@@ -26,42 +30,52 @@
</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 class="badge badge-danger" v-if="item.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') }}
{{ $trans("storefront::product_card.new") }}
</li>
<li class="badge badge-success" v-if="product.has_percentage_special_price">
-{{ product.special_price_percent }}%
<li
class="badge badge-success"
v-if="item.has_percentage_special_price"
>
-{{ item.special_price_percent }}%
</li>
</ul>
</div>
<div class="product-card-middle">
<ProductRating :ratingPercent="product.rating_percent" :reviewCount="product.reviews.length"/>
<product-rating
:ratingPercent="product.rating_percent"
:reviewCount="product.reviews.length"
>
</product-rating>
<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
class="product-price product-price-clone"
v-html="item.formatted_price"
></div>
</div>
<div class="product-card-bottom">
<div class="product-price" v-html="product.formatted_price"></div>
<div class="product-price" v-html="item.formatted_price"></div>
<button
v-if="hasNoOption || product.is_out_of_stock"
v-if="hasNoOption || item.is_out_of_stock"
class="btn btn-primary btn-add-to-cart"
:class="{ 'btn-loading': addingToCart }"
:disabled="product.is_out_of_stock"
:disabled="item.is_out_of_stock"
@click="addToCart"
>
<i class="las la-cart-arrow-down"></i>
{{ $trans('storefront::product_card.add_to_cart') }}
{{ $trans("storefront::product_card.add_to_cart") }}
</button>
<a
@@ -70,23 +84,26 @@
class="btn btn-primary btn-add-to-cart"
>
<i class="las la-eye"></i>
{{ $trans('storefront::product_card.view_options') }}
{{ $trans("storefront::product_card.view_options") }}
</a>
</div>
</div>
</template>
<script>
import ProductRating from './ProductRating.vue';
import ProductCardMixin from '../mixins/ProductCardMixin';
import ProductCardMixin from "../mixins/ProductCardMixin";
export default {
components: { ProductRating },
export default {
mixins: [ProductCardMixin],
mixins: [
ProductCardMixin,
],
props: ["product"],
props: ['product'],
};
data() {
return {
item: {
...(this.product.variant ? this.product.variant : this.product),
},
};
},
};
</script>

View File

@@ -1,7 +1,11 @@
<template>
<div :href="productUrl" class="vertical-product-card">
<a :href="productUrl" class="product-image">
<img :src="baseImage" :class="{ 'image-placeholder': ! hasBaseImage }" alt="product-image">
<img
:src="baseImage"
:class="{ 'image-placeholder': !hasBaseImage }"
:alt="product.name"
/>
</a>
<div class="product-info">
@@ -9,24 +13,31 @@
<h6>{{ product.name }}</h6>
</a>
<div class="product-price" v-html="product.formatted_price"></div>
<div class="product-price" v-html="item.formatted_price"></div>
<ProductRating :ratingPercent="product.rating_percent" :reviewCount="product.reviews.length"/>
<product-rating
:ratingPercent="product.rating_percent"
:reviewCount="product.reviews.length"
>
</product-rating>
</div>
</div>
</template>
<script>
import ProductRating from './ProductRating.vue';
import ProductCardMixin from '../mixins/ProductCardMixin';
import ProductCardMixin from "../mixins/ProductCardMixin";
export default {
components: { ProductRating },
export default {
mixins: [ProductCardMixin],
mixins: [
ProductCardMixin,
],
props: ["product"],
props: ['product'],
};
data() {
return {
item: {
...(this.product.variant ? this.product.variant : this.product),
},
};
},
};
</script>

View File

@@ -7,19 +7,19 @@
</li>
<li v-show="rangeFirstPage !== 1" class="page-item">
<button class="page-link" @click="goto(1)">
1
</button>
<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>
<button class="page-link" @click="goto(2)">2</button>
</li>
<li
v-show="rangeFirstPage !== 1 && rangeFirstPage !== 2 && rangeFirstPage !== 3"
v-show="
rangeFirstPage !== 1 &&
rangeFirstPage !== 2 &&
rangeFirstPage !== 3
"
class="page-item disabled"
>
<span class="page-link">...</span>
@@ -37,13 +37,17 @@
</li>
<li
v-show="rangeLastPage !== totalPage && rangeLastPage !== (totalPage - 1) && rangeLastPage !== (totalPage - 2)"
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">
<li v-show="rangeLastPage === totalPage - 2" class="page-item">
<button class="page-link" @click="goto(totalPage - 1)">
{{ totalPage - 1 }}
</button>
@@ -56,7 +60,11 @@
</li>
<li class="page-item" :class="{ disabled: hasLast }">
<button class="page-link" :class="{ disabled: hasLast }" @click="next">
<button
class="page-link"
:class="{ disabled: hasLast }"
@click="next"
>
<i class="las la-angle-right"></i>
</button>
</li>
@@ -64,80 +72,87 @@
</template>
<script>
export default {
props: {
totalPage: Number,
currentPage: Number,
rangeMax: {
type: Number,
default: 3,
},
export default {
props: {
totalPage: Number,
currentPage: Number,
rangeMax: {
type: Number,
default: 3,
},
},
mounted() {
if (this.currentPage > this.totalPage) {
this.$emit('page-changed', this.totalPage);
mounted() {
if (this.currentPage > this.totalPage) {
this.$emit("page-changed", this.totalPage);
}
},
computed: {
rangeFirstPage() {
if (this.currentPage === 1) {
return 1;
}
},
computed: {
rangeFirstPage() {
if (this.currentPage === 1) {
if (this.currentPage === this.totalPage) {
if (this.totalPage - this.rangeMax < 0) {
return 1;
}
if (this.currentPage === this.totalPage) {
if ((this.totalPage - this.rangeMax) < 0) {
return 1;
}
return this.totalPage - this.rangeMax + 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;
},
return this.currentPage - 1;
},
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;
},
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>

View File

@@ -1,13 +1,13 @@
import Errors from '../../../Errors';
import Errors from "../../../Errors";
export default {
props: ['initialAddresses', 'initialDefaultAddress', 'countries'],
props: ["initialAddresses", "initialDefaultAddress", "countries"],
data() {
return {
addresses: this.initialAddresses,
defaultAddress: this.initialDefaultAddress,
form: { state: '' },
form: { state: "" },
states: {},
errors: new Errors(),
formOpen: false,
@@ -36,33 +36,33 @@ export default {
methods: {
changeDefaultAddress(address) {
this.$set(this.defaultAddress, 'address_id', address.id);
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);
});
axios
.post(route("account.change_default_address"), {
address_id: address.id,
})
.then((response) => {
this.$notify(response.data);
})
.catch((error) => {
this.$notify(error.response.data.message);
});
},
changeCountry(country) {
this.form.country = country;
this.form.state = '';
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);
});
async fetchStates(country) {
const response = await axios.get(
route("countries.states.index", { code: country })
);
this.$set(this, "states", response.data);
},
edit(address) {
@@ -74,19 +74,21 @@ export default {
},
remove(address) {
if (! confirm(this.$trans('storefront::account.addresses.confirm'))) {
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);
});
axios
.delete(route("account.addresses.destroy", address.id))
.then((response) => {
this.$delete(this.addresses, address.id);
this.$notify(response.data.message);
})
.catch((error) => {
this.$notify(error.response.data.message);
});
},
cancel() {
@@ -108,56 +110,62 @@ export default {
},
update() {
$.ajax({
method: 'PUT',
url: route('account.addresses.update', { id: this.form.id }),
data: this.form,
}).then((response) => {
this.formOpen = false;
this.editing = false;
axios
.put(
route("account.addresses.update", { id: this.form.id }),
this.form
)
.then(({ data }) => {
this.formOpen = false;
this.editing = false;
this.addresses[this.form.id] = response.address;
this.addresses[this.form.id] = data.address;
this.resetForm();
this.$notify(response.message);
}).catch((xhr) => {
if (xhr.status === 422) {
this.errors.record(xhr.responseJSON.errors);
}
this.resetForm();
this.$notify(data.message);
})
.catch(({ response }) => {
if (response.status === 422) {
this.errors.record(response.data.errors);
}
this.$notify(xhr.responseJSON.message);
}).always(() => {
this.loading = false;
});
this.$notify(response.data.message);
})
.finally(() => {
this.loading = false;
});
},
create() {
$.ajax({
method: 'POST',
url: route('account.addresses.store'),
data: this.form,
}).then((response) => {
this.formOpen = false;
axios
.post(route("account.addresses.store"), this.form)
.then(({ data }) => {
this.formOpen = false;
let address = { [response.address.id]: response.address };
let address = { [data.address.id]: data.address };
this.$set(this, 'addresses', { ...this.addresses, ...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.resetForm();
this.$notify(data.message);
})
.catch(({ response }) => {
if (response.status === 422) {
this.errors.record(response.data.errors);
}
this.$notify(xhr.responseJSON.message);
}).always(() => {
this.loading = false;
});
this.$notify(response.data.message);
})
.finally(() => {
this.loading = false;
});
},
resetForm() {
this.form = { state: '' };
this.form = { state: "" };
},
},
};

View File

@@ -0,0 +1,57 @@
import ProductHelpersMixin from "../../../mixins/ProductHelpersMixin";
export default {
components: {
VPagination: () => import("../../VPagination.vue"),
},
mixins: [ProductHelpersMixin],
data() {
return {
fetchingReviews: false,
reviews: { data: [] },
currentPage: 1,
};
},
computed: {
reviewIsEmpty() {
return this.reviews.data.length === 0;
},
totalPage() {
return Math.ceil(this.reviews.total / 10);
},
},
created() {
this.fetchReviews();
},
methods: {
changePage(page) {
this.currentPage = page;
this.fetchReviews();
},
async fetchReviews() {
this.fetchingReviews = true;
try {
const response = await axios.get(
route("reviews.products.index", {
page: this.currentPage,
})
);
this.reviews = response.data;
} catch (error) {
this.$notify(error.response.data.message);
} finally {
this.fetchingReviews = false;
}
},
},
};

View File

@@ -1,13 +1,10 @@
import store from '../../../store';
import VPagination from '../../VPagination.vue';
import ProductHelpersMixin from '../../../mixins/ProductHelpersMixin';
import store from "../../../store";
import ProductHelpersMixin from "../../../mixins/ProductHelpersMixin";
export default {
components: { VPagination },
components: { VPagination: () => import("../../VPagination.vue") },
mixins: [
ProductHelpersMixin,
],
mixins: [ProductHelpersMixin],
data() {
return {
@@ -23,7 +20,7 @@ export default {
},
totalPage() {
return Math.ceil(this.products.total / 20);
return Math.ceil(this.products.total / 10);
},
},
@@ -32,21 +29,28 @@ export default {
},
methods: {
fetchWishlist() {
changePage(page) {
this.currentPage = page;
this.fetchWishlist();
},
async 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(() => {
try {
const response = await axios.get(
route("wishlist.products.index", {
page: this.currentPage,
})
);
this.products = response.data;
} catch (error) {
this.$notify(error.response.data.message);
} finally {
this.fetchingWishlist = false;
});
}
},
remove(product) {

View File

@@ -1,12 +1,9 @@
import store from '../../store';
import CartHelpersMixin from '../../mixins/CartHelpersMixin';
import ProductHelpersMixin from '../../mixins/ProductHelpersMixin';
import store from "../../store";
import CartHelpersMixin from "../../mixins/CartHelpersMixin";
import CartItemHelpersMixin from "../../mixins/CartItemHelpersMixin";
export default {
mixins: [
CartHelpersMixin,
ProductHelpersMixin,
],
mixins: [CartHelpersMixin, CartItemHelpersMixin],
data() {
return {
@@ -34,47 +31,25 @@ export default {
},
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;
}
updateCart(cartItem, qty) {
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;
});
axios
.put(route("cart.items.update", { cartItemId: cartItem.id }), {
qty: qty || 1,
})
.then((response) => {
store.updateCart(response.data);
})
.catch((error) => {
this.$notify(error.response.data.message);
})
.finally(() => {
this.loadingOrderSummary = false;
});
},
exceedsMaxStock(cartItem, qty) {
return cartItem.product.manage_stock && cartItem.product.qty < qty;
},
remove(cartItem) {
removeCartItem(cartItem) {
this.loadingOrderSummary = true;
store.removeCartItem(cartItem);
@@ -83,16 +58,19 @@ export default {
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;
});
axios
.delete(
route("cart.items.destroy", { cartItemId: cartItem.id })
)
.then((response) => {
store.updateCart(response.data);
})
.catch((error) => {
this.$notify(error.response.data.message);
})
.finally(() => {
this.loadingOrderSummary = false;
});
},
clearCart() {
@@ -102,29 +80,30 @@ export default {
this.crossSellProducts = [];
}
$.ajax({
method: 'POST',
url: route('cart.clear.store'),
}).then((cart) => {
store.updateCart(cart);
}).catch((xhr) => {
this.$notify(xhr.responseJSON.message);
});
axios
.post(route("cart.clear.store"))
.then((response) => {
store.updateCart(response.data);
})
.catch((error) => {
this.$notify(error.response.data.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);
});
async fetchCrossSellProducts() {
try {
const response = await axios.get(
route("cart.cross_sell_products.index")
);
this.crossSellProducts = response.data;
} catch (error) {
this.$notify(error.response.data.message);
}
},
},
};

View File

@@ -1,11 +1,10 @@
/* eslint-disable */
import store from "../../store";
import Errors from "../../Errors";
import CartHelpersMixin from "../../mixins/CartHelpersMixin";
import ProductHelpersMixin from "../../mixins/ProductHelpersMixin";
import CartItemHelpersMixin from "../../mixins/CartItemHelpersMixin";
export default {
mixins: [CartHelpersMixin, ProductHelpersMixin],
mixins: [CartHelpersMixin, CartItemHelpersMixin],
props: [
"customerEmail",
@@ -78,17 +77,13 @@ export default {
return this.gateways[this.form.payment_method].instructions;
}
},
hasFreeShipping() {
return this.cart.coupon?.free_shipping ?? false;
},
},
watch: {
"form.billingAddressId": function () {
this.mergeSavedBillingAddress();
},
"form.shippingAddressId": function () {
this.mergeSavedShippingAddress();
},
"form.billing.city": function (newCity) {
if (newCity) {
this.addTaxes();
@@ -156,6 +151,9 @@ export default {
if (this.defaultAddress.address_id) {
this.form.billingAddressId = this.defaultAddress.address_id;
this.form.shippingAddressId = this.defaultAddress.address_id;
this.mergeSavedBillingAddress();
this.mergeSavedShippingAddress();
}
if (!this.hasAddress) {
@@ -181,6 +179,14 @@ export default {
},
methods: {
changeBillingAddress(address) {
if (this.form.billingAddressId === address.id) return;
this.form.billingAddressId = address.id;
this.mergeSavedBillingAddress();
},
addNewBillingAddress() {
this.resetAddressErrors("billing");
@@ -192,6 +198,14 @@ export default {
}
},
changeShippingAddress(address) {
if (this.form.shippingAddressId === address.id) return;
this.form.shippingAddressId = address.id;
this.mergeSavedShippingAddress();
},
addNewShippingAddress() {
this.resetAddressErrors("shipping");
@@ -257,8 +271,8 @@ export default {
return;
}
this.fetchStates(country, (states) => {
this.$set(this.states, "billing", states);
this.fetchStates(country, (response) => {
this.$set(this.states, "billing", response.data);
this.$set(this.form.billing, "state", "");
});
},
@@ -273,17 +287,16 @@ export default {
return;
}
this.fetchStates(country, (states) => {
this.$set(this.states, "shipping", states);
this.fetchStates(country, (response) => {
this.$set(this.states, "shipping", response.data);
this.$set(this.form.shipping, "state", "");
});
},
fetchStates(country, callback) {
$.ajax({
method: "GET",
url: route("countries.states.index", { code: country }),
}).then(callback);
axios
.get(route("countries.states.index", { code: country }))
.then(callback);
},
changeBillingState(state) {
@@ -302,21 +315,74 @@ export default {
this.$set(this.form, "shipping_method", shippingMethodName);
},
addTaxes() {
async addTaxes() {
this.loadingOrderSummary = true;
$.ajax({
method: "POST",
url: route("cart.taxes.store"),
data: this.form,
})
.then((cart) => {
store.updateCart(cart);
try {
const response = await axios.post(
route("cart.taxes.store", {
...(store.hasCoupon() && {
coupon_code: store.getCoupon(),
}),
}),
this.form
);
store.updateCart(response.data);
} catch (error) {
this.$notify(error.response.data.message);
} finally {
this.loadingOrderSummary = false;
}
},
updateCart(cartItem, qty) {
this.loadingOrderSummary = true;
axios
.put(
route("cart.items.update", {
cartItemId: cartItem.id,
...(store.hasCoupon() && {
coupon_code: store.getCoupon(),
}),
}),
{
qty: qty || 1,
}
)
.then((response) => {
store.updateCart(response.data);
})
.catch((xhr) => {
this.$notify(xhr.responseJSON.message);
.catch((error) => {
this.$notify(error.response.data.message);
})
.always(() => {
.finally(() => {
this.loadingOrderSummary = false;
});
},
removeCartItem(cartItem) {
this.loadingOrderSummary = true;
store.removeCartItem(cartItem);
axios
.delete(
route("cart.items.destroy", {
cartItemId: cartItem.id,
...(store.hasCoupon() && {
coupon_code: store.getCoupon(),
}),
})
)
.then((response) => {
store.updateCart(response.data);
})
.catch((error) => {
this.$notify(error.response.data.message);
})
.finally(() => {
this.loadingOrderSummary = false;
});
},
@@ -328,68 +394,72 @@ export default {
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;
axios
.post(
route("checkout.store", {
...(store.hasCoupon() && {
coupon_code: store.getCoupon(),
}),
}),
{
...this.form,
ship_to_a_different_address:
+this.form.ship_to_a_different_address,
}
)
.then(({ data }) => {
if (data.redirectUrl) {
window.location.href = data.redirectUrl;
} else if (this.form.payment_method === "stripe") {
this.confirmStripePayment(response);
this.confirmStripePayment(data);
} else if (this.form.payment_method === "paytm") {
this.confirmPaytmPayment(response);
this.confirmPaytmPayment(data);
} else if (this.form.payment_method === "razorpay") {
this.confirmRazorpayPayment(response);
this.confirmRazorpayPayment(data);
} else if (this.form.payment_method === "paystack") {
this.confirmPaystackPayment(response);
this.confirmPaystackPayment(data);
} else if (this.form.payment_method === "authorizenet") {
this.confirmAuthorizeNetPayment(response);
this.confirmAuthorizeNetPayment(data);
} else if (this.form.payment_method === "flutterwave") {
this.confirmFlutterWavePayment(response);
this.confirmFlutterWavePayment(data);
} else if (this.form.payment_method === "mercadopago") {
this.confirmMercadoPagoPayment(response);
this.confirmMercadoPagoPayment(data);
} else {
this.confirmOrder(
response.orderId,
data.orderId,
this.form.payment_method
);
}
})
.catch((xhr) => {
if (xhr.status === 422) {
this.errors.record(xhr.responseJSON.errors);
.catch(({ response }) => {
if (response.status === 422) {
this.errors.record(response.data.errors);
}
this.$notify(xhr.responseJSON.message);
this.$notify(response.data.message);
this.placingOrder = false;
});
},
confirmOrder(orderId, paymentMethod, params = {}) {
$.ajax({
method: "GET",
url: route("checkout.complete.store", {
orderId,
paymentMethod,
...params,
}),
})
axios
.get(
route("checkout.complete.store", {
orderId,
paymentMethod,
...params,
})
)
.then(() => {
window.location.href = route("checkout.complete.show");
})
.catch((xhr) => {
.catch((error) => {
this.placingOrder = false;
this.loadingOrderSummary = false;
this.deleteOrder(orderId);
this.$notify(xhr.responseJSON.message);
this.$notify(error.response.data.message);
});
},
@@ -398,12 +468,11 @@ export default {
return;
}
$.ajax({
method: "GET",
url: route("checkout.payment_canceled.store", { orderId }),
}).then((xhr) => {
this.$notify(xhr.message);
});
axios
.get(route("checkout.payment_canceled.store", { orderId }))
.then((response) => {
this.$notify(response.data.message);
});
},
renderPayPalButton() {
@@ -414,31 +483,36 @@ export default {
.Buttons({
async createOrder() {
try {
response = await $.ajax({
method: "POST",
url: route("checkout.create"),
data: vm.form,
});
response = await axios.post(
route("checkout.create"),
vm.form
);
return response.resourceId;
} catch (xhr) {
if (xhr.status === 422) {
vm.errors.record(xhr.responseJSON.errors);
} else {
vm.$notify(xhr.responseJSON.message);
return response.data.resourceId;
} catch ({ response }) {
if (response.status === 422) {
vm.errors.record(response.data.errors);
return;
}
vm.$notify(response.data.message);
}
},
onApprove() {
vm.loadingOrderSummary = true;
vm.confirmOrder(response.orderId, "paypal", response);
vm.confirmOrder(
response.data.orderId,
"paypal",
response.data
);
},
onError() {
vm.deleteOrder(response.orderId);
vm.deleteOrder(response.data.orderId);
},
onCancel() {
vm.deleteOrder(response.orderId);
vm.deleteOrder(response.data.orderId);
},
})
.render("#paypal-button-container");
@@ -593,8 +667,8 @@ export default {
}).openIframe();
},
confirmAuthorizeNetPayment(authorizeNetOrder) {
this.authorizeNetToken = authorizeNetOrder.token;
confirmAuthorizeNetPayment({ token }) {
this.authorizeNetToken = token;
this.$nextTick(() => {
this.$refs.authorizeNetForm.submit();
@@ -643,7 +717,7 @@ export default {
confirmMercadoPagoPayment(mercadoPagoOrder) {
this.placingOrder = false;
const supportedLocales = {
const SUPPORTED_LOCALES = {
en_US: "en-US",
es_AR: "es-AR",
es_CL: "es-CL",
@@ -657,7 +731,8 @@ export default {
const mercadoPago = new MercadoPago(mercadoPagoOrder.publicKey, {
locale:
supportedLocales[mercadoPagoOrder.currentLocale] || "en-US",
SUPPORTED_LOCALES[mercadoPagoOrder.currentLocale] ||
"en-US",
});
mercadoPago.checkout({

View File

@@ -1,15 +1,10 @@
import store from '../../store';
import ProductRating from '../ProductRating.vue';
import ProductHelpersMixin from '../../mixins/ProductHelpersMixin';
import store from "../../store";
import ProductHelpersMixin from "../../mixins/ProductHelpersMixin";
export default {
components: { ProductRating },
mixins: [ProductHelpersMixin],
mixins: [
ProductHelpersMixin,
],
props: ['compare'],
props: ["compare"],
data() {
return {
@@ -39,10 +34,10 @@ export default {
methods: {
badgeClass(product) {
if (product.is_in_stock) {
return 'badge-success';
return "badge-success";
}
return 'badge-danger';
return "badge-danger";
},
hasAttribute(product, attribute) {
@@ -56,9 +51,11 @@ export default {
attributeValues(product, attribute) {
for (let productAttribute of product.attributes) {
if (productAttribute.name === attribute.name) {
return productAttribute.values.map((productAttributeValue) => {
return productAttributeValue.value;
}).join(', ');
return productAttribute.values
.map((productAttributeValue) => {
return productAttributeValue.value;
})
.join(", ");
}
}
},
@@ -66,14 +63,11 @@ export default {
remove(product) {
this.$delete(this.products, product.id);
if (! this.hasAnyProduct) {
if (!this.hasAnyProduct) {
this.relatedProducts = [];
}
$.ajax({
method: 'DELETE',
url: route('compare.destroy', { id: product.id }),
});
store.removeFromCompareList(product.id);
},
inWishlist(product) {
@@ -86,34 +80,40 @@ export default {
addToCart(product) {
if (product.options_count !== 0) {
return window.location.href = this.productUrl(product);
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);
axios
.post(
route("cart.items.store", {
product_id: product.id,
qty: 1,
})
)
.then((response) => {
store.updateCart(response.data);
$('.header-cart').trigger('click');
}).catch((xhr) => {
this.$notify(xhr.responseJSON.message);
});
$(".header-cart").trigger("click");
})
.catch((error) => {
this.$notify(error.response.data.message);
});
},
fetchRelatedProducts() {
async 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(() => {
try {
const response = await axios.get(
route("compare.related_products.index")
);
this.relatedProducts = response.data;
} catch (error) {
this.$notify(error.response.data.message);
} finally {
this.fetchingRelatedProducts = false;
});
}
},
},
};

View File

@@ -6,14 +6,14 @@
class="banner"
:target="banner.open_in_new_window ? '_blank' : '_self'"
>
<img :src="banner.image.path" alt="banner">
<img :src="banner.image.path" alt="Banner" />
</a>
</div>
</section>
</template>
<script>
export default {
props: ['banner'],
};
export default {
props: ["banner"],
};
</script>

View File

@@ -6,9 +6,13 @@
<a
:href="data.banner_1.call_to_action_url"
class="banner"
:target="data.banner_1.open_in_new_window ? '_blank' : '_self'"
:target="
data.banner_1.open_in_new_window
? '_blank'
: '_self'
"
>
<img :src="data.banner_1.image.path" alt="banner">
<img :src="data.banner_1.image.path" alt="Banner" />
</a>
</div>
@@ -16,9 +20,13 @@
<a
:href="data.banner_2.call_to_action_url"
class="banner"
:target="data.banner_2.open_in_new_window ? '_blank' : '_self'"
:target="
data.banner_2.open_in_new_window
? '_blank'
: '_self'
"
>
<img :src="data.banner_2.image.path" alt="banner">
<img :src="data.banner_2.image.path" alt="Banner" />
</a>
</div>
@@ -26,9 +34,13 @@
<a
:href="data.banner_3.call_to_action_url"
class="banner"
:target="data.banner_3.open_in_new_window ? '_blank' : '_self'"
:target="
data.banner_3.open_in_new_window
? '_blank'
: '_self'
"
>
<img :src="data.banner_3.image.path" alt="banner">
<img :src="data.banner_3.image.path" alt="Banner" />
</a>
</div>
</div>
@@ -37,7 +49,7 @@
</template>
<script>
export default {
props: ['data'],
};
export default {
props: ["data"],
};
</script>

View File

@@ -9,9 +9,13 @@
<a
:href="data.banner_1.call_to_action_url"
class="banner"
:target="data.banner_1.open_in_new_window ? '_blank' : '_self'"
:target="
data.banner_1.open_in_new_window
? '_blank'
: '_self'
"
>
<img :src="data.banner_1.image.path" alt="banner">
<img :src="data.banner_1.image.path" alt="Banner" />
</a>
</div>
@@ -19,9 +23,13 @@
<a
:href="data.banner_2.call_to_action_url"
class="banner"
:target="data.banner_2.open_in_new_window ? '_blank' : '_self'"
:target="
data.banner_2.open_in_new_window
? '_blank'
: '_self'
"
>
<img :src="data.banner_2.image.path" alt="banner">
<img :src="data.banner_2.image.path" alt="Banner" />
</a>
</div>
@@ -29,9 +37,13 @@
<a
:href="data.banner_3.call_to_action_url"
class="banner"
:target="data.banner_3.open_in_new_window ? '_blank' : '_self'"
:target="
data.banner_3.open_in_new_window
? '_blank'
: '_self'
"
>
<img :src="data.banner_3.image.path" alt="banner">
<img :src="data.banner_3.image.path" alt="Banner" />
</a>
</div>
</div>
@@ -40,7 +52,7 @@
</template>
<script>
export default {
props: ['data'],
};
export default {
props: ["data"],
};
</script>

View File

@@ -6,9 +6,13 @@
<a
:href="data.banner_1.call_to_action_url"
class="banner"
:target="data.banner_1.open_in_new_window ? '_blank' : '_self'"
:target="
data.banner_1.open_in_new_window
? '_blank'
: '_self'
"
>
<img :src="data.banner_1.image.path" alt="banner">
<img :src="data.banner_1.image.path" alt="Banner" />
</a>
</div>
@@ -16,9 +20,13 @@
<a
:href="data.banner_2.call_to_action_url"
class="banner"
:target="data.banner_2.open_in_new_window ? '_blank' : '_self'"
:target="
data.banner_2.open_in_new_window
? '_blank'
: '_self'
"
>
<img :src="data.banner_2.image.path" alt="banner">
<img :src="data.banner_2.image.path" alt="Banner" />
</a>
</div>
</div>
@@ -27,7 +35,7 @@
</template>
<script>
export default {
props: ['data'],
};
export default {
props: ["data"],
};
</script>

View File

@@ -19,7 +19,7 @@ export default {
return this.initialLogo.path;
}
return `${window.FleetCart.baseUrl}/themes/storefront/public/images/image-placeholder.png`;
return `${window.FleetCart.baseUrl}/build/assets/image-placeholder.png`;
},
},

View File

@@ -15,16 +15,28 @@
@click="change(tab)"
>
<div class="featured-category-image">
<img :src="tab.logo" :class="{ 'image-placeholder': ! tab.hasLogo }" alt="category logo">
<img
:src="tab.logo"
:class="{ 'image-placeholder': !tab.hasLogo }"
alt="Category logo"
/>
</div>
<span class="featured-category-name">{{ tab.label }}</span>
<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 class="tab-content">
<div class="featured-category-products">
<ProductCard
v-for="product in products"
:key="product.id"
:product="product"
/>
</div>
</div>
</div>
@@ -33,88 +45,76 @@
:key="index"
:label="category.name"
:initial-logo="category.logo"
:url="route('storefront.featured_category_products.index', { categoryNumber: index + 1 })"
: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';
import { slickPrevArrow, slickNextArrow } from "../../functions";
import ProductCard from "../ProductCard.vue";
import DynamicTabsMixin from "../../mixins/DynamicTabsMixin";
export default {
components: { ProductCard },
export default {
components: { ProductCard },
mixins: [
DynamicTabsMixin,
],
mixins: [DynamicTabsMixin],
props: ['data'],
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,
},
},
],
};
},
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: 768,
settings: {
slidesToShow: 3,
slidesToScroll: 3,
},
},
{
breakpoint: 577,
settings: {
slidesToShow: 2,
slidesToScroll: 2,
},
},
],
};
},
},
};
</script>

View File

@@ -1,75 +1,89 @@
<template>
<div class="col-xl-6 col-lg-18">
<div class="col-xl-6 col-lg-18" v-if="hasAnyProduct">
<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"/>
<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';
import { slickPrevArrow, slickNextArrow } from "../../functions";
import FlashSaleProductCard from "./FlashSaleProductCard.vue";
import("countdown").then((module) => (window.countdown = module.default));
export default {
components: { FlashSaleProductCard },
export default {
components: { FlashSaleProductCard },
props: ['title', 'url'],
props: ["title", "url"],
data() {
return {
products: [],
};
data() {
return {
products: [],
};
},
computed: {
hasAnyProduct() {
return this.products.length !== 0;
},
},
created() {
$.ajax({
method: 'GET',
url: this.url,
}).then((products) => {
this.products = products;
created() {
this.fetchProducts();
},
methods: {
async fetchProducts() {
await axios.get(this.url).then((response) => {
this.products = response.data;
this.$nextTick(() => {
$(this.$refs.productsPlaceholder).slick(this.slickOptions());
$(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,
},
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,
},
},
{
breakpoint: 768,
settings: {
slidesToShow: 1,
slidesToScroll: 1,
},
],
};
},
},
],
};
},
};
},
};
</script>

View File

@@ -1,28 +1,49 @@
<template>
<section class="vertical-products-wrap">
<section
class="vertical-products-wrap"
:class="{
'flash-sale-enabled': flashSaleEnabled,
}"
>
<div class="container">
<div class="row">
<flash-sale
v-if="flashSaleEnabled"
:title="data.flash_sale_title"
:url="route('storefront.flash_sale_products.index')"
>
</flash-sale>
<vertical-products
:flash-sale-enabled="flashSaleEnabled"
:title="data.vertical_products_1_title"
:url="route('storefront.vertical_products.index', { columnNumber: 1 })"
:url="
route('storefront.vertical_products.index', {
columnNumber: 1,
})
"
>
</vertical-products>
<vertical-products
:flash-sale-enabled="flashSaleEnabled"
:title="data.vertical_products_2_title"
:url="route('storefront.vertical_products.index', { columnNumber: 2 })"
:url="
route('storefront.vertical_products.index', {
columnNumber: 2,
})
"
>
</vertical-products>
<vertical-products
:flash-sale-enabled="flashSaleEnabled"
:title="data.vertical_products_3_title"
:url="route('storefront.vertical_products.index', { columnNumber: 3 })"
:url="
route('storefront.vertical_products.index', {
columnNumber: 3,
})
"
>
</vertical-products>
</div>
@@ -31,7 +52,12 @@
</template>
<script>
export default {
props: ['data'],
};
export default {
components: {
FlashSale: () => import("./FlashSale.vue"),
VerticalProducts: () => import("./VerticalProducts.vue"),
},
props: ["data", "flashSaleEnabled"],
};
</script>

View File

@@ -2,7 +2,11 @@
<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">
<img
:src="baseImage"
:class="{ 'image-placeholder': !hasBaseImage }"
:alt="product.name"
/>
</a>
</div>
@@ -11,22 +15,26 @@
</a>
<div class="product-info">
<div class="product-price" v-html="product.formatted_price"></div>
<div class="product-price" v-html="item.formatted_price"></div>
<ProductRating :ratingPercent="product.rating_percent" :reviewCount="product.reviews.length"/>
<product-rating
:ratingPercent="product.rating_percent"
:reviewCount="product.reviews.length"
>
</product-rating>
</div>
<div class="daily-deals-countdown clearfix"></div>
<countdown :end-date="product.pivot.end_date"></countdown>
<div class="deal-progress">
<div class="deal-stock">
<div class="stock-available">
{{ $trans('storefront::product_card.available') }}
{{ $trans("storefront::product_card.available") }}
<span>{{ product.pivot.qty }}</span>
</div>
<div class="stock-sold">
{{ $trans('storefront::product_card.sold') }}
{{ $trans("storefront::product_card.sold") }}
<span>{{ product.pivot.sold }}</span>
</div>
</div>
@@ -39,38 +47,30 @@
</template>
<script>
import ProductRating from '../ProductRating.vue';
import ProductCardMixin from '../../mixins/ProductCardMixin';
import Countdown from "../Countdown.vue";
import ProductCardMixin from "../../mixins/ProductCardMixin";
export default {
components: { ProductRating },
export default {
components: { Countdown },
mixins: [
ProductCardMixin,
],
mixins: [ProductCardMixin],
props: ['product'],
props: ["product"],
computed: {
progress() {
return (this.product.pivot.sold / this.product.pivot.qty * 100) + '%';
data() {
return {
item: {
...(this.product.variant ? this.product.variant : this.product),
},
},
};
},
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'),
],
});
computed: {
progress() {
return (
(this.product.pivot.sold / this.product.pivot.qty) * 100 + "%"
);
},
};
},
};
</script>

View File

@@ -2,7 +2,7 @@
<section class="grid-products-wrap clearfix">
<div class="container">
<div class="tab-products-header clearfix">
<ul class="tabs float-left">
<ul class="tabs float-left overflow-hidden">
<li
v-for="(tab, index) in tabs"
:key="index"
@@ -14,9 +14,21 @@
</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 class="tab-content">
<div class="grid-products">
<div
v-for="(productChunks, index) in $chunk(products, 12)"
:key="index"
class="grid-products-slide d-flex flex-wrap flex-grow-1"
>
<div
class="grid-products-item"
v-for="product in productChunks"
:key="product.id"
>
<ProductCard :product="product" />
</div>
</div>
</div>
</div>
@@ -24,7 +36,11 @@
v-for="(tabLabel, index) in data"
:key="index"
:label="tabLabel"
:url="route('storefront.product_grid.index', { tabNumber: index + 1 })"
:url="
route('storefront.product_grid.index', {
tabNumber: index + 1,
})
"
>
</dynamic-tab>
</div>
@@ -32,46 +48,44 @@
</template>
<script>
import ProductCard from '../ProductCard.vue';
import DynamicTabsMixin from '../../mixins/DynamicTabsMixin';
import { slickPrevArrow, slickNextArrow } from '../../functions';
import { slickPrevArrow, slickNextArrow } from "../../functions";
import ProductCard from "../ProductCard.vue";
import DynamicTabsMixin from "../../mixins/DynamicTabsMixin";
export default {
components: { ProductCard },
export default {
components: { ProductCard },
mixins: [
DynamicTabsMixin,
],
mixins: [DynamicTabsMixin],
props: ['data'],
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,
},
},
],
};
},
methods: {
selector() {
return $(".grid-products");
},
};
slickOptions() {
return {
rows: 0,
dots: false,
arrows: true,
infinite: false,
slidesToShow: 1,
slidesToScroll: 1,
rtl: window.FleetCart.rtl,
prevArrow: slickPrevArrow(),
nextArrow: slickNextArrow(),
responsive: [
{
breakpoint: 768,
settings: {
dots: true,
arrows: false,
},
},
],
};
},
},
};
</script>

View File

@@ -14,12 +14,14 @@
</ul>
</div>
<div class="tab-content landscape-left-tab-products">
<ProductCard
v-for="product in products"
:key="product.id"
:product="product"
/>
<div class="tab-content">
<div class="landscape-left-tab-products">
<ProductCard
v-for="product in products"
:key="product.id"
:product="product"
/>
</div>
</div>
<dynamic-tab
@@ -39,9 +41,9 @@
</template>
<script>
import { slickPrevArrow, slickNextArrow } from "../../functions";
import ProductCard from "../ProductCard.vue";
import DynamicTabsMixin from "../../mixins/DynamicTabsMixin";
import { slickPrevArrow, slickNextArrow } from "../../functions";
export default {
components: { ProductCard },
@@ -103,7 +105,7 @@ export default {
},
},
{
breakpoint: 661,
breakpoint: 768,
settings: {
dots: true,
arrows: false,

View File

@@ -16,8 +16,14 @@
</ul>
</div>
<div class="tab-content landscape-right-tab-products">
<ProductCard v-for="product in products" :key="product.id" :product="product"/>
<div class="tab-content">
<div class="landscape-right-tab-products">
<ProductCard
v-for="product in products"
:key="product.id"
:product="product"
/>
</div>
</div>
</div>
@@ -25,83 +31,95 @@
v-for="(tabLabel, index) in data.tabs"
:key="index"
:label="tabLabel"
:url="route('storefront.tab_products.index', {sectionNumber: 2, tabNumber: index + 1 })"
: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';
import ProductCard from "../ProductCard.vue";
import DynamicTabsMixin from "../../mixins/DynamicTabsMixin";
export default {
components: { ProductCard },
export default {
components: { ProductCard },
mixins: [DynamicTabsMixin],
mixins: [DynamicTabsMixin],
props: ['data'],
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,
},
},
],
};
},
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: 768,
settings: {
slidesToShow: 3,
slidesToScroll: 3,
},
},
{
breakpoint: 641,
settings: {
slidesToShow: 2,
slidesToScroll: 2,
},
},
],
};
},
},
};
</script>

View File

@@ -1,14 +1,14 @@
<template>
<section class="top-brands-wrap clearfix">
<div class="container">
<div class="top-brands clearfix">
<div class="top-brands overflow-hidden clearfix">
<a
v-for="(topBrand, index) in topBrands"
:key="index"
:href="topBrand.url"
class="top-brand-image"
class="top-brand-item d-inline-flex align-items-center justify-content-center overflow-hidden"
>
<img :src="topBrand.logo.path" alt="brand logo">
<img :src="topBrand.logo.path" alt="Brand logo" />
</a>
</div>
</div>
@@ -16,7 +16,62 @@
</template>
<script>
export default {
props: ['topBrands'],
};
export default {
props: ["topBrands"],
mounted() {
$(".top-brands").slick(this.slickOptions());
},
methods: {
slickOptions() {
return {
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: 450,
settings: {
slidesToShow: 2,
slidesToScroll: 2,
},
},
],
};
},
},
};
</script>

View File

@@ -1,13 +1,24 @@
<template>
<div class="col-xl-4 col-lg-6">
<div
:class="flashSaleEnabled ? 'col-xl-4 col-lg-6' : 'col-xl-6 col-lg-6'"
v-if="hasAnyProduct"
>
<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
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>
@@ -15,44 +26,53 @@
</template>
<script>
import ProductCardVertical from '../ProductCardVertical.vue';
import ProductCardVertical from "../ProductCardVertical.vue";
export default {
components: { ProductCardVertical },
export default {
components: { ProductCardVertical },
props: ['title', 'url'],
props: ["flashSaleEnabled", "title", "url"],
data() {
return {
products: [],
};
data() {
return {
products: [],
};
},
computed: {
hasAnyProduct() {
return this.products.length !== 0;
},
},
created() {
$.ajax({
method: 'GET',
url: this.url,
}).then((products) => {
this.products = products;
created() {
this.fetchProducts();
},
methods: {
async fetchProducts() {
await axios.get(this.url).then((response) => {
this.products = response.data;
this.$nextTick(() => {
$(this.$refs.productsPlaceholder).slick(this.slickOptions());
$(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,
};
},
slickOptions() {
return {
rows: 0,
dots: false,
arrows: true,
infinite: true,
slidesToShow: 1,
slidesToScroll: 1,
rtl: window.FleetCart.rtl,
};
},
};
},
};
</script>

View File

@@ -15,10 +15,7 @@ export default {
accept() {
this.show = false;
$.ajax({
method: 'DELETE',
url: route('storefront.cookie_bar.destroy'),
});
axios.delete(route("storefront.cookie_bar.destroy"));
},
},
};

View File

@@ -12,6 +12,7 @@
:placeholder="
$trans('storefront::layout.search_for_products')
"
@focus="showExistingSuggestions"
@keydown.esc="hideSuggestions"
@keydown.down="nextSuggestion"
@keydown.up="prevSuggestion"
@@ -92,7 +93,7 @@
<div class="header-search-sm-form">
<form class="search-form" @submit.prevent="search">
<div class="btn-close">
<div class="btn-close" @click="showSuggestions = false">
<i class="las la-arrow-left"></i>
</div>
@@ -107,6 +108,7 @@
"
:value="form.query"
@input="form.query = $event.target.value"
@focus="showExistingSuggestions"
@keydown.esc="hideSuggestions"
@keydown.down="nextSuggestion"
@keydown.up="prevSuggestion"
@@ -118,7 +120,10 @@
</form>
</div>
<div class="search-suggestions" v-if="shouldShowSuggestions">
<div
class="search-suggestions overflow-hidden"
v-if="shouldShowSuggestions"
>
<div
class="search-suggestions-inner custom-scrollbar"
ref="searchSuggestionsInner"
@@ -179,7 +184,7 @@
'image-placeholder':
!hasBaseImage(product),
}"
alt="product image"
:alt="product.name"
/>
</div>
@@ -212,31 +217,29 @@
</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>
<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>
</template>
<script>
import ProductHelpersMixin from "../../mixins/ProductHelpersMixin";
import { throttle } from "lodash";
export default {
mixins: [ProductHelpersMixin],
props: [
"categories",
"mostSearchedKeywords",
@@ -303,7 +306,7 @@ export default {
},
watch: {
"form.query": function (newQuery) {
"form.query": throttle(function (newQuery) {
if (newQuery === "") {
this.clearSuggestions();
} else {
@@ -311,7 +314,7 @@ export default {
this.fetchSuggestions();
}
},
}, 600),
},
methods: {
@@ -322,17 +325,16 @@ export default {
},
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;
axios
.get(route("suggestions.index", this.form))
.then(({ data }) => {
this.suggestions.categories = data.categories;
this.suggestions.products = data.products;
this.suggestions.remaining = data.remaining;
this.clearActiveSuggestion();
this.resetSuggestionScrollBar();
});
this.clearActiveSuggestion();
this.resetSuggestionScrollBar();
});
},
search() {
@@ -362,12 +364,18 @@ export default {
});
},
showExistingSuggestions() {
if (this.form.query !== "") {
this.showSuggestions = true;
}
},
clearSuggestions() {
this.suggestions.categories = [];
this.suggestions.products = [];
},
hideSuggestions(e) {
hideSuggestions() {
this.showSuggestions = false;
this.clearActiveSuggestion();
@@ -447,6 +455,22 @@ export default {
this.$refs.searchSuggestionsInner.scrollTop = 0;
}
},
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}/build/assets/image-placeholder.png`;
},
},
};
</script>

View File

@@ -1,17 +1,17 @@
export default {
data() {
return {
email: '',
email: "",
subscribed: false,
subscribing: false,
error: '',
error: "",
disable_popup: false,
};
},
watch: {
email() {
this.error = '';
this.error = "";
},
disable_popup(bool) {
@@ -25,48 +25,46 @@ export default {
mounted() {
setTimeout(() => {
$('.newsletter-wrap').modal('show');
$(".newsletter-wrap").modal("show");
}, 1000);
},
methods: {
enableNewsletterPopup() {
$.ajax({
method: 'POST',
url: route('storefront.newsletter_popup.store'),
});
axios.post(route("storefront.newsletter_popup.store"));
},
disableNewsletterPopup() {
$.ajax({
method: 'DELETE',
url: route('storefront.newsletter_popup.destroy'),
});
axios.delete(route("storefront.newsletter_popup.destroy"));
},
subscribe() {
if (! this.email || this.subscribed) {
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;
});
axios
.post(route("subscribers.store"), {
email: this.email,
})
.then(() => {
this.email = "";
this.subscribed = true;
})
.catch((error) => {
if (error.response.status === 422) {
this.error = error.response.data.errors.email[0];
return;
}
this.error = error.response.data.message;
})
.finally(() => {
this.subscribing = false;
});
},
},
};

View File

@@ -1,7 +1,7 @@
export default {
data() {
return {
email: '',
email: "",
subscribed: false,
subscribing: false,
};
@@ -9,28 +9,32 @@ export default {
methods: {
subscribe() {
if (! this.email || this.subscribed) {
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;
});
axios
.post(route("subscribers.store"), {
email: this.email,
})
.then(() => {
this.email = "";
this.subscribed = true;
})
.catch((error) => {
if (error.response.status === 422) {
this.$notify(error.response.data.errors.email[0]);
return;
}
this.$notify(error.response.data.message);
})
.finally(() => {
this.subscribing = false;
});
},
},
};

View File

@@ -1,8 +1,7 @@
import store from '../../store';
import SidebarCartItem from './SidebarCartItem.vue';
import store from "../../store";
export default {
components: { SidebarCartItem },
components: { SidebarCartItem: () => import("./SidebarCartItem.vue") },
computed: {
cart() {
@@ -14,7 +13,30 @@ export default {
},
cartIsNotEmpty() {
return ! store.cartIsEmpty();
return !store.cartIsEmpty();
},
},
mounted() {
this.initEventListeners();
},
methods: {
initEventListeners() {
const sidebarCartWrap = $(".sidebar-cart-wrap");
const overlay = $(".overlay");
$(".header-cart").on("click", (e) => {
e.stopPropagation();
overlay.addClass("active");
sidebarCartWrap.addClass("active");
});
$(".sidebar-cart-close").on("click", () => {
overlay.removeClass("active");
sidebarCartWrap.removeClass("active");
});
},
},
};

View File

@@ -1,26 +1,98 @@
<template>
<div class="sidebar-cart-item">
<a :href="productUrl(cartItem.product)" class="product-image">
<div class="cart-item sidebar-cart-item">
<a :href="productUrl(cartItem)" class="product-image">
<img
:src="baseImage(cartItem.product)"
:class="{ 'image-placeholder': ! hasBaseImage(cartItem.product) }"
alt="product image"
>
:src="baseImage(cartItem)"
:class="{
'image-placeholder': !hasBaseImage(cartItem),
}"
:alt="cartItem.product.name"
/>
</a>
<div class="product-info">
<a :href="productUrl(cartItem.product)" class="product-name" :title="cartItem.product.name">
<a
:href="productUrl(cartItem)"
class="product-name"
:title="cartItem.product.name"
>
{{ cartItem.product.name }}
</a>
<ul class="list-inline product-options">
<ul
v-if="Object.entries(cartItem.variations).length !== 0"
class="list-inline product-options"
>
<li
v-for="(variation, key, index) in cartItem.variations"
:key="index"
>
<label>{{ variation.name }}:</label>
{{ variation.values[0].label
}}{{
index === Object.values(cartItem.variations).length - 1
? ""
: ","
}}
</li>
</ul>
<ul
v-if="Object.entries(cartItem.options).length !== 0"
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 class="d-flex align-items-center justify-content-between mt-1">
<div class="number-picker">
<div class="input-group-quantity">
<button
type="button"
class="btn btn-number btn-minus"
:disabled="cartItem.qty <= 1"
@click="updateQuantity(cartItem, cartItem.qty - 1)"
>
-
</button>
<input
type="text"
:value="cartItem.qty"
min="1"
:max="maxQuantity(cartItem)"
class="form-control input-number input-quantity"
@focus="$event.target.select()"
@input="
changeQuantity(
cartItem,
Number($event.target.value)
)
"
@keydown.up="
updateQuantity(cartItem, cartItem.qty + 1)
"
@keydown.down="
updateQuantity(cartItem, cartItem.qty - 1)
"
/>
<button
type="button"
class="btn btn-number btn-plus"
:disabled="isQtyIncreaseDisabled(cartItem)"
@click="updateQuantity(cartItem, cartItem.qty + 1)"
>
+
</button>
</div>
</div>
<div class="product-price">
x {{ cartItem.unitPrice.inCurrentCurrency.formatted }}
</div>
</div>
</div>
@@ -33,37 +105,52 @@
</template>
<script>
import store from '../../store';
import ProductHelpersMixin from '../../mixins/ProductHelpersMixin';
import store from "../../store";
import CartItemHelpersMixin from "../../mixins/CartItemHelpersMixin";
export default {
mixins: [
ProductHelpersMixin,
],
export default {
mixins: [CartItemHelpersMixin],
props: ['cartItem'],
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);
methods: {
updateCart(cartItem, qty) {
axios
.put(
route("cart.items.update", {
cartItemId: cartItem.id,
...(store.hasCoupon() && {
coupon_code: store.getCoupon(),
}),
}),
{
qty: qty || 1,
}
)
.then((response) => {
store.updateCart(response.data);
})
.catch((error) => {
this.$notify(error.response.data.message);
});
},
},
};
remove() {
store.removeCartItem(this.cartItem);
axios
.delete(
route("cart.items.destroy", {
cartItemId: this.cartItem.id,
...(store.hasCoupon() && {
coupon_code: store.getCoupon(),
}),
})
)
.then((response) => {
store.updateCart(response.data);
});
},
},
};
</script>

View File

@@ -1,8 +1,9 @@
import noUiSlider from "nouislider";
import { trans } from "../../functions";
import { collapseFilters } from "./index/helpers";
import noUiSlider from "nouislider";
export default {
components: { VPagination: () => import("../VPagination.vue") },
props: [
"initialQuery",
"initialBrandName",
@@ -14,11 +15,12 @@ export default {
"initialTagName",
"initialTagSlug",
"initialAttribute",
"minPrice",
"maxPrice",
"initialSort",
"initialPerPage",
"initialPage",
"initialViewMode"
"initialViewMode",
],
data() {
@@ -40,8 +42,8 @@ export default {
toPrice: this.maxPrice,
sort: this.initialSort,
perPage: this.initialPerPage,
page: this.initialPage
}
page: this.initialPage,
},
};
},
@@ -62,26 +64,27 @@ export default {
return trans("storefront::products.showing_results", {
from: this.products.from,
to: this.products.to,
total: this.products.total
total: this.products.total,
});
}
},
},
mounted() {
this.addEventListeners();
this.initPriceFilter();
this.fetchProducts();
this.initLatestProductsSlider();
},
methods: {
addEventListeners() {
$(this.$refs.sortSelect).on("change", e => {
$(this.$refs.sortSelect).on("change", (e) => {
this.queryParams.sort = e.currentTarget.value;
this.fetchProducts();
});
$(this.$refs.perPageSelect).on("change", e => {
$(this.$refs.perPageSelect).on("change", (e) => {
this.queryParams.perPage = e.currentTarget.value;
this.fetchProducts();
@@ -92,11 +95,11 @@ export default {
noUiSlider.create(this.$refs.priceRange, {
connect: true,
direction: window.FleetCart.rtl ? "rtl" : "ltr",
start: [0, this.maxPrice],
start: [this.minPrice, this.maxPrice],
range: {
min: [0],
max: [this.maxPrice]
}
min: [this.minPrice],
max: [this.maxPrice],
},
});
this.$refs.priceRange.noUiSlider.on("update", (values, handle) => {
@@ -160,29 +163,36 @@ export default {
this.fetchProducts();
},
fetchProducts(options = { updateAttributeFilters: true }) {
async fetchProducts(options = { updateAttributeFilters: true }) {
this.fetchingProducts = true;
$.ajax({
url: route("products.index", this.queryParams)
})
.then(response => {
this.products = response.products;
try {
const response = await axios.get(
route("products.index", this.queryParams)
);
if (options.updateAttributeFilters) {
this.attributeFilters = response.attributes;
}
this.products = response.data.products;
this.$nextTick(() => {
collapseFilters();
});
})
.catch(xhr => {
this.$notify(xhr.responseJSON.message);
})
.always(() => {
this.fetchingProducts = false;
});
}
}
if (options.updateAttributeFilters) {
this.attributeFilters = response.data.attributes;
}
} catch (error) {
this.$notify(error.response.data.message);
} finally {
this.fetchingProducts = false;
}
},
initLatestProductsSlider() {
$(this.$refs.latestProducts).slick({
rows: 0,
dots: false,
arrows: true,
infinite: true,
slidesToShow: 1,
slidesToScroll: 1,
rtl: window.FleetCart.rtl,
});
},
},
};

View File

@@ -1,22 +1,41 @@
import store from '../../store';
import Errors from '../../Errors';
import ProductRating from '../ProductRating.vue';
import ProductCardMixin from '../../mixins/ProductCardMixin';
import RelatedProducts from './show/RelatedProducts.vue';
import store from "../../store";
import Errors from "../../Errors";
import GalleryMixin from "./show/mixins/GalleryMixin";
import VariationMixin from "./show/mixins/VariationMixin";
import VariantMixin from "./show/mixins/VariantMixin";
import OptionMixin from "./show/mixins/OptionMixin";
import QuantityMixin from "./show/mixins/QuantityMixin";
import ReviewMixin from "./show/mixins/ReviewMixin";
import ProductCardMixin from "../../mixins/ProductCardMixin";
export default {
components: { ProductRating, RelatedProducts },
components: {
VPagination: () => import("../VPagination.vue"),
RelatedProducts: () => import("./show/RelatedProducts.vue"),
},
mixins: [
GalleryMixin,
VariationMixin,
VariantMixin,
OptionMixin,
QuantityMixin,
ReviewMixin,
ProductCardMixin,
],
props: ['product', 'reviewCount', 'avgRating'],
props: ["product", "reviewCount", "avgRating"],
data() {
return {
item: {
...(this.product.variant ? this.product.variant : this.product),
},
oldMediaLength: 0,
activeVariationValues: {},
variationImagePath: null,
price: this.product.formatted_price,
activeTab: 'description',
activeTab: "description",
currentReviewPage: 1,
fetchingReviews: false,
reviews: {},
@@ -25,6 +44,7 @@ export default {
cartItemForm: {
product_id: this.product.id,
qty: 1,
variations: {},
options: {},
},
errors: new Errors(),
@@ -32,202 +52,68 @@ export default {
},
computed: {
totalReviews() {
if (! this.reviews.total) {
return this.reviewCount;
}
return this.reviews.total;
productUrl() {
return route("products.show", {
slug: this.product.slug,
...(this.hasAnyVariant && {
variant: this.item.uid,
}),
});
},
ratingPercent() {
return (this.avgRating / 5) * 100;
isActiveItem() {
return this.item.is_active === true;
},
emptyReviews() {
return this.totalReviews === 0;
isAddToCartDisabled() {
return this.item.is_out_of_stock ?? true;
},
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;
});
isMobileDevice() {
return window.matchMedia("only screen and (max-width: 991px)")
.matches;
},
addToCart() {
if (this.isAddToCartDisabled) return;
this.addingToCart = true;
$.ajax({
method: 'POST',
url: route('cart.items.store', this.cartItemForm),
}).then((cart) => {
store.updateCart(cart);
axios
.post(route("cart.items.store"), {
...this.cartItemForm,
...(this.hasAnyVariant && { variant_id: this.item.id }),
})
.then((response) => {
store.updateCart(response.data);
$('.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;
$(".header-cart").trigger("click");
})
.catch(({ response }) => {
if (response.status === 422) {
this.errors.record(response.data.errors);
return;
}
this.$notify(response.data.message);
})
.finally(() => {
this.addingToCart = false;
});
},
initUpSellProductsSlider() {
$(this.$refs.upSellProducts).slick({
rows: 0,
dots: false,
arrows: true,
infinite: true,
slidesToShow: 1,
slidesToScroll: 1,
rtl: window.FleetCart.rtl,
});
},
},

View File

@@ -1,15 +1,15 @@
<template>
<div class="col">
<ProductCard :product="product"/>
<div class="grid-view-products-item">
<ProductCard :product="product" />
</div>
</template>
<script>
import ProductCard from './../../ProductCard.vue';
import ProductCard from "./../../ProductCard.vue";
export default {
components: { ProductCard },
export default {
components: { ProductCard },
props: ['product'],
};
props: ["product"],
};
</script>

View File

@@ -1,24 +1,31 @@
<template>
<div class="list-product-card">
<div class="list-product-card-inner">
<div class="product-card-left">
<div class="product-card-left position-relative">
<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>
<img
:src="baseImage"
:class="{ 'image-placeholder': !hasBaseImage }"
:alt="product.name"
/>
</a>
<ul class="list-inline product-badge">
<li class="badge badge-danger" v-if="item.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="item.has_percentage_special_price"
>
-{{ item.special_price_percent }}%
</li>
</ul>
</div>
<div class="product-card-right">
@@ -28,19 +35,23 @@
<div class="clearfix"></div>
<ProductRating :ratingPercent="product.rating_percent" :reviewCount="product.reviews.length"/>
<product-rating
:ratingPercent="product.rating_percent"
:reviewCount="product.reviews.length"
>
</product-rating>
<div class="product-price" v-html="product.formatted_price"></div>
<div class="product-price" v-html="item.formatted_price"></div>
<button
v-if="hasNoOption || product.is_out_of_stock"
v-if="hasNoOption || item.is_out_of_stock"
class="btn btn-default btn-add-to-cart"
:class="{ 'btn-loading': addingToCart }"
:disabled="product.is_out_of_stock"
:disabled="item.is_out_of_stock"
@click="addToCart"
>
<i class="las la-cart-arrow-down"></i>
{{ $trans('storefront::product_card.add_to_cart') }}
{{ $trans("storefront::product_card.add_to_cart") }}
</button>
<a
@@ -49,26 +60,29 @@
class="btn btn-default btn-add-to-cart"
>
<i class="las la-eye"></i>
{{ $trans('storefront::product_card.view_options') }}
{{ $trans("storefront::product_card.view_options") }}
</a>
<div class="product-card-actions">
<button
class="btn btn-wishlist"
:class="{ 'added': inWishlist }"
:class="{ added: inWishlist }"
@click="syncWishlist"
>
<i class="la-heart" :class="inWishlist ? 'las' : 'lar'"></i>
{{ $trans('storefront::product_card.wishlist') }}
<i
class="la-heart"
:class="inWishlist ? 'las' : 'lar'"
></i>
{{ $trans("storefront::product_card.wishlist") }}
</button>
<button
class="btn btn-compare"
:class="{ 'added': inCompareList }"
:class="{ added: inCompareList }"
@click="syncCompareList"
>
<i class="las la-random"></i>
{{ $trans('storefront::product_card.compare') }}
{{ $trans("storefront::product_card.compare") }}
</button>
</div>
</div>
@@ -77,16 +91,19 @@
</template>
<script>
import ProductRating from './../../ProductRating.vue';
import ProductCardMixin from '../../../mixins/ProductCardMixin';
import ProductCardMixin from "../../../mixins/ProductCardMixin";
export default {
components: { ProductRating },
export default {
mixins: [ProductCardMixin],
mixins: [
ProductCardMixin,
],
props: ["product"],
props: ['product'],
};
data() {
return {
item: {
...(this.product.variant ? this.product.variant : this.product),
},
};
},
};
</script>

View File

@@ -1,97 +1,96 @@
<template>
<section class="landscape-products-wrap" v-if="hasAnyProduct">
<div class="products-header">
<h5 class="section-title">{{ $trans('storefront::product.related_products') }}</h5>
<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>
<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';
import { slickPrevArrow, slickNextArrow } from "../../../functions";
import ProductCard from "./../../ProductCard.vue";
export default {
components: { ProductCard },
export default {
components: { ProductCard },
props: ['products'],
props: ["products"],
computed: {
hasAnyProduct() {
return this.products.length !== 0;
},
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,
},
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: 1341,
settings: {
slidesToShow: 3,
slidesToScroll: 3,
},
{
breakpoint: 1081,
settings: {
slidesToShow: 2,
slidesToScroll: 2,
},
},
{
breakpoint: 992,
settings: {
slidesToShow: 4,
slidesToScroll: 4,
},
{
breakpoint: 992,
settings: {
slidesToShow: 4,
slidesToScroll: 4,
},
},
{
breakpoint: 881,
settings: {
slidesToShow: 3,
slidesToScroll: 3,
},
{
breakpoint: 881,
settings: {
slidesToShow: 3,
slidesToScroll: 3,
},
},
{
breakpoint: 661,
settings: {
dots: true,
arrows: false,
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,
},
{
breakpoint: 641,
settings: {
dots: true,
arrows: false,
slidesToShow: 2,
slidesToScroll: 2,
},
},
],
});
},
};
},
],
});
},
};
</script>

View File

@@ -0,0 +1,245 @@
import Drift from "drift-zoom";
import GLightbox from "glightbox";
let galleryPreviewSlider;
let galleryThumbnailSlider;
let galleryPreviewLightbox;
export default {
mounted() {
galleryPreviewSlider = this.initGalleryPreviewSlider();
galleryThumbnailSlider = this.initGalleryThumbnailSlider();
galleryPreviewLightbox = this.initGalleryPreviewLightbox();
this.initGalleryPreviewZoom();
this.initUpSellProductsSlider();
},
methods: {
initGalleryPreviewSlider() {
return $(".product-gallery-preview").slick({
rows: 0,
speed: 200,
fade: true,
dots: false,
swipe: false,
arrows: false,
infinite: false,
draggable: false,
slidesToShow: 1,
slidesToScroll: 1,
rtl: window.FleetCart.rtl,
});
},
initGalleryThumbnailSlider() {
return $(".product-gallery-thumbnail")
.on("setPosition", (_, slick) => {
if (slick.slideCount <= slick.options.slidesToShow) {
slick.$slideTrack.css("transform", "");
}
})
.slick({
rows: 0,
dots: false,
arrows: true,
infinite: false,
slidesToShow: 6,
slideToScroll: 1,
focusOnSelect: true,
rtl: window.FleetCart.rtl,
asNavFor: $(".product-gallery-preview"),
responsive: [
{
breakpoint: 1601,
settings: {
slidesToShow: 5,
},
},
{
breakpoint: 992,
settings: {
slidesToShow: 6,
},
},
{
breakpoint: 577,
settings: {
slidesToShow: 5,
},
},
{
breakpoint: 451,
settings: {
slidesToShow: 4,
},
},
],
});
},
updateGallerySlider() {
if (this.hasAnyMedia) {
this.addNewGallerySlides();
this.removeOldGallerySlides();
} else {
// Add empty placeholder slide if variant has no media
this.addGalleryEmptySlide();
this.removeOldGallerySlides();
}
this.addGalleryEventListeners();
},
addNewGallerySlides() {
this.item.media.forEach(({ path }, index) => {
this.addGalleryPreviewSlide(path, index);
this.addGalleryThumbnailSlide(path, index);
});
},
addGalleryPreviewSlide(filePath, slideIndex) {
galleryPreviewSlider.slick(
"slickAdd",
this.galleryPreviewSlideTemplate(filePath),
slideIndex,
true
);
},
addGalleryThumbnailSlide(filePath, slideIndex) {
galleryThumbnailSlider.slick(
"slickAdd",
this.galleryThumbnailSlideTemplate(filePath),
slideIndex,
true
);
},
addGalleryEmptySlide() {
const filePath = `${FleetCart.baseUrl}/build/assets/image-placeholder.png`;
galleryPreviewSlider.slick(
"slickAdd",
this.galleryPreviewEmptySlideTemplate(filePath),
null,
true
);
galleryThumbnailSlider.slick(
"slickAdd",
this.galleryThumbnailEmptySlideTemplate(filePath),
null,
true
);
},
removeOldGallerySlides() {
const slideCount =
galleryPreviewSlider.slick("getSlick").slideCount - 1;
[...Array(this.oldMediaLength)].forEach((_, index) => {
const slideIndex = slideCount - index;
galleryPreviewSlider.slick("slickRemove", slideIndex);
galleryThumbnailSlider.slick("slickRemove", slideIndex);
});
},
addGalleryEventListeners() {
this.$nextTick(() => {
galleryThumbnailSlider.slick("refresh");
galleryPreviewLightbox.reload();
this.initGalleryPreviewZoom();
});
},
initGalleryPreviewZoom() {
if (this.isMobileDevice()) {
this.initGalleryPreviewMobileZoom();
return;
}
this.initGalleryPreviewDesktopZoom();
},
initGalleryPreviewMobileZoom() {
[
...document.querySelectorAll(".gallery-preview-item > img"),
].forEach((el) => {
new Drift(el, {
namespace: "mobile-drift",
inlinePane: true,
});
});
},
initGalleryPreviewDesktopZoom() {
[
...document.querySelectorAll(".gallery-preview-item > img"),
].forEach((el) => {
new Drift(el, {
inlinePane: false,
hoverBoundingBox: true,
boundingBoxContainer: document.body,
paneContainer: document.querySelector(".product-gallery"),
});
});
},
initGalleryPreviewLightbox() {
return GLightbox({
zoomable: true,
preload: false,
});
},
galleryPreviewSlideTemplate(filePath) {
return `
<div class="gallery-preview-slide">
<div class="gallery-preview-item">
<img src="${filePath}" data-zoom="${filePath}" alt="${this.product.name}">
<a href="${filePath}" data-gallery="product-gallery-preview" class="gallery-view-icon glightbox">
<i class="las la-search-plus"></i>
</a>
</div>
</div>
`;
},
galleryThumbnailSlideTemplate(filePath) {
return `
<div class="gallery-thumbnail-slide">
<div class="gallery-thumbnail-item">
<img src="${filePath}" alt="${this.product.name}">
</div>
</div>
`;
},
galleryPreviewEmptySlideTemplate(filePath) {
return `
<div class="gallery-preview-slide">
<div class="gallery-preview-item">
<img src="${filePath}" data-zoom="${filePath}" alt="${this.product.name}" class="image-placeholder">
<a href="${filePath}" data-gallery="product-gallery-preview" class="gallery-view-icon glightbox">
<i class="las la-search-plus"></i>
</a>
</div>
</div>
`;
},
galleryThumbnailEmptySlideTemplate(filePath) {
return `
<div class="gallery-thumbnail-slide">
<div class="gallery-thumbnail-item">
<img src="${filePath}" alt="${this.product.name}" class="image-placeholder">
</div>
</div>
`;
},
},
};

View File

@@ -0,0 +1,61 @@
export default {
methods: {
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}`);
}
},
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}`);
}
},
},
};

View File

@@ -0,0 +1,57 @@
export default {
computed: {
maxQuantity() {
return this.item.is_in_stock && this.item.does_manage_stock
? this.item.qty
: null;
},
isQtyIncreaseDisabled() {
return (
this.item.is_out_of_stock ||
(this.maxQuantity !== null &&
this.cartItemForm.qty >= this.item.qty) ||
!this.isActiveItem
);
},
isQtyDecreaseDisabled() {
return (
this.item.is_out_of_stock ||
this.cartItemForm.qty <= 1 ||
!this.isActiveItem
);
},
},
methods: {
updateQuantity(qty) {
if (isNaN(qty) || qty < 1) {
this.cartItemForm.qty = 1;
return;
}
this.cartItemForm.qty = qty;
if (this.exceedsMaxStock(qty)) {
this.cartItemForm.qty = this.item.qty;
return;
}
},
exceedsMaxStock(qty) {
return this.item.does_manage_stock && this.item.qty < qty;
},
reduceToMaxQuantity() {
if (
this.item.does_manage_stock &&
this.cartItemForm.qty > this.item.qty
) {
this.cartItemForm.qty = this.item.qty;
}
},
},
};

View File

@@ -0,0 +1,89 @@
export default {
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 / 5);
},
},
created() {
this.fetchReviews();
},
methods: {
async fetchReviews() {
this.fetchingReviews = true;
try {
const response = await axios.get(
route("products.reviews.index", {
productId: this.product.id,
page: this.currentReviewPage,
})
);
this.reviews = response.data;
} catch (error) {
this.$notify(error.response.data.message);
} finally {
this.fetchingReviews = false;
}
},
addNewReview() {
this.addingNewReview = true;
axios
.post(
route("products.reviews.store", {
productId: this.product.id,
}),
this.reviewForm
)
.then((response) => {
this.reviewForm = {};
this.reviews.total++;
this.reviews.data.unshift(response.data);
this.$notify(
this.$trans("storefront::product.review_submitted")
);
})
.catch(({ response }) => {
if (response.status === 422) {
this.errors.record(response.data.errors);
return;
}
this.$notify(response.data.message);
})
.finally(() => {
this.addingNewReview = false;
$(".captcha-field img").trigger("click");
});
},
changeReviewPage(page) {
this.currentReviewPage = page;
this.fetchReviews();
},
},
};

View File

@@ -0,0 +1,57 @@
import md5 from "blueimp-md5";
export default {
methods: {
doesVariantExist(uid) {
return this.product.variants.some(({ uids }) => uids.includes(uid));
},
setOldMediaLength() {
this.oldMediaLength = this.hasAnyMedia ? this.item.media.length : 1;
},
setVariant() {
const selectedUids = Object.values(this.cartItemForm.variations)
.sort()
.join(".");
const variant = this.product.variants.find(
(variant) => variant.uids === selectedUids
);
if (variant !== undefined) {
this.item = { ...variant };
this.reduceToMaxQuantity();
return;
}
// Set empty variant data if variant does not exist
const uid = md5(
Object.values(this.cartItemForm.variations).sort().join(".")
);
this.item = {
uid,
media: [],
};
},
setVariantSlug() {
const url = route("products.show", {
slug: this.product.slug,
variant: this.item.uid,
});
window.history.replaceState({}, "", url);
},
updateVariantDetails() {
this.setOldMediaLength();
this.setVariant();
this.setVariantSlug();
this.updateGallerySlider();
},
},
};

View File

@@ -0,0 +1,106 @@
export default {
computed: {
hasAnyVariationImage() {
return this.variationImagePath !== null;
},
},
created() {
this.setActiveVariationsValue();
},
methods: {
isVariationValueEnabled(variationUid, variationIndex, valueUid) {
// Check if enabled first variation values
if (variationIndex === 0) {
return this.doesVariantExist(valueUid);
}
// Check if enabled variation values between first and last variation
if (
variationIndex > 0 &&
variationIndex < this.product.variations.length - 1
) {
return this.doesVariantExist(valueUid);
}
// Check if enabled last variation values
if (variationIndex === this.product.variations.length - 1) {
const variations = this.cartItemForm.variations;
const valueUids = Object.values(variations).filter(
(uid) => uid !== variations[variationUid]
);
valueUids.push(valueUid);
return this.doesVariantExist(valueUids.sort().join("."));
}
},
setActiveVariationsValue() {
if (!this.hasAnyVariant) return;
this.item.uids.split(".").forEach((uid) => {
this.product.variations.some((variation) => {
const value = variation.values.find(
(value) => value.uid === uid
);
if (value !== undefined) {
this.$set(
this.activeVariationValues,
variation.uid,
value.label
);
this.$set(
this.cartItemForm.variations,
variation.uid,
uid
);
return true;
}
});
});
},
setActiveVariationValueLabel(variationIndex) {
this.variationImagePath = null;
const variation = this.product.variations[variationIndex];
const value = variation.values.find(
(value) =>
value.uid === this.cartItemForm.variations[variation.uid]
);
this.$set(this.activeVariationValues, variation.uid, value.label);
},
setVariationValueLabel(variationIndex, valueIndex) {
const variation = this.product.variations[variationIndex];
const value = variation.values[valueIndex];
if (!this.isMobileDevice() && variation.type === "image") {
this.variationImagePath = value.image.path;
}
this.$set(this.activeVariationValues, variation.uid, value.label);
},
isActiveVariationValue(variationUid, valueUid) {
if (!this.cartItemForm.variations.hasOwnProperty(variationUid)) {
return false;
}
return this.cartItemForm.variations[variationUid] === valueUid;
},
syncVariationValue(variationUid, variationIndex, valueUid, valueIndex) {
if (!this.isActiveVariationValue(variationUid, valueUid)) {
this.$set(this.cartItemForm.variations, variationUid, valueUid);
this.setVariationValueLabel(variationIndex, valueIndex);
this.updateVariantDetails();
}
},
},
};

View File

@@ -1,11 +1,11 @@
import Vue from 'vue';
import Vue from "vue";
export function notify(message, options = {}) {
Vue.$toast.open({
message,
type: 'default',
type: "default",
duration: 3000,
position: (screen.width < 992) ? 'bottom' : 'bottom-right',
position: screen.width < 992 ? "bottom" : "bottom-right",
...options,
});
}
@@ -39,23 +39,31 @@ export function chunk(array, size) {
export function slickPrevArrow() {
if (window.FleetCart.rtl) {
return `<div class="arrow-prev">
<i class="las la-angle-right"></i> ${trans('storefront::layout.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')}
<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>
${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>
${trans(
"storefront::layout.next"
)} <i class="las la-angle-right"></i>
</div>`;
}

View File

@@ -0,0 +1,95 @@
import "./axios";
import "./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 HeaderSearch from "./components/layout/HeaderSearch.vue";
import ProductRating from "./components/ProductRating.vue";
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 BannerTwoColumn from "./components/home/BannerTwoColumn.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 NewsletterSubscription from "./components/layout/NewsletterSubscription";
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 MyReviews from "./components/account/reviews/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("header-search", HeaderSearch);
Vue.component("product-rating", ProductRating);
Vue.component("sidebar-cart", () => import("./components/layout/SidebarCart"));
Vue.component("newsletter-popup", () =>
import("./components/layout/NewsletterPopup")
);
Vue.component("newsletter-subscription", NewsletterSubscription);
Vue.component("cookie-bar", () => import("./components/layout/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", () =>
import("./components/home/FlashSaleAndVerticalProducts.vue")
);
Vue.component("banner-two-column", BannerTwoColumn);
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-reviews", MyReviews);
Vue.component("my-addresses", MyAddresses);
new Vue({
el: "#app",
computed: {
compareCount() {
return store.compareCount();
},
wishlistCount() {
return store.wishlistCount();
},
cart() {
return store.state.cart;
},
},
});

View File

@@ -1,4 +1,4 @@
import store from '../store';
import store from "../store";
export default {
data() {
@@ -21,7 +21,7 @@ export default {
},
cartIsNotEmpty() {
return ! store.cartIsEmpty();
return !store.cartIsEmpty();
},
hasShippingMethod() {
@@ -39,45 +39,48 @@ export default {
methods: {
applyCoupon() {
if (! this.couponCode) {
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;
axios
.post(route("cart.coupon.store"), { coupon: this.couponCode })
.then((response) => {
this.couponCode = null;
this.couponError = null;
store.updateCart(cart);
}).catch((xhr) => {
this.couponError = xhr.responseJSON.message;
}).always(() => {
this.loadingOrderSummary = false;
this.applyingCoupon = false;
});
store.updateCart(response.data);
})
.catch((error) => {
this.couponError = error.response.data.message;
})
.finally(() => {
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;
});
axios
.delete(route("cart.coupon.destroy"))
.then((response) => {
store.updateCart(response.data);
})
.catch((error) => {
this.$notify(error.response.data.message);
})
.finally(() => {
this.loadingOrderSummary = false;
});
},
updateShippingMethod(shippingMethodName) {
if (! shippingMethodName) {
async updateShippingMethod(shippingMethodName) {
if (!shippingMethodName) {
return;
}
@@ -85,16 +88,24 @@ export default {
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(() => {
try {
const response = await axios.post(
route("cart.shipping_method.store", {
...(store.hasCoupon() && {
coupon_code: store.getCoupon(),
}),
}),
{
shipping_method: shippingMethodName,
}
);
store.updateCart(response.data);
} catch (error) {
this.$notify(error.response.data.message);
} finally {
this.loadingOrderSummary = false;
});
}
},
},
};

View File

@@ -0,0 +1,90 @@
export default {
methods: {
productUrl({ product, variant }) {
return route("products.show", {
slug: product.slug,
...(variant !== null && {
variant: variant.uid,
}),
});
},
hasBaseImage({ item }) {
return item.base_image.length !== 0;
},
baseImage(cartItem) {
if (this.hasBaseImage(cartItem)) {
return cartItem.item.base_image.path;
}
return `${window.FleetCart.baseUrl}/build/assets/image-placeholder.png`;
},
optionValues(option) {
let values = [];
for (let value of option.values) {
values.push(value.label);
}
return values.join(", ");
},
maxQuantity({ item }) {
return item.is_in_stock && item.does_manage_stock ? item.qty : null;
},
exceedsMaxStock({ item, qty }) {
return item.does_manage_stock && item.qty < qty;
},
isQtyIncreaseDisabled(cartItem) {
return (
this.maxQuantity(cartItem) !== null &&
cartItem.qty >= cartItem.item.qty
);
},
changeQuantity(cartItem, qty) {
if (isNaN(qty) || qty < 1) {
qty = 1;
this.updateCart(cartItem, qty);
return;
}
cartItem.qty = qty;
if (this.exceedsMaxStock(cartItem)) {
qty = cartItem.item.qty;
this.updateCart(cartItem, qty);
return;
}
this.updateCart(cartItem, qty);
},
updateQuantity(cartItem, qty) {
if (isNaN(qty) || qty < 1) {
qty = 1;
cartItem.qty = 1;
return;
}
cartItem.qty = qty;
if (this.exceedsMaxStock(cartItem)) {
cartItem.qty = cartItem.item.qty;
return;
}
this.updateCart(cartItem, qty);
},
},
};

View File

@@ -26,7 +26,7 @@ export default {
};
},
change(activeTab) {
async change(activeTab) {
if (this.activeTab === activeTab || activeTab === undefined) {
return;
}
@@ -34,20 +34,17 @@ export default {
this.loading = true;
this.activeTab = activeTab;
$.ajax({
method: "GET",
url: activeTab.url,
}).then((products) => {
if (this.selector().hasClass("slick-initialized")) {
this.selector().slick("unslick");
}
const response = await axios.get(activeTab.url);
this.products = products;
this.loading = false;
if (this.selector().hasClass("slick-initialized")) {
this.selector().slick("unslick");
}
this.$nextTick(() => {
this.selector().slick(this.slickOptions());
});
this.products = response.data;
this.loading = false;
this.$nextTick(() => {
this.selector().slick(this.slickOptions());
});
},
},

View File

@@ -9,7 +9,16 @@ export default {
computed: {
productUrl() {
return route("products.show", this.product.slug);
return route("products.show", {
slug: this.product.slug,
...(this.hasAnyVariant && {
variant: this.item.uid,
}),
});
},
hasAnyVariant() {
return this.product.variant !== null;
},
hasAnyOption() {
@@ -20,16 +29,18 @@ export default {
return !this.hasAnyOption;
},
hasAnyMedia() {
return this.item.media.length !== 0;
},
hasBaseImage() {
return this.product.base_image.length !== 0;
return this.item.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`;
return this.hasBaseImage
? this.item.base_image.path
: `${window.FleetCart.baseUrl}/build/assets/image-placeholder.png`;
},
inWishlist() {
@@ -53,24 +64,27 @@ export default {
addToCart() {
this.addingToCart = true;
$.ajax({
method: "POST",
url: route("cart.items.store", {
product_id: this.product.id,
qty: 1,
}),
})
.then((cart) => {
store.updateCart(cart);
axios
.post(
route("cart.items.store", {
product_id: this.product.id,
qty: 1,
...(this.hasAnyVariant && {
variant_id: this.item.id,
}),
})
)
.then((response) => {
store.updateCart(response.data);
if (document.location.href !== route("cart.index")) {
$(".header-cart").trigger("click");
}
})
.catch((xhr) => {
this.$notify(xhr.responseJSON.message);
.catch((error) => {
this.$notify(error.response.data.message);
})
.always(() => {
.finally(() => {
this.addingToCart = false;
});
},

View File

@@ -1,19 +1,44 @@
export default {
methods: {
productUrl(product) {
return route('products.show', product.slug);
return route("products.show", {
slug: product.slug,
...(this.hasAnyVariant(product) && {
variant: product.variant.uid,
}),
});
},
hasAnyVariant(product) {
return product.variant !== null;
},
hasBaseImage(product) {
return product.base_image.length !== 0;
return this.hasAnyVariant(product)
? product.variant.base_image.length !== 0
: product.base_image.length !== 0;
},
baseImage(product) {
if (this.hasBaseImage(product)) {
return product.base_image.path;
return this.hasAnyVariant(product)
? product.variant.base_image.path
: product.base_image.path;
}
return `${window.FleetCart.baseUrl}/themes/storefront/public/images/image-placeholder.png`;
return `${window.FleetCart.baseUrl}/build/assets/image-placeholder.png`;
},
productPrice(product) {
return this.hasAnyVariant(product)
? product.variant.formatted_price
: product.formatted_price;
},
productIsInStock(product) {
return this.hasAnyVariant(product)
? product.variant.is_in_stock
: product.is_in_stock;
},
},
};

View File

@@ -6,6 +6,7 @@ export default {
cart: FleetCart.cart,
wishlist: FleetCart.wishlist,
compareList: FleetCart.compareList,
coupon: {},
}),
cartIsEmpty() {
@@ -14,6 +15,8 @@ export default {
updateCart(cart) {
this.state.cart = cart;
this.setCoupon(cart);
},
removeCartItem(cartItem) {
@@ -30,6 +33,24 @@ export default {
);
},
hasCoupon() {
return this.state.cart.coupon.hasOwnProperty("code");
},
getCoupon() {
return this.state.cart.coupon.code;
},
setCoupon(cart) {
if (cart.coupon.code) {
this.state.cart.coupon = cart.coupon;
}
},
compareCount() {
return this.state.compareList.length;
},
wishlistCount() {
return this.state.wishlist.length;
},
@@ -46,27 +67,22 @@ export default {
}
},
addToWishlist(productId) {
async 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 },
await axios.post(route("wishlist.store"), {
productId,
});
},
removeFromWishlist(productId) {
this.state.wishlist.splice(this.state.wishlist.indexOf(productId), 1);
$.ajax({
method: "DELETE",
url: route("wishlist.destroy", { productId }),
});
axios.delete(route("wishlist.destroy", { productId }));
},
inCompareList(productId) {
@@ -84,22 +100,17 @@ export default {
addToCompareList(productId) {
this.state.compareList.push(productId);
$.ajax({
method: "POST",
url: route("compare.store"),
data: { productId },
axios.post(route("compare.store"), {
productId,
});
},
removeFromCompareList(productId) {
async removeFromCompareList(productId) {
this.state.compareList.splice(
this.state.compareList.indexOf(productId),
1
);
$.ajax({
method: "DELETE",
url: route("compare.destroy", { productId }),
});
await axios.delete(route("compare.destroy", { productId }));
},
};

View File

@@ -1,87 +1,65 @@
require('./vendors/vendors');
import "./vendors/vendors";
$(() => {
/* variables
/*----------------------------------------*/
let _window = $(window),
body = $('body');
body = $("body");
/* button loading
/*----------------------------------------*/
$('[data-loading]').on('click', (e) => {
e.currentTarget.classList.add('btn-loading');
$("[data-loading]").on("click", (e) => {
e.currentTarget.classList.add("btn-loading");
});
/* select option
/*----------------------------------------*/
let select = $('.custom-select-option');
let select = $(".custom-select-option");
select.niceSelect();
select.on('change', (e) => {
e.target.dispatchEvent(new Event('nice-select-updated', { bubbles: true }));
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();
});
let overlay = $(".overlay");
/* header
/*----------------------------------------*/
let headerWrap = $('.header-wrap'),
headerWrapInner = $('.header-wrap-inner'),
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 = $(".header-search-sm"),
searchInputSm = $(".search-input-sm"),
headerSearchSmClose = $(".header-search-sm-form .btn-close");
headerSearchSm.on('click', (e) => {
headerSearchSm.on("click", (e) => {
let target = $(e.currentTarget);
target.parents('.header-search').next().toggleClass('active');
searchInputSm.focus();
target.parents(".header-search").next().toggleClass("active");
searchInputSm.trigger("focus");
});
headerSearchSmClose.on('click', (e) => {
headerSearchSmClose.on("click", (e) => {
let target = $(e.currentTarget);
target.parents('.header-search-sm-form').removeClass('active');
target.parents(".header-search-sm-form").removeClass("active");
});
_window.on('resize', () => {
_window.on("resize", () => {
headerWrapInnerHeight = headerWrapInner.outerHeight();
});
_window.on('load scroll resize', () => {
_window.on("load scroll resize", () => {
let headerWrapHeight = headerWrap.outerHeight(),
headerWrapOffsetTop = headerWrap.offset().top + headerWrapHeight;
@@ -89,18 +67,18 @@ $(() => {
let scrollTop = _window.scrollTop();
if (scrollTop > headerWrapOffsetTop) {
headerWrap.css('padding-top', `${headerWrapInnerHeight}px`);
headerWrapInner.addClass('sticky');
headerWrap.css("padding-top", `${headerWrapInnerHeight}px`);
headerWrapInner.addClass("sticky");
setTimeout(() => {
headerWrapInner.addClass('show');
headerWrapInner.addClass("show");
});
return;
}
headerWrap.css('padding-top', 0);
headerWrapInner.removeClass('sticky show');
headerWrap.css("padding-top", 0);
headerWrapInner.removeClass("sticky show");
}
stickyHeader();
@@ -109,9 +87,9 @@ $(() => {
/* menu dropdown arrow
/*----------------------------------------*/
let megaMenuItem = $('.mega-menu > li'),
subMenuDropdown = $('.sub-menu > .dropdown'),
sidebarMenuSubMenu = $('.sidebar-menu .sub-menu');
let megaMenuItem = $(".mega-menu > li"),
subMenuDropdown = $(".sub-menu > .dropdown"),
sidebarMenuSubMenu = $(".sidebar-menu .sub-menu");
function menuDropdownArrow(parentSelector, childSelector) {
parentSelector.each(function () {
@@ -119,38 +97,42 @@ $(() => {
if (self.children().length > 1) {
if (window.FleetCart.rtl) {
self.children(`${childSelector}`).append('<i class="las la-angle-left"></i>');
self.children(`${childSelector}`).append(
'<i class="las la-angle-left"></i>'
);
return;
}
self.children(`${childSelector}`).append('<i class="las la-angle-right"></i>');
self.children(`${childSelector}`).append(
'<i class="las la-angle-right"></i>'
);
}
});
}
menuDropdownArrow(subMenuDropdown, 'a');
menuDropdownArrow(megaMenuItem, '.menu-item');
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');
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');
categoryNavInner.on("click", () => {
categoryDropdownWrap.toggleClass("show");
});
_window.on('load resize', () => {
_window.on("load resize", () => {
let verticalMegaMenuListHeight = 0,
homeSliderHeight = homeSlider.height(),
categoryDropdownHeight = homeSliderHeight;
categoryDropdown.css('height', `${categoryDropdownHeight}px`);
categoryDropdown.css("height", `${categoryDropdownHeight}px`);
verticalMegaMenuList.each(function () {
let self = $(this);
@@ -158,52 +140,52 @@ $(() => {
verticalMegaMenuListHeight += self.height();
if (verticalMegaMenuListHeight + 78 > categoryDropdownHeight) {
self.addClass('hide');
moreCategories.removeClass('hide');
self.addClass("hide");
moreCategories.removeClass("hide");
return;
}
self.removeClass('hide');
moreCategories.addClass('hide');
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');
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) => {
sidebarMenuIcon.on("click", (e) => {
e.stopPropagation();
overlay.addClass('active');
sidebarMenuWrap.addClass('active');
overlay.addClass("active");
sidebarMenuWrap.addClass("active");
});
sidebarMenuClose.on('click', (e) => {
overlay.removeClass('active');
sidebarMenuWrap.removeClass('active');
sidebarMenuClose.on("click", (e) => {
overlay.removeClass("active");
sidebarMenuWrap.removeClass("active");
});
sidebarMenuWrap.on('click', (e) => {
sidebarMenuWrap.on("click", (e) => {
e.stopPropagation();
});
sidebarMenuTab.on('click', (e) => {
sidebarMenuTab.on("click", (e) => {
let target = $(e.currentTarget);
e.preventDefault();
target.tab('show');
target.tab("show");
});
sidebarMenuList.each(function () {
@@ -211,191 +193,131 @@ $(() => {
if (self.children().length > 1) {
if (window.FleetCart.rtl) {
self.children('a').after('<i class="las la-angle-left"></i>');
self.children("a").after('<i class="las la-angle-left"></i>');
return;
}
self.children('a').after('<i class="las la-angle-right"></i>');
self.children("a").after('<i class="las la-angle-right"></i>');
}
});
sidebarMenuDropdown.on('click', (e) => {
sidebarMenuDropdown.on("click", (e) => {
let target = $(e.currentTarget);
if (! target.hasClass('active')) {
$('.sidebar-menu > li').removeClass('active');
target.addClass('active');
if (!target.hasClass("active")) {
$(".sidebar-menu > li").removeClass("active");
target.addClass("active");
} else {
$('.sidebar-menu > li').removeClass('active');
$(".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);
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);
$(".sidebar-menu .open").removeClass("open").slideUp(300);
});
sidebarMenuLink.on('click', (e) => {
sidebarMenuLink.on("click", (e) => {
e.stopPropagation();
});
sidebarMenuListUl.on('click', (e) => {
sidebarMenuListUl.on("click", (e) => {
e.stopPropagation();
});
sidebarMenuSubMenu.on('click', (e) => {
sidebarMenuSubMenu.on("click", (e) => {
let target = $(e.currentTarget);
if (! target.hasClass('active')) {
target.addClass('active');
if (!target.hasClass("active")) {
target.addClass("active");
} else {
target.removeClass('active');
target.removeClass("active");
}
target.children('ul').slideToggle(300);
target.children("ul").slideToggle(300);
});
sidebarMenuSubMenuUl.on('click', function (e) {
sidebarMenuSubMenuUl.on("click", function (e) {
e.stopPropagation();
});
sidebarMenuSubMenuLink.on('click', (e) => {
sidebarMenuSubMenuLink.on("click", (e) => {
e.stopPropagation();
});
/* slider
/*----------------------------------------*/
let homeSlider = $('.home-slider');
const 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();
const { speed, autoplay, autoplaySpeed, fade, dots, arrows } =
homeSlider.data();
homeSlider
.slick({
rows: 0,
rtl: window.FleetCart.rtl,
cssEase: fade ? "cubic-bezier(0.7, 0, 0.3, 1)" : "ease",
speed,
autoplay,
autoplaySpeed,
fade,
dots,
arrows,
})
.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');
let mobileViewFilter = $(".mobile-view-filter");
let filterSectionWrap = $(".filter-section-wrap");
let sidebarFilterClose = $(".sidebar-filter-close");
mobileViewFilter.on('click', (e) => {
mobileViewFilter.on("click", (e) => {
e.stopPropagation();
filterSectionWrap.addClass('active');
overlay.addClass('active');
filterSectionWrap.addClass("active");
overlay.addClass("active");
});
sidebarFilterClose.on('click', () => {
filterSectionWrap.removeClass('active');
overlay.removeClass('active');
sidebarFilterClose.on("click", () => {
filterSectionWrap.removeClass("active");
overlay.removeClass("active");
});
filterSectionWrap.on('click', (e) => {
filterSectionWrap.on("click", (e) => {
e.stopPropagation();
});
body.on('click', () => {
overlay.removeClass('active');
sidebarCart.removeClass('active');
sidebarMenuWrap.removeClass('active');
filterSectionWrap.removeClass('active');
body.on("click", () => {
overlay.removeClass("active");
$(".sidebar-cart-wrap").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');
$(".browse-categories li").each((_, 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');
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>');
@@ -403,129 +325,9 @@ $(() => {
filterCategoriesLink.before('<i class="las la-angle-right"></i>');
}
parentUls.show().siblings('i').addClass('open');
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);
}
$(".browse-categories li i").on("click", (e) => {
$(e.currentTarget).toggleClass("open").siblings("ul").slideToggle(300);
});
});

View File

@@ -1,10 +1,10 @@
require('flatpickr');
import "flatpickr";
for (let el of $('.datetime-picker')) {
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'),
mode: el.hasAttribute("data-range") ? "range" : "single",
enableTime: el.hasAttribute("data-time"),
noCalendar: el.hasAttribute("data-no-calender"),
altInput: true,
});
}

View File

@@ -1,11 +1,3 @@
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');
import "popper.js";
import "jquery-nice-select";
import "slick-animation";