Features: 1) Introduce handleDeposit function with validation logic and deposit transaction flow; 2) Add useDeposit composable and balance.vue page for user account balance management; 3) Enhance wishlist and cart functionality with authentication checks and notification improvements;
Fixes: 1) Replace `ElNotification` with `useNotification` across all components and composables; 2) Add missing semicolons, consistent formatting, and type annotations in multiple files; 3) Resolve non-reactive elements in wishlist and cart state management; Extra: 1) Update i18n translations with new strings for promocodes, balance, authentication, and profile settings; 2) Refactor SCSS styles including variable additions and component-specific tweaks; 3) Remove redundant queries, unused imports, and `storePage.ts` file for cleanup.
This commit is contained in:
parent
761fecf67f
commit
c60ac13e88
62 changed files with 1560 additions and 563 deletions
|
|
@ -68,12 +68,20 @@ await Promise.all([
|
|||
watch(
|
||||
() => appStore.activeState,
|
||||
(state) => {
|
||||
appStore.setOverflowHidden(state !== '')
|
||||
appStore.setOverflowHidden(state !== '');
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
);
|
||||
|
||||
let stopWatcher: VoidFunction = () => {}
|
||||
watch(locale, () => {
|
||||
useHead({
|
||||
htmlAttrs: {
|
||||
lang: locale.value
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let stopWatcher: VoidFunction = () => {};
|
||||
|
||||
onMounted( async () => {
|
||||
refreshInterval = setInterval(async () => {
|
||||
|
|
|
|||
|
|
@ -8,5 +8,6 @@ $accentDark: #5743b5;
|
|||
$accentLight: #a69cdc;
|
||||
$accentDisabled: #826fa2;
|
||||
$accentSmooth: #656bd1;
|
||||
$contrast: #FFC107;
|
||||
$error: #f13838;
|
||||
$default_border_radius: 4px;
|
||||
|
|
@ -58,8 +58,6 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useLogout} from "~/composables/auth";
|
||||
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
|
|
@ -79,8 +77,6 @@ const productsInCartQuantity = computed(() => {
|
|||
const productsInWishlistQuantity = computed(() => {
|
||||
return wishlistStore.wishlist ? wishlistStore.wishlist.products.edges.length : 0;
|
||||
});
|
||||
|
||||
const { logout } = useLogout();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
type="text"
|
||||
v-model="query"
|
||||
:placeholder="t('fields.search')"
|
||||
inputmode="search"
|
||||
/>
|
||||
<div class="search__tools">
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@
|
|||
</div>
|
||||
</nuxt-link-locale>
|
||||
<div class="card__content">
|
||||
<div class="card__price">{{ product.price }}</div>
|
||||
<div class="card__price">{{ product.price }} {{ CURRENCY }}</div>
|
||||
<p class="card__name">{{ product.name }}</p>
|
||||
<el-rate
|
||||
v-model="rating"
|
||||
|
|
@ -58,6 +58,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="card__bottom">
|
||||
<div class="card__bottom-inner">
|
||||
<ui-button
|
||||
class="card__bottom-button"
|
||||
v-if="isProductInCart"
|
||||
|
|
@ -94,6 +95,30 @@
|
|||
<icon name="mdi:cards-heart-outline" size="28" v-else />
|
||||
</div>
|
||||
</div>
|
||||
<div class="tools" v-if="isToolsVisible && isProductInCart">
|
||||
<button
|
||||
class="tools__item tools__item-button"
|
||||
@click="overwriteOrder({
|
||||
type: 'remove',
|
||||
productUuid: product.uuid,
|
||||
productName: product.name
|
||||
})"
|
||||
>
|
||||
-
|
||||
</button>
|
||||
<span class="tools__item tools__item-count" v-text="'X' + productinCartQuantity" />
|
||||
<button
|
||||
class="tools__item tools__item-button"
|
||||
@click="overwriteOrder({
|
||||
type: 'add',
|
||||
productUuid: product.uuid,
|
||||
productName: product.name
|
||||
})"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -106,10 +131,12 @@ import 'swiper/css/effect-fade';
|
|||
import 'swiper/css/pagination'
|
||||
import {useWishlistOverwrite} from "~/composables/wishlist";
|
||||
import {useOrderOverwrite} from "~/composables/orders/useOrderOverwrite";
|
||||
import {CURRENCY} from "~/config/constants";
|
||||
|
||||
const props = defineProps<{
|
||||
product: IProduct;
|
||||
isList?: boolean;
|
||||
isToolsVisible?: boolean;
|
||||
}>();
|
||||
|
||||
const {t} = useI18n();
|
||||
|
|
@ -129,6 +156,9 @@ const isProductInWishlist = computed(() => {
|
|||
const isProductInCart = computed(() => {
|
||||
return cartStore.currentOrder?.orderProducts?.edges.find((prod) => prod.node.product.uuid === props.product?.uuid);
|
||||
});
|
||||
const productinCartQuantity = computed(() => {
|
||||
return cartStore.currentOrder?.orderProducts?.edges.filter((prod) => prod.node.product.uuid === props.product.uuid)[0].node.quantity;
|
||||
});
|
||||
|
||||
const rating = computed(() => {
|
||||
return props.product.feedbacks.edges[0]?.node?.rating ?? 5;
|
||||
|
|
@ -173,7 +203,7 @@ function goTo(index: number) {
|
|||
|
||||
&__list {
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
align-items: stretch;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
|
||||
|
|
@ -191,10 +221,17 @@ function goTo(index: number) {
|
|||
margin-top: 0;
|
||||
width: fit-content;
|
||||
flex-shrink: 0;
|
||||
padding-inline: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
|
||||
&-inner {
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
&-button {
|
||||
width: fit-content;
|
||||
|
|
@ -286,7 +323,7 @@ function goTo(index: number) {
|
|||
|
||||
&__quantity {
|
||||
width: fit-content;
|
||||
background-color: rgba($accent, 0.2);
|
||||
background-color: rgba($contrast, 0.5);
|
||||
border-radius: $default_border_radius;
|
||||
padding: 5px 10px;
|
||||
font-size: 14px;
|
||||
|
|
@ -295,11 +332,14 @@ function goTo(index: number) {
|
|||
&__bottom {
|
||||
margin-top: auto;
|
||||
padding: 0 20px 20px 20px;
|
||||
max-width: 100%;
|
||||
|
||||
&-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 5px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&-button {
|
||||
width: 84%;
|
||||
|
|
@ -326,6 +366,52 @@ function goTo(index: number) {
|
|||
}
|
||||
}
|
||||
|
||||
.tools {
|
||||
width: 100%;
|
||||
border-radius: 4px;
|
||||
background-color: rgba($accent, 0.2);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr 1fr;
|
||||
height: 30px;
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&-count {
|
||||
border-left: 1px solid $accent;
|
||||
border-right: 1px solid $accent;
|
||||
|
||||
color: $accent;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&-button {
|
||||
cursor: pointer;
|
||||
background-color: rgba($accent, 0.2);
|
||||
border-radius: 4px 0 0 4px;
|
||||
transition: 0.2s;
|
||||
|
||||
color: $accent;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
|
||||
@include hover {
|
||||
background-color: $accent;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.swiper-pagination) {
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -11,12 +11,14 @@
|
|||
:placeholder="t('fields.email')"
|
||||
:rules="[required]"
|
||||
v-model="email"
|
||||
:inputMode="'email'"
|
||||
/>
|
||||
<ui-input
|
||||
:type="'text'"
|
||||
:placeholder="t('fields.phoneNumber')"
|
||||
:rules="[required]"
|
||||
v-model="phoneNumber"
|
||||
:inputMode="'tel'"
|
||||
/>
|
||||
<ui-input
|
||||
:type="'text'"
|
||||
|
|
|
|||
|
|
@ -1,67 +1,77 @@
|
|||
<template>
|
||||
<!-- <form @submit.prevent="handleDeposit()" class="form">-->
|
||||
<form @submit.prevent="" class="form">
|
||||
<form @submit.prevent="handleDeposit()" class="form">
|
||||
<div class="form__box">
|
||||
<ui-input
|
||||
:type="'text'"
|
||||
:placeholder="''"
|
||||
v-model="amount"
|
||||
:numberOnly="true"
|
||||
:inputMode="'decimal'"
|
||||
/>
|
||||
<icon name="ic:baseline-compare-arrows" size="30" />
|
||||
<ui-input
|
||||
:type="'text'"
|
||||
:placeholder="''"
|
||||
v-model="amount"
|
||||
:numberOnly="true"
|
||||
:inputMode="'decimal'"
|
||||
/>
|
||||
</div>
|
||||
<!-- <ui-button-->
|
||||
<!-- class="form__button"-->
|
||||
<!-- :isDisabled="!isFormValid"-->
|
||||
<!-- :isLoading="loading"-->
|
||||
<!-- >-->
|
||||
<!-- {{ $t('buttons.topUp') }}-->
|
||||
<!-- </ui-button>-->
|
||||
<ui-button
|
||||
class="form__button"
|
||||
:isDisabled="!isFormValid"
|
||||
:isLoading="loading"
|
||||
>
|
||||
{{ t('buttons.topUp') }}
|
||||
</ui-button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// import {useDeposit} from "@/composables/user/useDeposit.js";
|
||||
<script setup lang="ts">
|
||||
import {useDeposit} from "~/composables/user";
|
||||
|
||||
const { t } = useI18n()
|
||||
const companyStore = useCompanyStore()
|
||||
const { t } = useI18n();
|
||||
const companyStore = useCompanyStore();
|
||||
|
||||
const paymentMin = computed(() => companyStore.companyInfo?.paymentGatewayMinimum)
|
||||
const paymentMax = computed(() => companyStore.companyInfo?.paymentGatewayMaximum)
|
||||
const paymentMin = computed(() => companyStore.companyInfo?.paymentGatewayMinimum || 0);
|
||||
const paymentMax = computed(() => companyStore.companyInfo?.paymentGatewayMaximum || 500);
|
||||
|
||||
const amount = ref('')
|
||||
const amount = ref<string>("0");
|
||||
|
||||
const isFormValid = computed(() => {
|
||||
return (
|
||||
amount.value >= paymentMin.value &&
|
||||
amount.value <= paymentMax.value
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// const { deposit, loading } = useDeposit();
|
||||
//
|
||||
// async function handleDeposit() {
|
||||
// await deposit(amount.value);
|
||||
// }
|
||||
const { deposit, loading } = useDeposit();
|
||||
|
||||
async function handleDeposit() {
|
||||
await deposit(amount.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
|
||||
&__box {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 20px;
|
||||
|
||||
& span {
|
||||
flex-shrink: 0;
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
&__button {
|
||||
width: fit-content;
|
||||
padding-inline: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
:placeholder="t('fields.email')"
|
||||
:rules="[isEmail]"
|
||||
v-model="email"
|
||||
:inputMode="'email'"
|
||||
/>
|
||||
<ui-input
|
||||
:type="'password'"
|
||||
|
|
@ -48,7 +49,7 @@ import {useValidators} from "~/composables/rules";
|
|||
const { t } = useI18n();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const { required, isEmail } = useValidators()
|
||||
const { required, isEmail } = useValidators();
|
||||
|
||||
const email = ref<string>('');
|
||||
const password = ref<string>('');
|
||||
|
|
@ -58,7 +59,7 @@ const isFormValid = computed(() => {
|
|||
return (
|
||||
isEmail(email.value) === true &&
|
||||
required(password.value) === true
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
const { login, loading } = useLogin();
|
||||
|
|
|
|||
|
|
@ -21,12 +21,14 @@
|
|||
:placeholder="t('fields.phoneNumber')"
|
||||
:rules="[required]"
|
||||
v-model="phoneNumber"
|
||||
:inputMode="'tel'"
|
||||
/>
|
||||
<ui-input
|
||||
:type="'email'"
|
||||
:placeholder="t('fields.email')"
|
||||
:rules="[isEmail]"
|
||||
v-model="email"
|
||||
:inputMode="'email'"
|
||||
/>
|
||||
</div>
|
||||
<ui-input
|
||||
|
|
@ -59,26 +61,29 @@
|
|||
</form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import {useValidators} from "~/composables/rules";
|
||||
import {useRegister} from "~/composables/auth/index.js";
|
||||
import {useRouteQuery} from "@vueuse/router";
|
||||
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const { required, isEmail, isPasswordValid } = useValidators()
|
||||
const { required, isEmail, isPasswordValid } = useValidators();
|
||||
|
||||
const firstName = ref('')
|
||||
const lastName = ref('')
|
||||
const phoneNumber = ref('')
|
||||
const email = ref('')
|
||||
const password = ref('')
|
||||
const confirmPassword = ref('')
|
||||
const firstName = ref<string>('');
|
||||
const lastName = ref<string>('');
|
||||
const phoneNumber = ref<string>('');
|
||||
const email = ref<string>('');
|
||||
const password = ref<string>('');
|
||||
const confirmPassword = ref<string>('');
|
||||
|
||||
const compareStrings = (v) => {
|
||||
if (v === password.value) return true
|
||||
return t('errors.compare')
|
||||
}
|
||||
const referrer = useRouteQuery('referrer', '');
|
||||
|
||||
const compareStrings = (v: string) => {
|
||||
if (v === password.value) return true;
|
||||
return t('errors.compare');
|
||||
};
|
||||
|
||||
const isFormValid = computed(() => {
|
||||
return (
|
||||
|
|
@ -88,20 +93,21 @@ const isFormValid = computed(() => {
|
|||
isEmail(email.value) === true &&
|
||||
isPasswordValid(password.value) === true &&
|
||||
compareStrings(confirmPassword.value) === true
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { register, loading } = useRegister();
|
||||
|
||||
async function handleRegister() {
|
||||
await register(
|
||||
firstName.value,
|
||||
lastName.value,
|
||||
phoneNumber.value,
|
||||
email.value,
|
||||
password.value,
|
||||
confirmPassword.value
|
||||
);
|
||||
await register({
|
||||
firstName: firstName.value,
|
||||
lastName: lastName.value,
|
||||
phoneNumber: phoneNumber.value,
|
||||
email: email.value,
|
||||
password: password.value,
|
||||
confirmPassword: confirmPassword.value,
|
||||
referrer: referrer.value
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -118,7 +124,7 @@ async function handleRegister() {
|
|||
|
||||
&__box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
:placeholder="t('fields.email')"
|
||||
:rules="[isEmail]"
|
||||
v-model="email"
|
||||
:inputMode="'email'"
|
||||
/>
|
||||
<ui-button
|
||||
class="form__button"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<!-- <form class="form" @submit.prevent="handleUpdate()">-->
|
||||
<form class="form" @submit.prevent="">
|
||||
<form class="form" @submit.prevent="handleUpdate">
|
||||
<div class="form__box">
|
||||
<ui-input
|
||||
:type="'text'"
|
||||
:placeholder="t('fields.firstName')"
|
||||
|
|
@ -13,6 +13,8 @@
|
|||
:rules="[required]"
|
||||
v-model="lastName"
|
||||
/>
|
||||
</div>
|
||||
<div class="form__box">
|
||||
<ui-input
|
||||
:type="'email'"
|
||||
:placeholder="t('fields.email')"
|
||||
|
|
@ -25,6 +27,8 @@
|
|||
:rules="[required]"
|
||||
v-model="phoneNumber"
|
||||
/>
|
||||
</div>
|
||||
<div class="form__box">
|
||||
<ui-input
|
||||
:type="'password'"
|
||||
:placeholder="t('fields.newPassword')"
|
||||
|
|
@ -37,65 +41,58 @@
|
|||
:rules="[compareStrings]"
|
||||
v-model="confirmPassword"
|
||||
/>
|
||||
<!-- <ui-button-->
|
||||
<!-- class="form__button"-->
|
||||
<!-- :isLoading="loading"-->
|
||||
<!-- >-->
|
||||
<!-- {{ t('buttons.save') }}-->
|
||||
<!-- </ui-button>-->
|
||||
</div>
|
||||
<ui-button
|
||||
class="form__button"
|
||||
:isLoading="loading"
|
||||
>
|
||||
{{ t('buttons.save') }}
|
||||
</ui-button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import {useValidators} from "~/composables/rules";
|
||||
// import {useUserUpdating} from "@/composables/user";
|
||||
import {useUserUpdating} from "~/composables/user/index.js";
|
||||
|
||||
const { t } = useI18n()
|
||||
const userStore = useUserStore()
|
||||
const { t } = useI18n();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const { required, isEmail, isPasswordValid } = useValidators()
|
||||
const { required, isEmail, isPasswordValid } = useValidators();
|
||||
|
||||
const userFirstName = computed(() => userStore.user?.firstName)
|
||||
const userLastName = computed(() => userStore.user?.lastName)
|
||||
const userEmail = computed(() => userStore.user?.email)
|
||||
const userPhoneNumber = computed(() => userStore.user?.phoneNumber)
|
||||
const user = computed(() => userStore.user);
|
||||
|
||||
const firstName = ref('')
|
||||
const lastName = ref('')
|
||||
const email = ref('')
|
||||
const phoneNumber = ref('')
|
||||
const password = ref('')
|
||||
const confirmPassword = ref('')
|
||||
const firstName = ref<string>('');
|
||||
const lastName = ref<string>('');
|
||||
const email = ref<string>('');
|
||||
const phoneNumber = ref<string>('');
|
||||
const password = ref<string>('');
|
||||
const confirmPassword = ref<string>('');
|
||||
|
||||
const compareStrings = (v) => {
|
||||
if (v === password.value) return true
|
||||
return t('errors.compare')
|
||||
const compareStrings = (v: string) => {
|
||||
if (v === password.value) return true;
|
||||
return t('errors.compare');
|
||||
};
|
||||
|
||||
const { updateUser, loading } = useUserUpdating();
|
||||
|
||||
watchEffect(() => {
|
||||
firstName.value = user.value?.firstName || '';
|
||||
lastName.value = user.value?.lastName || '';
|
||||
email.value = user.value?.email || '';
|
||||
phoneNumber.value = user.value?.phoneNumber || '';
|
||||
});
|
||||
|
||||
async function handleUpdate() {
|
||||
await updateUser(
|
||||
firstName.value,
|
||||
lastName.value,
|
||||
email.value,
|
||||
phoneNumber.value,
|
||||
password.value,
|
||||
confirmPassword.value,
|
||||
);
|
||||
}
|
||||
|
||||
// const { updateUser, loading } = useUserUpdating();
|
||||
//
|
||||
// watchEffect(() => {
|
||||
// firstName.value = userFirstName.value || ''
|
||||
// lastName.value = userLastName.value || ''
|
||||
// email.value = userEmail.value || ''
|
||||
// phoneNumber.value = userPhoneNumber.value || ''
|
||||
// })
|
||||
//
|
||||
// async function handleUpdate() {
|
||||
// await updateUser(
|
||||
// firstName.value,
|
||||
// lastName.value,
|
||||
// email.value,
|
||||
// phoneNumber.value,
|
||||
// password.value,
|
||||
// confirmPassword.value,
|
||||
// );
|
||||
// }
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
@ -103,5 +100,16 @@ const compareStrings = (v) => {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
|
||||
&__box {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
&__button {
|
||||
width: fit-content;
|
||||
padding-inline: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<nav class="nav">
|
||||
<div class="nav__inner">
|
||||
<nuxt-link-locale
|
||||
class="nav__item"
|
||||
:class="[{ active: route.path.includes('settings') }]"
|
||||
|
|
@ -32,31 +33,59 @@
|
|||
<icon name="ph:shopping-cart-light" size="20" />
|
||||
{{ t('profile.cart.title') }}
|
||||
</nuxt-link-locale>
|
||||
<nuxt-link-locale
|
||||
class="nav__item"
|
||||
:class="[{ active: route.path.includes('balance') }]"
|
||||
to="/profile/balance"
|
||||
>
|
||||
<icon name="ic:outline-attach-money" size="20" />
|
||||
{{ t('profile.balance.title') }}
|
||||
</nuxt-link-locale>
|
||||
<nuxt-link-locale
|
||||
class="nav__item"
|
||||
:class="[{ active: route.path.includes('promocodes') }]"
|
||||
to="/profile/promocodes"
|
||||
>
|
||||
<icon name="fluent:ticket-20-filled" size="20" />
|
||||
{{ t('profile.promocodes.title') }}
|
||||
</nuxt-link-locale>
|
||||
</div>
|
||||
<div class="nav__logout" @click="logout">
|
||||
<icon name="material-symbols:power-settings-new-outline" size="20" />
|
||||
{{ t('profile.logout') }}
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useLogout} from "~/composables/auth";
|
||||
|
||||
const {t} = useI18n();
|
||||
const route = useRoute();
|
||||
|
||||
const { logout } = useLogout();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.nav {
|
||||
background-color: $white;
|
||||
border-radius: $default_border_radius;
|
||||
padding-block: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.2);
|
||||
position: sticky;
|
||||
top: 141px;
|
||||
width: max-content;
|
||||
height: fit-content;
|
||||
|
||||
&__inner {
|
||||
background-color: $white;
|
||||
border-radius: $default_border_radius;
|
||||
padding-block: 7px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
&__item {
|
||||
cursor: pointer;
|
||||
padding: 5px 30px 5px 20px;
|
||||
padding: 7px 30px 7px 10px;
|
||||
border-left: 2px solid $white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -64,6 +93,7 @@ const route = useRoute();
|
|||
transition: 0.2s;
|
||||
|
||||
color: $accent;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
|
||||
@include hover {
|
||||
|
|
@ -73,6 +103,29 @@ const route = useRoute();
|
|||
&.active {
|
||||
border-color: $accent;
|
||||
color: $accentDark;
|
||||
background-color: rgba($accent, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
&__logout {
|
||||
cursor: pointer;
|
||||
margin-top: 25px;
|
||||
border-radius: $default_border_radius;
|
||||
background-color: rgba($accent, 0.2);
|
||||
border: 1px solid $accent;
|
||||
padding: 7px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
transition: 0.2s;
|
||||
|
||||
color: $accent;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
|
||||
@include hover {
|
||||
background-color: $accent;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
@input="onInput"
|
||||
@keydown="numberOnly ? onlyNumbersKeydown($event) : null"
|
||||
class="block__input"
|
||||
:inputmode="inputMode || 'text'"
|
||||
>
|
||||
<button
|
||||
@click.prevent="togglePasswordVisible"
|
||||
|
|
@ -31,9 +32,10 @@ const emit = defineEmits<{
|
|||
const props = defineProps<{
|
||||
type: string,
|
||||
placeholder: string,
|
||||
modelValue?: [string, number],
|
||||
modelValue?: string | number,
|
||||
rules?: Rule[],
|
||||
numberOnly?: boolean
|
||||
numberOnly?: boolean,
|
||||
inputMode?: "text" | "email" | "search" | "tel" | "url" | "none" | "numeric" | "decimal"
|
||||
}>();
|
||||
|
||||
const isPasswordVisible = ref(props.type);
|
||||
|
|
@ -108,7 +110,7 @@ function onInput(e: Event) {
|
|||
line-height: 20px;
|
||||
|
||||
&::placeholder {
|
||||
color: #2B2B2B;
|
||||
color: #575757;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@
|
|||
:src="currentLocale.flag"
|
||||
:alt="currentLocale.code"
|
||||
/>
|
||||
<skeletons-ui-language-switcher v-else />
|
||||
<!-- <skeletons-ui-language-switcher v-else />-->
|
||||
<template #fallback>
|
||||
<skeletons-ui-language-switcher />
|
||||
<!-- <skeletons-ui-language-switcher />-->
|
||||
</template>
|
||||
</client-only>
|
||||
</div>
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
:key="locale.code"
|
||||
format="webp"
|
||||
densities="x1"
|
||||
@click="switchLanguage(locale.code)"
|
||||
@click="uiSwitchLanguage(locale.code)"
|
||||
:src="locale.flag"
|
||||
:alt="locale.code"
|
||||
/>
|
||||
|
|
@ -45,20 +45,25 @@
|
|||
import {onClickOutside} from "@vueuse/core";
|
||||
import {useLanguageSwitch} from "@/composables/languages/index.js";
|
||||
|
||||
const languageStore = useLanguageStore()
|
||||
const languageStore = useLanguageStore();
|
||||
|
||||
const locales = computed(() => languageStore.languages)
|
||||
const currentLocale = computed(() => languageStore.currentLocale)
|
||||
const locales = computed(() => languageStore.languages);
|
||||
const currentLocale = computed(() => languageStore.currentLocale);
|
||||
|
||||
const isSwitcherVisible = ref<boolean>(false)
|
||||
const setSwitcherVisible = (state) => {
|
||||
isSwitcherVisible.value = state
|
||||
}
|
||||
const isSwitcherVisible = ref<boolean>(false);
|
||||
const setSwitcherVisible = (state: boolean) => {
|
||||
isSwitcherVisible.value = state;
|
||||
};
|
||||
|
||||
const switcherRef = ref(null)
|
||||
onClickOutside(switcherRef, () => isSwitcherVisible.value = false)
|
||||
const switcherRef = ref(null);
|
||||
onClickOutside(switcherRef, () => isSwitcherVisible.value = false);
|
||||
|
||||
const { switchLanguage } = useLanguageSwitch()
|
||||
const { switchLanguage } = useLanguageSwitch();
|
||||
|
||||
const uiSwitchLanguage = (localeCode: string) => {
|
||||
switchLanguage(localeCode);
|
||||
setSwitcherVisible(false);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -5,19 +5,19 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const router = useRouter()
|
||||
|
||||
const props = defineProps<{
|
||||
routePath: string
|
||||
}>()
|
||||
}>();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const redirect = () => {
|
||||
if (props.routePath) {
|
||||
router.push({
|
||||
path: props.routePath
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { useUserStore } from '~/stores/user';
|
|||
import { useAppStore } from '~/stores/app';
|
||||
import {DEFAULT_LOCALE} from "~/config/constants";
|
||||
import {useNotification} from "~/composables/notification";
|
||||
import {usePromocodes} from "~/composables/promocodes";
|
||||
|
||||
export function useLogin() {
|
||||
const { t } = useI18n();
|
||||
|
|
@ -56,12 +57,14 @@ export function useLogin() {
|
|||
}
|
||||
|
||||
userStore.setUser(authData.user);
|
||||
cookieAccess.value = authData.accessToken
|
||||
cookieAccess.value = authData.accessToken;
|
||||
|
||||
useNotification(
|
||||
t('popup.success.login'),
|
||||
'success'
|
||||
);
|
||||
appStore.unsetActiveState();
|
||||
|
||||
useNotification({
|
||||
message: t('popup.success.login'),
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
if (authData.user.language !== cookieLocale.value) {
|
||||
await checkAndRedirect(authData.user.language);
|
||||
|
|
@ -69,8 +72,8 @@ export function useLogin() {
|
|||
|
||||
await useWishlist();
|
||||
await usePendingOrder(authData.user.email);
|
||||
|
||||
appStore.unsetActiveState();
|
||||
await usePromocodes();
|
||||
//TODO: combine three requests
|
||||
}
|
||||
|
||||
watch(error, (err) => {
|
||||
|
|
@ -82,11 +85,11 @@ export function useLogin() {
|
|||
} else {
|
||||
message = err.message;
|
||||
}
|
||||
useNotification(
|
||||
useNotification({
|
||||
message,
|
||||
'error',
|
||||
t('popup.errors.main')
|
||||
);
|
||||
type: 'error',
|
||||
title: t('popup.errors.main')
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@ export function useNewPassword() {
|
|||
});
|
||||
|
||||
if (result?.data?.confirmResetPassword.success) {
|
||||
useNotification(
|
||||
t('popup.success.newPassword'),
|
||||
'success'
|
||||
);
|
||||
useNotification({
|
||||
message: t('popup.success.newPassword'),
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
await router.push({path: '/'})
|
||||
|
||||
|
|
@ -46,11 +46,11 @@ export function useNewPassword() {
|
|||
} else {
|
||||
message = err.message;
|
||||
}
|
||||
useNotification(
|
||||
useNotification({
|
||||
message,
|
||||
'error',
|
||||
t('popup.errors.main')
|
||||
);
|
||||
type: 'error',
|
||||
title: t('popup.errors.main')
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ export function usePasswordReset() {
|
|||
});
|
||||
|
||||
if (result?.data?.resetPassword.success) {
|
||||
useNotification(
|
||||
t('popup.success.reset'),
|
||||
'success'
|
||||
);
|
||||
useNotification({
|
||||
message: t('popup.success.reset'),
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
appStore.unsetActiveState();
|
||||
}
|
||||
|
|
@ -35,11 +35,11 @@ export function usePasswordReset() {
|
|||
} else {
|
||||
message = err.message;
|
||||
}
|
||||
useNotification(
|
||||
useNotification({
|
||||
message,
|
||||
'error',
|
||||
t('popup.errors.main')
|
||||
);
|
||||
type: 'error',
|
||||
title: t('popup.errors.main')
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { useUserStore } from '~/stores/user';
|
|||
import { isGraphQLError } from '~/utils/error';
|
||||
import {DEFAULT_LOCALE} from "~/config/constants";
|
||||
import {useNotification} from "~/composables/notification";
|
||||
import {usePromocodes} from "~/composables/promocodes";
|
||||
|
||||
export function useRefresh() {
|
||||
const { t } = useI18n();
|
||||
|
|
@ -52,6 +53,8 @@ export function useRefresh() {
|
|||
|
||||
await useWishlist();
|
||||
await usePendingOrder(data.user.email);
|
||||
await usePromocodes();
|
||||
//TODO: combine three requests
|
||||
}
|
||||
|
||||
watch(error, (err) => {
|
||||
|
|
@ -63,11 +66,11 @@ export function useRefresh() {
|
|||
} else {
|
||||
message = err.message;
|
||||
}
|
||||
useNotification(
|
||||
useNotification({
|
||||
message,
|
||||
'error',
|
||||
t('popup.errors.main')
|
||||
);
|
||||
type: 'error',
|
||||
title: t('popup.errors.main')
|
||||
});
|
||||
})
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,16 @@ import {isGraphQLError} from "~/utils/error";
|
|||
import type {IRegisterResponse} from "~/types";
|
||||
import {useNotification} from "~/composables/notification";
|
||||
|
||||
interface IRegisterArguments {
|
||||
firstName: string,
|
||||
lastName: string,
|
||||
phoneNumber: string,
|
||||
email: string,
|
||||
password: string,
|
||||
confirmPassword: string,
|
||||
referrer: string
|
||||
}
|
||||
|
||||
export function useRegister() {
|
||||
const {t} = useI18n();
|
||||
const appStore = useAppStore();
|
||||
|
|
@ -13,41 +23,37 @@ export function useRegister() {
|
|||
const { mutate, loading, error } = useMutation<IRegisterResponse>(REGISTER);
|
||||
|
||||
async function register(
|
||||
firstName: string,
|
||||
lastName: string,
|
||||
phoneNumber: string,
|
||||
email: string,
|
||||
password: string,
|
||||
confirmPassword: string
|
||||
payload: IRegisterArguments
|
||||
) {
|
||||
const result = await mutate({
|
||||
firstName,
|
||||
lastName,
|
||||
phoneNumber,
|
||||
email,
|
||||
password,
|
||||
confirmPassword
|
||||
firstName: payload.firstName,
|
||||
lastName: payload.lastName,
|
||||
phoneNumber: payload.phoneNumber,
|
||||
email: payload.email,
|
||||
password: payload.password,
|
||||
confirmPassword: payload.confirmPassword,
|
||||
referrer: payload.referrer
|
||||
});
|
||||
|
||||
if (result?.data?.createUser?.success) {
|
||||
detectMailClient(email);
|
||||
detectMailClient(payload.email);
|
||||
|
||||
useNotification(
|
||||
h('div', [
|
||||
useNotification({
|
||||
message: h('div', [
|
||||
h('p', t('popup.success.register')),
|
||||
mailClientUrl.value ? h(
|
||||
'button',
|
||||
{
|
||||
class: 'el-notification__button',
|
||||
onClick: () => {
|
||||
openMailClient()
|
||||
openMailClient();
|
||||
}
|
||||
},
|
||||
t('buttons.goEmail')
|
||||
) : ''
|
||||
]),
|
||||
'success'
|
||||
);
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
appStore.unsetActiveState();
|
||||
}
|
||||
|
|
@ -62,11 +68,11 @@ export function useRegister() {
|
|||
} else {
|
||||
message = err.message;
|
||||
}
|
||||
useNotification(
|
||||
useNotification({
|
||||
message,
|
||||
'error',
|
||||
t('popup.errors.main')
|
||||
);
|
||||
type: 'error',
|
||||
title: t('popup.errors.main')
|
||||
});
|
||||
})
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
export const useAppConfig = () => {
|
||||
const runtimeConfig = useRuntimeConfig();
|
||||
|
||||
const APP_DOMAIN: string = runtimeConfig.public.evibesBaseDomain;
|
||||
const APP_NAME: string = runtimeConfig.public.evibesProjectName;
|
||||
const APP_NAME_KEY: string = APP_NAME.toLowerCase();
|
||||
|
||||
return {
|
||||
APP_DOMAIN,
|
||||
APP_NAME,
|
||||
APP_NAME_KEY,
|
||||
COOKIES_LOCALE_KEY: `${APP_NAME_KEY}-locale`,
|
||||
|
|
|
|||
|
|
@ -24,10 +24,10 @@ export function useContactUs() {
|
|||
});
|
||||
|
||||
if (result?.data?.contactUs.received) {
|
||||
useNotification(
|
||||
t('popup.success.contactUs'),
|
||||
'success'
|
||||
);
|
||||
useNotification({
|
||||
message: t('popup.success.contactUs'),
|
||||
type: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -40,11 +40,11 @@ export function useContactUs() {
|
|||
} else {
|
||||
message = err.message;
|
||||
}
|
||||
useNotification(
|
||||
useNotification({
|
||||
message,
|
||||
'error',
|
||||
t('popup.errors.main')
|
||||
);
|
||||
type: 'error',
|
||||
title: t('popup.errors.main')
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
|||
1
storefront/composables/date/index.ts
Normal file
1
storefront/composables/date/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from './useDate';
|
||||
14
storefront/composables/date/useDate.ts
Normal file
14
storefront/composables/date/useDate.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
export function useDate(
|
||||
iso: string | undefined,
|
||||
locale: string = 'en-gb'
|
||||
): string {
|
||||
if (!iso) return '';
|
||||
const date = new Date(iso);
|
||||
const parsedLocale = locale.replace('-', '-').toLocaleUpperCase()
|
||||
|
||||
return new Intl.DateTimeFormat(parsedLocale, {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: '2-digit'
|
||||
}).format(date);
|
||||
}
|
||||
|
|
@ -1,26 +1,32 @@
|
|||
export function useNotification(
|
||||
message: string,
|
||||
import type {VNodeChild} from "vue";
|
||||
|
||||
interface INotificationArguments {
|
||||
message: string | VNodeChild,
|
||||
type: string,
|
||||
title?: string
|
||||
) {
|
||||
const duration = 5000;
|
||||
}
|
||||
|
||||
const createProgressBar = (duration: number, message: string) => {
|
||||
return h('div', [
|
||||
h('p', message),
|
||||
h('div', {
|
||||
export function useNotification(
|
||||
args: INotificationArguments
|
||||
) {
|
||||
const duration = 5000
|
||||
|
||||
const progressBar = h('div', {
|
||||
class: 'el-notification__progress',
|
||||
style: {
|
||||
animationDuration: `${duration}ms`
|
||||
}
|
||||
style: { animationDuration: `${duration}ms` }
|
||||
})
|
||||
]);
|
||||
};
|
||||
|
||||
const bodyContent: VNodeChild =
|
||||
typeof args.message === 'string'
|
||||
? h('p', args.message)
|
||||
: args.message
|
||||
|
||||
const messageVNode = h('div', [bodyContent, progressBar])
|
||||
|
||||
ElNotification({
|
||||
title: title,
|
||||
title: args.title,
|
||||
duration,
|
||||
message: createProgressBar(duration, message),
|
||||
type: type
|
||||
} as import('element-plus').NotificationOptions);
|
||||
message: messageVNode,
|
||||
type: args.type
|
||||
} as NotificationOptions)
|
||||
}
|
||||
|
|
@ -26,7 +26,9 @@ interface IOverwriteOrderArguments {
|
|||
export function useOrderOverwrite () {
|
||||
const {t} = useI18n();
|
||||
const cartStore = useCartStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const isAuthenticated = computed(() => userStore.isAuthenticated);
|
||||
const orderUuid = computed(() => cartStore.currentOrder?.uuid);
|
||||
|
||||
const {
|
||||
|
|
@ -58,6 +60,7 @@ export function useOrderOverwrite () {
|
|||
async function overwriteOrder (
|
||||
args: IOverwriteOrderArguments
|
||||
) {
|
||||
if (isAuthenticated.value) {
|
||||
switch (args.type) {
|
||||
case "add":
|
||||
const addResult = await addMutate({
|
||||
|
|
@ -68,10 +71,10 @@ export function useOrderOverwrite () {
|
|||
if (addResult?.data?.addOrderProduct?.order) {
|
||||
cartStore.setCurrentOrders(addResult.data.addOrderProduct.order);
|
||||
|
||||
useNotification(
|
||||
t('popup.success.addToCart', { product: args.productName }),
|
||||
'success'
|
||||
);
|
||||
useNotification({
|
||||
message: t('popup.success.addToCart', { product: args.productName }),
|
||||
type: 'success'
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -85,10 +88,10 @@ export function useOrderOverwrite () {
|
|||
if (removeResult?.data?.removeOrderProduct?.order) {
|
||||
cartStore.setCurrentOrders(removeResult.data.removeOrderProduct.order);
|
||||
|
||||
useNotification(
|
||||
t('popup.success.removeFromCart', { product: args.productName }),
|
||||
'success'
|
||||
);
|
||||
useNotification({
|
||||
message: t('popup.success.removeFromCart', { product: args.productName }),
|
||||
type: 'success'
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -102,10 +105,10 @@ export function useOrderOverwrite () {
|
|||
if (removeKindResult?.data?.removeOrderProductsOfAKind?.order) {
|
||||
cartStore.setCurrentOrders(removeKindResult.data.removeOrderProductsOfAKind.order);
|
||||
|
||||
useNotification(
|
||||
t('popup.success.removeFromCart', { product: args.productName }),
|
||||
'success'
|
||||
);
|
||||
useNotification({
|
||||
message: t('popup.success.removeFromCart', { product: args.productName }),
|
||||
type: 'success'
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -119,10 +122,10 @@ export function useOrderOverwrite () {
|
|||
if (removeAllResult?.data?.removeAllOrderProducts?.order) {
|
||||
cartStore.setCurrentOrders(removeAllResult.data.removeAllOrderProducts.order);
|
||||
|
||||
useNotification(
|
||||
t('popup.success.removeAllFromCart', { product: args.productName }),
|
||||
'success'
|
||||
);
|
||||
useNotification({
|
||||
message: t('popup.success.removeAllFromCart', { product: args.productName }),
|
||||
type: 'success'
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -136,10 +139,10 @@ export function useOrderOverwrite () {
|
|||
|
||||
if (bulkResult?.data?.bulkOrderAction?.order) {
|
||||
cartStore.setCurrentOrders(bulkResult.data.bulkOrderAction.order);
|
||||
useNotification(
|
||||
t('popup.success.bulkRemoveWishlist'),
|
||||
'success'
|
||||
);
|
||||
useNotification({
|
||||
message: t('popup.success.bulkRemoveWishlist'),
|
||||
type: 'success'
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -147,6 +150,12 @@ export function useOrderOverwrite () {
|
|||
default:
|
||||
console.error('No type provided for overwriteOrder');
|
||||
}
|
||||
} else {
|
||||
useNotification({
|
||||
message: t('popup.errors.loginFirst'),
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
watch(addError || removedError || removedKindError || removedAllError || bulkError, (err) => {
|
||||
|
|
@ -158,11 +167,11 @@ export function useOrderOverwrite () {
|
|||
} else {
|
||||
message = err.message;
|
||||
}
|
||||
useNotification(
|
||||
useNotification({
|
||||
message,
|
||||
'error',
|
||||
t('popup.errors.main')
|
||||
);
|
||||
type: 'error',
|
||||
title: t('popup.errors.main')
|
||||
});
|
||||
});
|
||||
|
||||
return{
|
||||
|
|
|
|||
1
storefront/composables/promocodes/index.ts
Normal file
1
storefront/composables/promocodes/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from './usePromocodes'
|
||||
24
storefront/composables/promocodes/usePromocodes.ts
Normal file
24
storefront/composables/promocodes/usePromocodes.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import type {IPromocodesResponse} from "~/types";
|
||||
import {GET_PROMOCODES} from "~/graphql/queries/standalone/promocodes";
|
||||
|
||||
export async function usePromocodes () {
|
||||
const promocodesStore = usePromocodeStore();
|
||||
|
||||
const { data, error } = await useAsyncQuery<IPromocodesResponse>(
|
||||
GET_PROMOCODES
|
||||
);
|
||||
|
||||
if (!error.value && data.value?.promocodes.edges) {
|
||||
promocodesStore.setPromocodes(data.value.promocodes.edges);
|
||||
}
|
||||
|
||||
watch(error, (err) => {
|
||||
if (err) {
|
||||
console.error('usePromocodes error:', err);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
};
|
||||
}
|
||||
|
|
@ -38,11 +38,11 @@ export function useSearch() {
|
|||
} else {
|
||||
message = err.message;
|
||||
}
|
||||
useNotification(
|
||||
useNotification({
|
||||
message,
|
||||
'error',
|
||||
t('popup.errors.main')
|
||||
);
|
||||
type: 'error',
|
||||
title: t('popup.errors.main')
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1 +1,4 @@
|
|||
export * from './useUserActivation';
|
||||
export * from './useAvatarUpload';
|
||||
export * from './useUserUpdating';
|
||||
export * from './useDeposit';
|
||||
52
storefront/composables/user/useAvatarUpload.ts
Normal file
52
storefront/composables/user/useAvatarUpload.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import type {IAvatarUploadResponse,} from "~/types";
|
||||
import {UPLOAD_AVATAR} from "~/graphql/mutations/user";
|
||||
import {isGraphQLError} from "~/utils/error";
|
||||
import {useNotification} from "~/composables/notification";
|
||||
|
||||
export function useAvatarUpload() {
|
||||
const { t } = useI18n();
|
||||
const userStore = useUserStore();
|
||||
const { mutate, onDone, loading, error } = useMutation<IAvatarUploadResponse>(UPLOAD_AVATAR, {
|
||||
context: {
|
||||
hasUpload: true
|
||||
}}
|
||||
);
|
||||
|
||||
async function uploadAvatar(event: Event) {
|
||||
const file = (event.target as HTMLInputElement).files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
await mutate({ avatar: file });
|
||||
}
|
||||
|
||||
onDone(({ data }) => {
|
||||
const user = data?.uploadAvatar.user;
|
||||
if (user) {
|
||||
userStore.setUser(user);
|
||||
useNotification({
|
||||
message: t('popup.success.avatarUpload'),
|
||||
type: 'success'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
watch(error, (err) => {
|
||||
if (!err) return;
|
||||
console.error('useAvatarUpload error:', err);
|
||||
let message = t('popup.errors.defaultError');
|
||||
if (isGraphQLError(err)) {
|
||||
message = err.graphQLErrors?.[0]?.message || message;
|
||||
} else {
|
||||
message = err.message;
|
||||
}
|
||||
useNotification({
|
||||
message,
|
||||
type: 'error',
|
||||
title: t('popup.errors.main')
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
uploadAvatar
|
||||
};
|
||||
}
|
||||
42
storefront/composables/user/useDeposit.ts
Normal file
42
storefront/composables/user/useDeposit.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import {DEPOSIT} from "~/graphql/mutations/deposit";
|
||||
import {isGraphQLError} from "~/utils/error";
|
||||
import {useNotification} from "~/composables/notification";
|
||||
|
||||
export function useDeposit() {
|
||||
const {t} = useI18n();
|
||||
|
||||
const { mutate, loading, error } = useMutation(DEPOSIT);
|
||||
|
||||
async function deposit(
|
||||
amount: string
|
||||
) {
|
||||
const result = await mutate(
|
||||
{ amount }
|
||||
);
|
||||
|
||||
if (result?.data?.deposit) {
|
||||
window.open(result?.data.deposit.transaction.process.url)
|
||||
}
|
||||
}
|
||||
|
||||
watch(error, (err) => {
|
||||
if (!err) return;
|
||||
console.error('useDeposit error:', err);
|
||||
let message = t('popup.errors.defaultError');
|
||||
if (isGraphQLError(err)) {
|
||||
message = err.graphQLErrors?.[0]?.message || message;
|
||||
} else {
|
||||
message = err.message;
|
||||
}
|
||||
useNotification({
|
||||
message,
|
||||
type: 'error',
|
||||
title: t('popup.errors.main')
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
deposit,
|
||||
loading
|
||||
};
|
||||
}
|
||||
|
|
@ -15,10 +15,10 @@ export function useUserActivation() {
|
|||
const result = await mutate({ token, uid });
|
||||
|
||||
if (result?.data?.activateUser) {
|
||||
useNotification(
|
||||
t("popup.activationSuccess"),
|
||||
'success'
|
||||
);
|
||||
useNotification({
|
||||
message: t("popup.activationSuccess"),
|
||||
type: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -31,11 +31,11 @@ export function useUserActivation() {
|
|||
} else {
|
||||
message = err.message;
|
||||
}
|
||||
useNotification(
|
||||
useNotification({
|
||||
message,
|
||||
'error',
|
||||
t('popup.errors.main')
|
||||
);
|
||||
type: 'error',
|
||||
title: t('popup.errors.main')
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
|||
116
storefront/composables/user/useUserUpdating.ts
Normal file
116
storefront/composables/user/useUserUpdating.ts
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import {useLogout} from "@/composables/auth";
|
||||
import {UPDATE_USER} from "~/graphql/mutations/user";
|
||||
import type {IUserUpdatingResponse} from "~/types";
|
||||
import {useAppConfig} from "~/composables/config";
|
||||
import {isGraphQLError} from "~/utils/error";
|
||||
import {useNotification} from "~/composables/notification";
|
||||
import {DEFAULT_LOCALE} from "~/config/constants";
|
||||
import {useLocaleRedirect} from "~/composables/languages";
|
||||
|
||||
export function useUserUpdating() {
|
||||
const userStore = useUserStore();
|
||||
const {t} = useI18n();
|
||||
|
||||
const { mutate, loading, error } = useMutation<IUserUpdatingResponse>(UPDATE_USER);
|
||||
|
||||
const { COOKIES_LOCALE_KEY } = useAppConfig();
|
||||
const { checkAndRedirect } = useLocaleRedirect();
|
||||
const { logout } = useLogout();
|
||||
|
||||
const cookieLocale = useCookie(
|
||||
COOKIES_LOCALE_KEY,
|
||||
{
|
||||
default: () => DEFAULT_LOCALE,
|
||||
path: '/'
|
||||
}
|
||||
);
|
||||
const userUuid = computed(() => userStore.user?.uuid);
|
||||
const userEmail = computed(() => userStore.user?.email);
|
||||
|
||||
async function updateUser(
|
||||
firstName: string,
|
||||
lastName: string,
|
||||
email: string,
|
||||
phoneNumber: string,
|
||||
password: string,
|
||||
confirmPassword: string
|
||||
) {
|
||||
const fields = {
|
||||
uuid: userUuid.value,
|
||||
firstName,
|
||||
lastName,
|
||||
email,
|
||||
phoneNumber,
|
||||
password,
|
||||
confirmPassword
|
||||
};
|
||||
|
||||
const params = Object.fromEntries(
|
||||
Object.entries(fields).filter(([_, value]) =>
|
||||
value !== undefined && value !== null && value !== ''
|
||||
)
|
||||
);
|
||||
|
||||
// if (('password' in params && !('passwordConfirm' in params)) ||
|
||||
// (!('password' in params) && 'passwordConfirm' in params)) {
|
||||
// ElNotification({
|
||||
// title: t('popup.errors.main'),
|
||||
// message: t('popup.errors.noDataToUpdate'),
|
||||
// type: 'error'
|
||||
// });
|
||||
// }
|
||||
|
||||
if (Object.keys(params).length === 0) {
|
||||
useNotification({
|
||||
message: t('popup.errors.noDataToUpdate'),
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
|
||||
const result = await mutate(params);
|
||||
const data = result?.data?.updateUser;
|
||||
|
||||
if (data) {
|
||||
if (userEmail.value !== email) {
|
||||
await logout();
|
||||
|
||||
useNotification({
|
||||
message: t('popup.success.confirmEmail'),
|
||||
type: 'success'
|
||||
});
|
||||
} else {
|
||||
userStore.setUser(data.user);
|
||||
|
||||
useNotification({
|
||||
message: t('popup.success.userUpdate'),
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
if (data.user.language !== cookieLocale.value) {
|
||||
await checkAndRedirect(data.user.language);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watch(error, (err) => {
|
||||
if (!err) return;
|
||||
console.error('useUserUpdating error:', err);
|
||||
let message = t('popup.errors.defaultError');
|
||||
if (isGraphQLError(err)) {
|
||||
message = err.graphQLErrors?.[0]?.message || message;
|
||||
} else {
|
||||
message = err.message;
|
||||
}
|
||||
useNotification({
|
||||
message,
|
||||
type: 'error',
|
||||
title: t('popup.errors.main')
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
updateUser,
|
||||
loading
|
||||
};
|
||||
}
|
||||
|
|
@ -9,12 +9,12 @@ export async function useWishlist() {
|
|||
);
|
||||
|
||||
if (!error.value && data.value?.wishlists.edges[0]) {
|
||||
wishlistStore.setWishlist(data.value.wishlists.edges[0].node)
|
||||
wishlistStore.setWishlist(data.value.wishlists.edges[0].node);
|
||||
}
|
||||
|
||||
watch(error, (err) => {
|
||||
if (err) {
|
||||
console.error('useWishlist error:', err)
|
||||
console.error('useWishlist error:', err);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,9 @@ interface IOverwriteWishlistArguments {
|
|||
export function useWishlistOverwrite() {
|
||||
const {t} = useI18n();
|
||||
const wishlistStore = useWishlistStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const isAuthenticated = computed(() => userStore.isAuthenticated);
|
||||
const wishlistUuid = computed(() => wishlistStore.wishlist?.uuid);
|
||||
|
||||
const {
|
||||
|
|
@ -52,6 +54,7 @@ export function useWishlistOverwrite() {
|
|||
async function overwriteWishlist (
|
||||
args: IOverwriteWishlistArguments
|
||||
) {
|
||||
if (isAuthenticated.value) {
|
||||
switch (args.type) {
|
||||
case "add":
|
||||
const addResult = await addMutate({
|
||||
|
|
@ -62,10 +65,10 @@ export function useWishlistOverwrite() {
|
|||
if (addResult?.data?.addWishlistProduct?.wishlist) {
|
||||
wishlistStore.setWishlist(addResult.data.addWishlistProduct.wishlist);
|
||||
|
||||
useNotification(
|
||||
t('popup.success.addToWishlist', { product: args.productName }),
|
||||
'success'
|
||||
);
|
||||
useNotification({
|
||||
message: t('popup.success.addToWishlist', { product: args.productName }),
|
||||
type: 'success'
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -79,10 +82,10 @@ export function useWishlistOverwrite() {
|
|||
if (removeResult?.data?.removeWishlistProduct?.wishlist) {
|
||||
wishlistStore.setWishlist(removeResult.data.removeWishlistProduct.wishlist);
|
||||
|
||||
useNotification(
|
||||
t('popup.success.removeFromWishlist', { product: args.productName }),
|
||||
'success'
|
||||
);
|
||||
useNotification({
|
||||
message: t('popup.success.removeFromWishlist', { product: args.productName }),
|
||||
type: 'success'
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -96,10 +99,10 @@ export function useWishlistOverwrite() {
|
|||
if (removeAllResult?.data?.removeAllWishlistProducts?.wishlist) {
|
||||
wishlistStore.setWishlist(removeAllResult.data.removeAllWishlistProducts.wishlist);
|
||||
|
||||
useNotification(
|
||||
t('popup.success.removeAllFromWishlist'),
|
||||
'success'
|
||||
);
|
||||
useNotification({
|
||||
message: t('popup.success.removeAllFromWishlist'),
|
||||
type: 'success'
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -113,10 +116,10 @@ export function useWishlistOverwrite() {
|
|||
|
||||
if (bulkResult?.data?.bulkWishlistAction?.wishlist) {
|
||||
wishlistStore.setWishlist(bulkResult.data.bulkWishlistAction.wishlist);
|
||||
useNotification(
|
||||
t('popup.success.bulkRemoveWishlist'),
|
||||
'success'
|
||||
);
|
||||
useNotification({
|
||||
message: t('popup.success.bulkRemoveWishlist'),
|
||||
type: 'success'
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -124,6 +127,12 @@ export function useWishlistOverwrite() {
|
|||
default:
|
||||
console.error('No type provided for overwriteWishlist');
|
||||
}
|
||||
} else {
|
||||
useNotification({
|
||||
message: t('popup.errors.loginFirst'),
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
watch(addError || removedError || removeAllError || bulkError, (err) => {
|
||||
|
|
@ -135,11 +144,11 @@ export function useWishlistOverwrite() {
|
|||
} else {
|
||||
message = err.message;
|
||||
}
|
||||
useNotification(
|
||||
useNotification({
|
||||
message,
|
||||
'error',
|
||||
t('popup.errors.main')
|
||||
);
|
||||
type: 'error',
|
||||
title: t('popup.errors.main')
|
||||
});
|
||||
});
|
||||
|
||||
return{
|
||||
|
|
|
|||
|
|
@ -86,3 +86,5 @@ export const SUPPORTED_LOCALES: LocaleDefinition[] = [
|
|||
];
|
||||
|
||||
export const DEFAULT_LOCALE = SUPPORTED_LOCALES.find(locale => locale.default)?.code || 'en-gb';
|
||||
|
||||
export const CURRENCY = '$'
|
||||
12
storefront/graphql/fragments/promocodes.fragment.ts
Normal file
12
storefront/graphql/fragments/promocodes.fragment.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
export const PROMOCODE_FRAGMENT = gql`
|
||||
fragment Promocode on PromoCodeType {
|
||||
code
|
||||
discount
|
||||
discountType
|
||||
endTime
|
||||
id
|
||||
startTime
|
||||
usedOn
|
||||
uuid
|
||||
}
|
||||
`
|
||||
|
|
@ -12,5 +12,10 @@ export const USER_FRAGMENT = gql`
|
|||
balance {
|
||||
amount
|
||||
}
|
||||
orders {
|
||||
uuid
|
||||
humanReadableId
|
||||
status
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
@ -7,7 +7,8 @@ export const REGISTER = gql`
|
|||
$email: String!,
|
||||
$phoneNumber: String!,
|
||||
$password: String!,
|
||||
$confirmPassword: String!
|
||||
$confirmPassword: String!,
|
||||
$referrer: String!,
|
||||
) {
|
||||
createUser(
|
||||
firstName: $firstName,
|
||||
|
|
@ -15,7 +16,8 @@ export const REGISTER = gql`
|
|||
email: $email,
|
||||
phoneNumber: $phoneNumber,
|
||||
password: $password,
|
||||
confirmPassword: $confirmPassword
|
||||
confirmPassword: $confirmPassword,
|
||||
referrer: $referrer
|
||||
) {
|
||||
success
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,3 +40,18 @@ export const UPDATE_USER = gql`
|
|||
}
|
||||
${USER_FRAGMENT}
|
||||
`
|
||||
|
||||
export const UPLOAD_AVATAR = gql`
|
||||
mutation uploadAvatar(
|
||||
$avatar: Upload!
|
||||
) {
|
||||
uploadAvatar(
|
||||
avatar: $avatar
|
||||
) {
|
||||
user {
|
||||
...User
|
||||
}
|
||||
}
|
||||
}
|
||||
${USER_FRAGMENT}
|
||||
`
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
import combineQuery from 'graphql-combine-query'
|
||||
import {GET_PRODUCTS} from "~/graphql/queries/standalone/products";
|
||||
import {GET_CATEGORY_BY_SLUG} from "~/graphql/queries/standalone/categories";
|
||||
|
||||
export const getStore = (
|
||||
productsVariables?: {
|
||||
first?: number;
|
||||
productAfter?: string;
|
||||
categoriesSlug?: string;
|
||||
orderby?: string;
|
||||
minPrice?: number;
|
||||
maxPrice?: number;
|
||||
attributes?: string;
|
||||
},
|
||||
categoryVariables?: {
|
||||
categorySlug?: string;
|
||||
}
|
||||
) => {
|
||||
const { document, variables } = combineQuery('getStoreData')
|
||||
.add(GET_PRODUCTS, productsVariables || {})
|
||||
.add(GET_CATEGORY_BY_SLUG, categoryVariables || {})
|
||||
|
||||
return { document, variables };
|
||||
};
|
||||
14
storefront/graphql/queries/standalone/promocodes.ts
Normal file
14
storefront/graphql/queries/standalone/promocodes.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import {PROMOCODE_FRAGMENT} from "~/graphql/fragments/promocodes.fragment";
|
||||
|
||||
export const GET_PROMOCODES = gql`
|
||||
query getPromocodes {
|
||||
promocodes {
|
||||
edges {
|
||||
node {
|
||||
...Promocode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${PROMOCODE_FRAGMENT}
|
||||
`
|
||||
|
|
@ -45,7 +45,8 @@
|
|||
"errors": {
|
||||
"main": "Error!",
|
||||
"defaultError": "Something went wrong..",
|
||||
"noDataToUpdate": "There is no data to update."
|
||||
"noDataToUpdate": "There is no data to update.",
|
||||
"loginFirst": "You should be logged in to do this action!"
|
||||
},
|
||||
"success": {
|
||||
"login": "Sign in successes",
|
||||
|
|
@ -60,7 +61,11 @@
|
|||
"addToWishlist": "{product} has been added to the wishlist!",
|
||||
"removeFromWishlist": "{product} has been removed from the wishlist!",
|
||||
"removeAllFromWishlist": "You have successfully emptied the wishlist!",
|
||||
"bulkRemoveWishlist": "Selected items have been successfully removed from the wishlist!"
|
||||
"bulkRemoveWishlist": "Selected items have been successfully removed from the wishlist!",
|
||||
"avatarUpload": "You have successfully uploaded an avatar!",
|
||||
"userUpdate": "Profile successfully updated!",
|
||||
"referralCopy": "You copied your referal link!",
|
||||
"promocodeCopy": "You copied your promocode!"
|
||||
},
|
||||
"addToCartLimit": "Total quantity limit is {quantity}!",
|
||||
"failAdd": "Please log in to make a purchase",
|
||||
|
|
@ -122,7 +127,10 @@
|
|||
"catalog": "Catalog",
|
||||
"contact": "Contact",
|
||||
"wishlist": "Wishlist",
|
||||
"cart": "Cart"
|
||||
"cart": "Cart",
|
||||
"settings": "Settings",
|
||||
"balance": "Balance",
|
||||
"promocodes": "Promocodes"
|
||||
},
|
||||
"contact": {
|
||||
"title": "Contact us"
|
||||
|
|
@ -148,19 +156,31 @@
|
|||
},
|
||||
"profile": {
|
||||
"settings": {
|
||||
"title": "Settings"
|
||||
"title": "Settings",
|
||||
"joinData": "Date of registration",
|
||||
"accountInfo": "Account info",
|
||||
"copyReferral": "Copy my referral link",
|
||||
"referralTooltip": "You will get a referral link after a successful purchase"
|
||||
},
|
||||
"orders": {
|
||||
"title": "Orders"
|
||||
},
|
||||
"wishlist": {
|
||||
"title": "Wishlist",
|
||||
"total": "{quantity} items worth {amount}"
|
||||
"total": "{quantity} items worth {amount}",
|
||||
"deleteTooltip": "Delete all from wishlist"
|
||||
},
|
||||
"cart": {
|
||||
"title": "Cart",
|
||||
"quantity": "Quantity: ",
|
||||
"total": "Total: "
|
||||
}
|
||||
},
|
||||
"balance": {
|
||||
"title": "Balance"
|
||||
},
|
||||
"promocodes": {
|
||||
"title": "Promocodes"
|
||||
},
|
||||
"logout": "Logout"
|
||||
}
|
||||
}
|
||||
|
|
@ -27,9 +27,9 @@ export default defineNuxtConfig({
|
|||
authType: 'Bearer',
|
||||
authHeader: 'X-EVIBES-AUTH',
|
||||
tokenStorage: 'cookie',
|
||||
tokenName: `${process.env.EVIBES_PROJECT_NAME?.toLowerCase()}-access`
|
||||
tokenName: `${process.env.EVIBES_PROJECT_NAME?.toLowerCase()}-access`,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
|
|
|
|||
51
storefront/package-lock.json
generated
51
storefront/package-lock.json
generated
|
|
@ -15,6 +15,7 @@
|
|||
"@vueuse/integrations": "^13.3.0",
|
||||
"@vueuse/nuxt": "^13.3.0",
|
||||
"@vueuse/router": "^13.3.0",
|
||||
"apollo-upload-client": "17.0.0",
|
||||
"axios": "^1.9.0",
|
||||
"graphql-combine-query": "^1.2.4",
|
||||
"graphql-tag": "^2.12.6",
|
||||
|
|
@ -75,7 +76,6 @@
|
|||
"version": "3.13.8",
|
||||
"resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.13.8.tgz",
|
||||
"integrity": "sha512-YM9lQpm0VfVco4DSyKooHS/fDTiKQcCHfxr7i3iL6a0kP/jNO5+4NFK6vtRDxaYisd5BrwOZHLJpPBnvRVpKPg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-typed-document-node/core": "^3.1.1",
|
||||
|
|
@ -896,7 +896,6 @@
|
|||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz",
|
||||
"integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
|
||||
|
|
@ -3488,7 +3487,6 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@wry/caches/-/caches-1.0.1.tgz",
|
||||
"integrity": "sha512-bXuaUNLVVkD20wcGBWRyo7j9N3TxePEWFZj2Y+r9OoUzfqmavM84+mFykRicNsBqatba5JLay1t48wxaXaWnlA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
|
|
@ -3501,7 +3499,6 @@
|
|||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@wry/context/-/context-0.7.4.tgz",
|
||||
"integrity": "sha512-jmT7Sb4ZQWI5iyu3lobQxICu2nC/vbUhP0vIdd6tHC9PTfenmRmuIFqktc6GH9cgi+ZHnsLWPvfSvc4DrYmKiQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
|
|
@ -3514,7 +3511,6 @@
|
|||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.5.7.tgz",
|
||||
"integrity": "sha512-BRFORjsTuQv5gxcXsuDXx6oGRhuVsEGwZy6LOzRRfgu+eSfxbhUQ9L9YtSEIuIjY/o7g3iWFjrc5eSY1GXP2Dw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
|
|
@ -3527,7 +3523,6 @@
|
|||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.5.0.tgz",
|
||||
"integrity": "sha512-FNoYzHawTMk/6KMQoEG5O4PuioX19UbwdQKF44yw0nLfOypfQdjtfZzo/UIJWAJ23sNIFbD1Ug9lbaDGMwbqQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
|
|
@ -3678,6 +3673,25 @@
|
|||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/apollo-upload-client": {
|
||||
"version": "17.0.0",
|
||||
"resolved": "https://registry.npmjs.org/apollo-upload-client/-/apollo-upload-client-17.0.0.tgz",
|
||||
"integrity": "sha512-pue33bWVbdlXAGFPkgz53TTmxVMrKeQr0mdRcftNY+PoHIdbGZD0hoaXHvO6OePJAkFz7OiCFUf98p1G/9+Ykw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"extract-files": "^11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >= 16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jaydenseric"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apollo/client": "^3.0.0",
|
||||
"graphql": "14 - 16"
|
||||
}
|
||||
},
|
||||
"node_modules/archiver": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz",
|
||||
|
|
@ -6208,6 +6222,18 @@
|
|||
"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/extract-files": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/extract-files/-/extract-files-11.0.0.tgz",
|
||||
"integrity": "sha512-FuoE1qtbJ4bBVvv94CC7s0oTnKUGvQs+Rjf1L2SJFfS+HTVVjhPFtehPdQ0JiGPqVNfSSZvL5yzHHQq2Z4WNhQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20 || >= 14.13"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jaydenseric"
|
||||
}
|
||||
},
|
||||
"node_modules/extract-zip": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
|
||||
|
|
@ -6888,7 +6914,7 @@
|
|||
"version": "5.16.2",
|
||||
"resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.16.2.tgz",
|
||||
"integrity": "sha512-E1uccsZxt/96jH/OwmLPuXMACILs76pKF2i3W861LpKBCYtGIyPQGtWLuBLkND4ox1KHns70e83PS4te50nvPQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
|
|
@ -6998,7 +7024,6 @@
|
|||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"react-is": "^16.7.0"
|
||||
|
|
@ -8229,7 +8254,6 @@
|
|||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
|
|
@ -9110,7 +9134,6 @@
|
|||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
|
|
@ -9250,7 +9273,6 @@
|
|||
"version": "0.18.1",
|
||||
"resolved": "https://registry.npmjs.org/optimism/-/optimism-0.18.1.tgz",
|
||||
"integrity": "sha512-mLXNwWPa9dgFyDqkNi54sjDyNJ9/fTI6WGBLgnXku1vdKY/jovHfZT5r+aiVeFFLOz+foPNOm5YJ4mqgld2GBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@wry/caches": "^1.0.0",
|
||||
|
|
@ -10290,7 +10312,6 @@
|
|||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
|
|
@ -10458,7 +10479,6 @@
|
|||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/read-package-up": {
|
||||
|
|
@ -10572,7 +10592,6 @@
|
|||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/rehackt/-/rehackt-0.1.0.tgz",
|
||||
"integrity": "sha512-7kRDOuLHB87D/JESKxQoRwv4DzbIdwkAGQ7p6QKGdVlY1IZheUnVhlk/4UZlNUVxdAXpyxikE3URsG067ybVzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
|
|
@ -11606,7 +11625,6 @@
|
|||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
|
||||
"integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
|
|
@ -11870,7 +11888,6 @@
|
|||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.10.3.tgz",
|
||||
"integrity": "sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
|
|
@ -13305,14 +13322,12 @@
|
|||
"version": "0.8.15",
|
||||
"resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz",
|
||||
"integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/zen-observable-ts": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.2.5.tgz",
|
||||
"integrity": "sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"zen-observable": "0.8.15"
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
"@vueuse/integrations": "^13.3.0",
|
||||
"@vueuse/nuxt": "^13.3.0",
|
||||
"@vueuse/router": "^13.3.0",
|
||||
"apollo-upload-client": "17.0.0",
|
||||
"axios": "^1.9.0",
|
||||
"graphql-combine-query": "^1.2.4",
|
||||
"graphql-tag": "^2.12.6",
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
<script setup>
|
||||
import {useBrandByUuid} from "~/composables/brands";
|
||||
import {usePageTitle} from "~/composables/utils/index.js";
|
||||
import {usePageTitle} from "~/composables/utils";
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
|
|
|
|||
|
|
@ -54,16 +54,42 @@
|
|||
allow-half
|
||||
disabled
|
||||
/>
|
||||
<div class="product__info-price">{{ product.price }}</div>
|
||||
<div class="product__info-price">{{ product.price }} {{ CURRENCY }}</div>
|
||||
<div class="product__info-bottom">
|
||||
<ui-button
|
||||
class="product__info-button"
|
||||
v-if="isProductInCart"
|
||||
@click="overwriteOrder({
|
||||
type: 'remove',
|
||||
productUuid: product.uuid,
|
||||
productName: product.name
|
||||
})"
|
||||
:isLoading="removeLoading"
|
||||
>
|
||||
{{ t('buttons.removeFromCart') }}
|
||||
</ui-button>
|
||||
<ui-button
|
||||
class="product__info-button"
|
||||
v-else
|
||||
@click="overwriteOrder({
|
||||
type: 'add',
|
||||
productUuid: product.uuid,
|
||||
productName: product.name
|
||||
})"
|
||||
:isLoading="addLoading"
|
||||
>
|
||||
{{ t('buttons.addToCart') }}
|
||||
</ui-button>
|
||||
<div class="product__info-wishlist">
|
||||
<icon name="mdi:cards-heart-outline" size="28" />
|
||||
<!-- <icon name="mdi:cards-heart" size="28" />-->
|
||||
<div
|
||||
class="product__info-wishlist"
|
||||
@click="overwriteWishlist({
|
||||
type: (isProductInWishlist ? 'remove' : 'add'),
|
||||
productUuid: product.uuid,
|
||||
productName: product.name
|
||||
})"
|
||||
>
|
||||
<icon name="mdi:cards-heart" size="28" v-if="isProductInWishlist" />
|
||||
<icon name="mdi:cards-heart-outline" size="28" v-else />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -109,7 +135,7 @@
|
|||
slidesPerView: 4
|
||||
}
|
||||
}"
|
||||
:navigation="{ prevEl: prevButton, nextEl: nextButton }"
|
||||
:navigation="{ prevEl: '.prev', nextEl: '.next' }"
|
||||
>
|
||||
<swiper-slide
|
||||
v-for="prod in products"
|
||||
|
|
@ -138,20 +164,38 @@ import { Swiper, SwiperSlide } from 'swiper/vue';
|
|||
import 'swiper/css';
|
||||
import 'swiper/css/navigation';
|
||||
import {Navigation} from "swiper/modules";
|
||||
import {CURRENCY} from "~/config/constants";
|
||||
import {useWishlistOverwrite} from "~/composables/wishlist";
|
||||
import {useOrderOverwrite} from "~/composables/orders/useOrderOverwrite";
|
||||
|
||||
const route = useRoute();
|
||||
const {t} = useI18n();
|
||||
const wishlistStore = useWishlistStore();
|
||||
const cartStore = useCartStore();
|
||||
|
||||
const { setPageTitle } = usePageTitle();
|
||||
const { scrollTo } = useScrollTo();
|
||||
|
||||
const slug = useRouteParams<string>('slug');
|
||||
|
||||
const { overwriteWishlist } = useWishlistOverwrite();
|
||||
const { addLoading, removeLoading, overwriteOrder } = useOrderOverwrite();
|
||||
const { product } = await useProductBySlug(slug.value);
|
||||
const { products, getProducts } = await useProducts();
|
||||
await getProducts({
|
||||
categoriesSlugs: product.value?.category.slug
|
||||
})
|
||||
});
|
||||
|
||||
const isProductInWishlist = computed(() => {
|
||||
const el = wishlistStore.wishlist?.products?.edges.find(
|
||||
(el) => el?.node?.uuid === product.value?.uuid
|
||||
);
|
||||
|
||||
return !!el;
|
||||
});
|
||||
const isProductInCart = computed(() => {
|
||||
return cartStore.currentOrder?.orderProducts?.edges.find((prod) => prod.node.product.uuid === product.value?.uuid);
|
||||
});
|
||||
|
||||
const images = computed<string[]>(() =>
|
||||
product.value
|
||||
|
|
|
|||
33
storefront/pages/profile/balance.vue
Normal file
33
storefront/pages/profile/balance.vue
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<div class="balance">
|
||||
<h2>{{ t('profile.balance.title') }}: {{ user?.balance.amount }}</h2>
|
||||
<forms-deposit />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {usePageTitle} from "~/composables/utils";
|
||||
|
||||
const {t} = useI18n();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const user = computed(() => userStore.user);
|
||||
|
||||
const { setPageTitle } = usePageTitle();
|
||||
|
||||
setPageTitle(t('breadcrumbs.balance'));
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.balance {
|
||||
background-color: $white;
|
||||
width: 100%;
|
||||
box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.2);
|
||||
padding: 20px;
|
||||
border-radius: $default_border_radius;
|
||||
height: fit-content;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 50px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -13,13 +13,14 @@
|
|||
:key="product.node.uuid"
|
||||
:product="product.node.product"
|
||||
:isList="true"
|
||||
:isToolsVisible="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {usePageTitle} from "~/composables/utils/index.js";
|
||||
<script setup lang="ts">
|
||||
import {usePageTitle} from "~/composables/utils";
|
||||
|
||||
const {t} = useI18n();
|
||||
const cartStore = useCartStore();
|
||||
|
|
@ -61,6 +62,7 @@ setPageTitle(t('breadcrumbs.cart'));
|
|||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.2);
|
||||
border-radius: $default_border_radius;
|
||||
|
||||
& p {
|
||||
font-weight: 600;
|
||||
|
|
@ -80,6 +82,7 @@ setPageTitle(t('breadcrumbs.cart'));
|
|||
flex-direction: column;
|
||||
gap: 20px;
|
||||
box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.2);
|
||||
border-radius: $default_border_radius;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
|
|
|||
88
storefront/pages/profile/promocodes.vue
Normal file
88
storefront/pages/profile/promocodes.vue
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
<template>
|
||||
<div class="promocodes">
|
||||
<h2>{{ t('profile.promocodes.title') }}</h2>
|
||||
<div class="promocodes__list">
|
||||
<div
|
||||
class="promocodes__item"
|
||||
v-for="promocode in promocodes"
|
||||
:key="promocode.node.uuid"
|
||||
>
|
||||
<icon
|
||||
name="material-symbols:content-copy"
|
||||
size="20"
|
||||
class="promocodes__item-button"
|
||||
@click="copyCode(promocode.node.code)"
|
||||
/>
|
||||
<p>{{ promocode.node.code }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {usePageTitle} from "~/composables/utils";
|
||||
import {useNotification} from "~/composables/notification/index.js";
|
||||
|
||||
const {t} = useI18n();
|
||||
const promocodesStore = usePromocodeStore();
|
||||
|
||||
const promocodes = computed(() => promocodesStore.promocodes);
|
||||
|
||||
const copyCode = (code: string) => {
|
||||
navigator.clipboard.writeText(code)
|
||||
.then(() => {
|
||||
useNotification({
|
||||
message: t('popup.success.promocodeCopy'),
|
||||
type: 'success'
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
// TODO: display more info about promo
|
||||
const { setPageTitle } = usePageTitle();
|
||||
|
||||
setPageTitle(t('breadcrumbs.promocodes'));
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.promocodes {
|
||||
background-color: $white;
|
||||
width: 100%;
|
||||
box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.2);
|
||||
padding: 20px;
|
||||
border-radius: $default_border_radius;
|
||||
height: fit-content;
|
||||
|
||||
&__list {
|
||||
margin-top: 50px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
&__item {
|
||||
border-radius: $default_border_radius;
|
||||
border: 1px solid $accent;
|
||||
padding: 7px 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
|
||||
&-button {
|
||||
cursor: pointer;
|
||||
transition: 0.2s;
|
||||
color: $accentDark;
|
||||
|
||||
@include hover {
|
||||
color: $accent;
|
||||
}
|
||||
}
|
||||
|
||||
& p {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,15 +1,239 @@
|
|||
<template>
|
||||
<div class="settings">
|
||||
<p>settings</p>
|
||||
<div class="settings__top">
|
||||
<div class="settings__top-left">
|
||||
<div class="settings__avatar">
|
||||
<input type="file" id="avatar" @change="uploadAvatar" />
|
||||
<label for="avatar">
|
||||
<img class="settings__avatar-image" v-if="user?.avatar" :src="user?.avatar" alt="">
|
||||
<icon name="material-symbols-light:person-outline-rounded" size="100" />
|
||||
<span class="settings__avatar-inner">
|
||||
<icon name="material-symbols:upload" size="40" />
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="settings__top-inner">
|
||||
<h2>{{ user?.firstName }} {{ user?.lastName }}</h2>
|
||||
<p>{{ t('profile.settings.joinData') }}: {{ joinData }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<el-tooltip
|
||||
:content="t('profile.settings.referralTooltip')"
|
||||
placement="top-end"
|
||||
:disabled="finishedOrdersQuantity > 0"
|
||||
>
|
||||
<button
|
||||
class="settings__button"
|
||||
@click="copyReferral"
|
||||
:disabled="finishedOrdersQuantity === 0"
|
||||
>
|
||||
<icon name="material-symbols:content-copy" size="20" />
|
||||
{{ t('profile.settings.copyReferral') }}
|
||||
</button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="settings__main">
|
||||
<p>{{ t('profile.settings.accountInfo') }}</p>
|
||||
<forms-update />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {usePageTitle} from "~/composables/utils";
|
||||
import { useDate } from '~/composables/date';
|
||||
import {DEFAULT_LOCALE} from "~/config/constants";
|
||||
import { useAppConfig } from "~/composables/config";
|
||||
import {useAvatarUpload} from "~/composables/user";
|
||||
import {useNotification} from "~/composables/notification";
|
||||
|
||||
const {t} = useI18n();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const { APP_DOMAIN, COOKIES_LOCALE_KEY } = useAppConfig();
|
||||
const { uploadAvatar } = useAvatarUpload();
|
||||
|
||||
const cookieLocale = useCookie(
|
||||
COOKIES_LOCALE_KEY,
|
||||
{
|
||||
default: () => DEFAULT_LOCALE,
|
||||
path: '/'
|
||||
}
|
||||
);
|
||||
|
||||
const user = computed(() => userStore.user);
|
||||
const finishedOrdersQuantity = computed(() => userStore.finishedOrdersQuantity);
|
||||
const joinData = computed(() => {
|
||||
return useDate(
|
||||
user.value?.dateJoined, cookieLocale.value
|
||||
);
|
||||
});
|
||||
|
||||
const referralLink = computed(() => {
|
||||
if (finishedOrdersQuantity.value > 0) {
|
||||
return `https://${APP_DOMAIN}/${DEFAULT_LOCALE}/?referrer=` + user.value?.uuid;
|
||||
} else {
|
||||
return `https://${APP_DOMAIN}/${DEFAULT_LOCALE}/`;
|
||||
}
|
||||
});
|
||||
|
||||
const copyReferral = () => {
|
||||
if (finishedOrdersQuantity.value > 0) {
|
||||
navigator.clipboard.writeText(referralLink.value)
|
||||
.then(() => {
|
||||
useNotification({
|
||||
message: t('popup.success.referralCopy'),
|
||||
type: 'success'
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const { setPageTitle } = usePageTitle();
|
||||
|
||||
setPageTitle(t('breadcrumbs.settings'));
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.settings {
|
||||
background-color: $white;
|
||||
width: 100%;
|
||||
box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.2);
|
||||
padding: 20px;
|
||||
border-radius: $default_border_radius;
|
||||
height: fit-content;
|
||||
|
||||
&__top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 25px;
|
||||
|
||||
&-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
&-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
|
||||
& h2 {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
& p {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__avatar {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: $default_border_radius;
|
||||
border: 2px solid $accent;
|
||||
background-color: rgba($accent, 0.2);
|
||||
box-shadow: 0 0 9.1px 0 rgba(0, 0, 0, 0.20);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
@include hover {
|
||||
.settings__avatar-inner {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
& label {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
|
||||
& span {
|
||||
color: $accent;
|
||||
}
|
||||
}
|
||||
|
||||
&-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
&-inner {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
opacity: 0;
|
||||
transition: 0.2s;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
|
||||
& span {
|
||||
font-size: 60px !important;
|
||||
color: $white !important;
|
||||
}
|
||||
}
|
||||
|
||||
& input {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__button {
|
||||
cursor: pointer;
|
||||
border-radius: $default_border_radius;
|
||||
background-color: rgba($accent, 0.2);
|
||||
border: 1px solid $accent;
|
||||
padding: 5px 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
transition: 0.2s;
|
||||
|
||||
color: $accent;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
|
||||
@include hover {
|
||||
background-color: $accent;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: #c0c0c0;
|
||||
cursor: not-allowed;
|
||||
|
||||
@include hover {
|
||||
background-color: #c0c0c0;
|
||||
color: $accent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__main {
|
||||
margin-top: 50px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 25px;
|
||||
|
||||
& p {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: $accentDark;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -11,12 +11,17 @@
|
|||
</ui-checkbox>
|
||||
<p>{{ t('profile.wishlist.total', {quantity: productsInWishlist.length, amount: totalPrice}) }}</p>
|
||||
</div>
|
||||
<el-tooltip
|
||||
:content="t('profile.wishlist.deleteTooltip')"
|
||||
placement="top-end"
|
||||
>
|
||||
<div
|
||||
class="wishlist__top-button"
|
||||
@click="onBulkRemove"
|
||||
>
|
||||
<icon name="material-symbols-light:delete-rounded" size="20" />
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="wishlist__list">
|
||||
<div class="wishlist__list-inner">
|
||||
|
|
@ -118,6 +123,7 @@ setPageTitle(t('breadcrumbs.wishlist'));
|
|||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.2);
|
||||
border-radius: $default_border_radius;
|
||||
|
||||
&-left {
|
||||
display: flex;
|
||||
|
|
@ -155,6 +161,7 @@ setPageTitle(t('breadcrumbs.wishlist'));
|
|||
background-color: $white;
|
||||
padding: 20px;
|
||||
box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.2);
|
||||
border-radius: $default_border_radius;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,18 @@
|
|||
import { provideApolloClient } from '@vue/apollo-composable'
|
||||
import type { ApolloClient } from '@apollo/client/core'
|
||||
import { useAppConfig } from '~/composables/config';
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
const apollo = useApollo()
|
||||
const defaultClient = apollo.clients!.default as ApolloClient<unknown>
|
||||
const { COOKIES_LOCALE_KEY } = useAppConfig();
|
||||
const localeCookie = useCookie(COOKIES_LOCALE_KEY);
|
||||
const originalFetch = globalThis.fetch;
|
||||
|
||||
provideApolloClient(defaultClient)
|
||||
})
|
||||
globalThis.fetch = (input, init = {}) => {
|
||||
const lang = localeCookie.value || 'en-gb';
|
||||
const headers = new Headers(init.headers as any);
|
||||
headers.set('Accept-Language', lang);
|
||||
|
||||
return originalFetch(input, {
|
||||
...init,
|
||||
headers
|
||||
});
|
||||
};
|
||||
});
|
||||
13
storefront/stores/promocodes.ts
Normal file
13
storefront/stores/promocodes.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import type {IPromocode} from "~/types";
|
||||
|
||||
export const usePromocodeStore = defineStore('promocode', () => {
|
||||
const promocodes = ref<{ node: IPromocode }[] | null>(null);
|
||||
const setPromocodes = (promo: { node: IPromocode }[]) => {
|
||||
promocodes.value = promo
|
||||
};
|
||||
|
||||
return {
|
||||
promocodes,
|
||||
setPromocodes
|
||||
}
|
||||
})
|
||||
|
|
@ -12,7 +12,11 @@ export const useUserStore = defineStore('user', () => {
|
|||
);
|
||||
|
||||
const user = ref<IUser | null>(null);
|
||||
|
||||
const isAuthenticated = computed(() => Boolean(cookieAccess.value && user.value));
|
||||
const finishedOrdersQuantity = computed(() => {
|
||||
return user.value?.orders.filter((order) => order.status === 'FINISHED').length || 0;
|
||||
});
|
||||
|
||||
const setUser = (data: IUser | null) => {
|
||||
user.value = data;
|
||||
|
|
@ -21,6 +25,7 @@ export const useUserStore = defineStore('user', () => {
|
|||
return {
|
||||
user,
|
||||
setUser,
|
||||
isAuthenticated
|
||||
isAuthenticated,
|
||||
finishedOrdersQuantity
|
||||
};
|
||||
})
|
||||
});
|
||||
9
storefront/types/api/promocodes.ts
Normal file
9
storefront/types/api/promocodes.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import type {IPromocode} from "~/types";
|
||||
|
||||
export interface IPromocodesResponse {
|
||||
promocodes: {
|
||||
edges: {
|
||||
node: IPromocode
|
||||
}[]
|
||||
}
|
||||
}
|
||||
|
|
@ -11,3 +11,15 @@ export interface IUserActivationResponse {
|
|||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface IUserUpdatingResponse {
|
||||
updateUser: {
|
||||
user: IUser
|
||||
}
|
||||
}
|
||||
|
||||
export interface IAvatarUploadResponse {
|
||||
uploadAvatar: {
|
||||
user: IUser
|
||||
}
|
||||
}
|
||||
10
storefront/types/app/promocodes.ts
Normal file
10
storefront/types/app/promocodes.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
export interface IPromocode {
|
||||
code: string,
|
||||
discount: string,
|
||||
discountType: string,
|
||||
endTime: string,
|
||||
id: string,
|
||||
startTime: string,
|
||||
usedOn: string,
|
||||
uuid: string
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
export interface IUser {
|
||||
avatar: string,
|
||||
uuid: string,
|
||||
attributes: string | null
|
||||
attributes: string | null,
|
||||
language: string,
|
||||
email: string,
|
||||
firstName: string,
|
||||
|
|
@ -10,5 +10,10 @@ export interface IUser {
|
|||
dateJoined: string,
|
||||
balance: {
|
||||
amount: number,
|
||||
}
|
||||
},
|
||||
orders: {
|
||||
uuid: string,
|
||||
humanReadableId: string,
|
||||
status: string
|
||||
}[]
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ export * from './app/wishlist'
|
|||
export * from './app/orders'
|
||||
export * from './app/category'
|
||||
export * from './app/store'
|
||||
export * from './app/promocodes'
|
||||
|
||||
|
||||
|
||||
|
|
@ -27,3 +28,4 @@ export * from './api/categories'
|
|||
export * from './api/brands'
|
||||
export * from './api/contact'
|
||||
export * from './api/store'
|
||||
export * from './api/promocodes'
|
||||
Loading…
Reference in a new issue