Features: 1) Introduce useUserBaseData composable to fetch and manage user's wishlist, orders, and promocodes; 2) Add reusable useOrders and useOrderOverwrite composables with advanced filtering and pagination; 3) Implement order.vue component for detailed order displays with UI enhancements;
Fixes: 1) Replace deprecated context usage in `useAvatarUpload` mutation; 2) Resolve incorrect locale parsing in `useDate` utility and fix non-reactive cart state in `profile/cart.vue`; 3) Update stale imports and standardize type naming across composables; Extra: 1) Refactor i18n strings including order status and search-related texts; 2) Replace temporary workarounds with `apollo-upload-client` configuration and add `apollo-upload-link.ts` plugin; 3) Cleanup redundant files, comments, and improve SCSS structure with new variables and placeholders.
This commit is contained in:
parent
c60ac13e88
commit
52b32bd608
40 changed files with 984 additions and 148 deletions
11
storefront/assets/styles/modules/normalize.scss
vendored
11
storefront/assets/styles/modules/normalize.scss
vendored
|
|
@ -37,6 +37,17 @@ button:focus-visible {
|
||||||
margin-inline: auto;
|
margin-inline: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//::-webkit-scrollbar {
|
||||||
|
// width: 12px;
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//::-webkit-scrollbar-thumb {
|
||||||
|
// background-color: rgba($accent, 0.5);
|
||||||
|
// border-radius: $default_border_radius;
|
||||||
|
// -webkit-transition: all .2s ease;
|
||||||
|
// transition: all .2s ease;
|
||||||
|
//}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: $accent;
|
background: $accent;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
246
storefront/components/cards/order.vue
Normal file
246
storefront/components/cards/order.vue
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
<template>
|
||||||
|
<el-collapse-item
|
||||||
|
class="order"
|
||||||
|
>
|
||||||
|
<template #title="{ isActive }">
|
||||||
|
<div :class="['order__top', { 'is-active': isActive }]">
|
||||||
|
<div>
|
||||||
|
<p>{{ t('profile.orders.id') }}: {{ order.humanReadableId }}</p>
|
||||||
|
<p v-if="order.buyTime">{{ useDate(order.buyTime, locale) }}</p>
|
||||||
|
<el-tooltip
|
||||||
|
:content="order.status"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<p class="status" :style="[{ backgroundColor: statusColor(order.status) }]">
|
||||||
|
{{ order.status }}
|
||||||
|
<icon name="material-symbols:info-outline-rounded" size="14" />
|
||||||
|
</p>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<icon
|
||||||
|
name="material-symbols:keyboard-arrow-down"
|
||||||
|
size="22"
|
||||||
|
class="order__top-icon"
|
||||||
|
:class="[{ active: isActive }]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="order__top-bottom" :class="{ active: !isActive }">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<nuxt-img
|
||||||
|
v-for="product in order.orderProducts.edges"
|
||||||
|
:key="product.node.uuid"
|
||||||
|
:src="product.node.product.images.edges[0].node.image"
|
||||||
|
:alt="product.node.product.name"
|
||||||
|
format="webp"
|
||||||
|
densities="x1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p>{{ order.totalPrice }}{{ CURRENCY }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="order__main">
|
||||||
|
<div
|
||||||
|
class="order__product"
|
||||||
|
v-for="product in order.orderProducts.edges"
|
||||||
|
:key="product.node.uuid"
|
||||||
|
>
|
||||||
|
<div class="order__product-left">
|
||||||
|
<nuxt-img
|
||||||
|
:src="product.node.product.images.edges[0].node.image"
|
||||||
|
:alt="product.node.product.name"
|
||||||
|
/>
|
||||||
|
<p>{{ product.node.product.name }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="order__product-right">
|
||||||
|
<h6>{{ t('profile.orders.price') }}: {{ product.node.product.price * product.node.quantity }}{{ CURRENCY }}</h6>
|
||||||
|
<p>{{ product.node.quantity }} X {{ product.node.product.price }}{{ CURRENCY }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="order__total">
|
||||||
|
<p>{{ t('profile.orders.total') }}: {{ order.totalPrice }}{{ CURRENCY }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-collapse-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {useDate} from "~/composables/date";
|
||||||
|
import {CURRENCY, orderStatuses} from "~/config/constants";
|
||||||
|
import type {IOrder} from "~/types";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
order: IOrder;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const {t, locale} = useI18n();
|
||||||
|
|
||||||
|
const statusColor = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case orderStatuses.FAILED:
|
||||||
|
return '#FF0000';
|
||||||
|
|
||||||
|
case orderStatuses.PAYMENT:
|
||||||
|
return '#FFC107';
|
||||||
|
|
||||||
|
case orderStatuses.CREATED:
|
||||||
|
return '#007BFF';
|
||||||
|
|
||||||
|
case orderStatuses.DELIVERING:
|
||||||
|
return '#00C853';
|
||||||
|
|
||||||
|
case orderStatuses.FINISHED:
|
||||||
|
return '#00C853';
|
||||||
|
|
||||||
|
case orderStatuses.MOMENTAL:
|
||||||
|
return '#00C853';
|
||||||
|
|
||||||
|
default:
|
||||||
|
return '#000';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.order {
|
||||||
|
&__top {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20px;
|
||||||
|
padding: 0 10px 5px 10px;
|
||||||
|
|
||||||
|
& div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 25px;
|
||||||
|
|
||||||
|
& p {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
&.status {
|
||||||
|
border-radius: $default_border_radius;
|
||||||
|
padding: 3px 7px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
transition: 0.2s;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
transform: rotate(-180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-bottom {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 0fr;
|
||||||
|
transition: grid-template-rows 0.2s ease;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
& div {
|
||||||
|
& div {
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid $accent;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 25px;
|
||||||
|
|
||||||
|
& div {
|
||||||
|
padding-top: 0;
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
& img {
|
||||||
|
height: 65px;
|
||||||
|
border-radius: $default_border_radius;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& p {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__product {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
border-bottom: 2px solid $accent;
|
||||||
|
|
||||||
|
&-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
& img {
|
||||||
|
height: 150px;
|
||||||
|
border-radius: $default_border_radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
& p {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
|
||||||
|
& h6 {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& p {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__total {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
& p {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-collapse-item__header) {
|
||||||
|
height: fit-content;
|
||||||
|
padding-block: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -130,7 +130,7 @@ import 'swiper/css';
|
||||||
import 'swiper/css/effect-fade';
|
import 'swiper/css/effect-fade';
|
||||||
import 'swiper/css/pagination'
|
import 'swiper/css/pagination'
|
||||||
import {useWishlistOverwrite} from "~/composables/wishlist";
|
import {useWishlistOverwrite} from "~/composables/wishlist";
|
||||||
import {useOrderOverwrite} from "~/composables/orders/useOrderOverwrite";
|
import {useOrderOverwrite} from "~/composables/orders";
|
||||||
import {CURRENCY} from "~/config/constants";
|
import {CURRENCY} from "~/config/constants";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
|
||||||
88
storefront/components/skeletons/cards/order.vue
Normal file
88
storefront/components/skeletons/cards/order.vue
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
<template>
|
||||||
|
<el-skeleton
|
||||||
|
class="sk"
|
||||||
|
animated
|
||||||
|
>
|
||||||
|
<template #template>
|
||||||
|
<div class="sk__main">
|
||||||
|
<el-skeleton-item
|
||||||
|
variant="p"
|
||||||
|
class="sk__text"
|
||||||
|
/>
|
||||||
|
<el-skeleton-item
|
||||||
|
variant="p"
|
||||||
|
class="sk__text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="sk__bottom">
|
||||||
|
<div class="sk__bottom-images">
|
||||||
|
<el-skeleton-item
|
||||||
|
variant="image"
|
||||||
|
class="sk__image"
|
||||||
|
v-for="idx in 3"
|
||||||
|
:key="idx"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<el-skeleton-item
|
||||||
|
variant="p"
|
||||||
|
class="sk__price"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-skeleton>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.sk {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: $default_border_radius;
|
||||||
|
background-color: rgba(255, 255, 255, 0.61);
|
||||||
|
border: 1px solid $accent;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
padding: 10px 8px;
|
||||||
|
|
||||||
|
&__main {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 25px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__text {
|
||||||
|
width: 100px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: $default_border_radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__bottom {
|
||||||
|
border-top: 1px solid $accent;
|
||||||
|
padding-top: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&-images {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__image {
|
||||||
|
width: 52px;
|
||||||
|
height: 65px;
|
||||||
|
border-radius: $default_border_radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__price {
|
||||||
|
width: 60px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -83,7 +83,7 @@ const emit = defineEmits<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const attributesQuery = useRouteQuery<string>('attributes', '');
|
const attributesQuery = useRouteQuery<string>('attributes', '');
|
||||||
|
//TODO: price in filters
|
||||||
const {
|
const {
|
||||||
selectedMap,
|
selectedMap,
|
||||||
selectedAllMap,
|
selectedAllMap,
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,11 @@ import type { ILoginResponse } from '~/types/api/auth';
|
||||||
import { isGraphQLError } from '~/utils/error';
|
import { isGraphQLError } from '~/utils/error';
|
||||||
import { useAppConfig } from '~/composables/config';
|
import { useAppConfig } from '~/composables/config';
|
||||||
import { useLocaleRedirect } from '~/composables/languages';
|
import { useLocaleRedirect } from '~/composables/languages';
|
||||||
import { useWishlist } from '~/composables/wishlist';
|
|
||||||
import { usePendingOrder } from '~/composables/orders';
|
|
||||||
import { useUserStore } from '~/stores/user';
|
import { useUserStore } from '~/stores/user';
|
||||||
import { useAppStore } from '~/stores/app';
|
import { useAppStore } from '~/stores/app';
|
||||||
import {DEFAULT_LOCALE} from "~/config/constants";
|
import {DEFAULT_LOCALE} from "~/config/constants";
|
||||||
import {useNotification} from "~/composables/notification";
|
import {useNotification} from "~/composables/notification";
|
||||||
import {usePromocodes} from "~/composables/promocodes";
|
import {useUserBaseData} from "~/composables/user";
|
||||||
|
|
||||||
export function useLogin() {
|
export function useLogin() {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
@ -70,10 +68,14 @@ export function useLogin() {
|
||||||
await checkAndRedirect(authData.user.language);
|
await checkAndRedirect(authData.user.language);
|
||||||
}
|
}
|
||||||
|
|
||||||
await useWishlist();
|
// await useWishlist();
|
||||||
await usePendingOrder(authData.user.email);
|
// await useOrders({
|
||||||
await usePromocodes();
|
// userEmail: authData.user.email,
|
||||||
//TODO: combine three requests
|
// status: "PENDING"
|
||||||
|
// });
|
||||||
|
// await usePromocodes();
|
||||||
|
|
||||||
|
await useUserBaseData(authData.user.email);
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(error, (err) => {
|
watch(error, (err) => {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
import { REFRESH } from '@/graphql/mutations/auth';
|
import { REFRESH } from '@/graphql/mutations/auth';
|
||||||
import { useAppConfig } from '~/composables/config';
|
import { useAppConfig } from '~/composables/config';
|
||||||
import { useLocaleRedirect } from '~/composables/languages';
|
import { useLocaleRedirect } from '~/composables/languages';
|
||||||
import { useWishlist } from '~/composables/wishlist';
|
|
||||||
import { usePendingOrder } from '~/composables/orders';
|
|
||||||
import { useUserStore } from '~/stores/user';
|
import { useUserStore } from '~/stores/user';
|
||||||
import { isGraphQLError } from '~/utils/error';
|
import { isGraphQLError } from '~/utils/error';
|
||||||
import {DEFAULT_LOCALE} from "~/config/constants";
|
import {DEFAULT_LOCALE} from "~/config/constants";
|
||||||
import {useNotification} from "~/composables/notification";
|
import {useNotification} from "~/composables/notification";
|
||||||
import {usePromocodes} from "~/composables/promocodes";
|
import {useUserBaseData} from "~/composables/user";
|
||||||
|
|
||||||
export function useRefresh() {
|
export function useRefresh() {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
@ -51,10 +49,13 @@ export function useRefresh() {
|
||||||
|
|
||||||
cookieRefresh.value = data.refreshToken
|
cookieRefresh.value = data.refreshToken
|
||||||
|
|
||||||
await useWishlist();
|
// await useWishlist();
|
||||||
await usePendingOrder(data.user.email);
|
// await useOrders({
|
||||||
await usePromocodes();
|
// userEmail: data.user.email,
|
||||||
//TODO: combine three requests
|
// status: "PENDING"
|
||||||
|
// });
|
||||||
|
// await usePromocodes();
|
||||||
|
await useUserBaseData(data.user.email);
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(error, (err) => {
|
watch(error, (err) => {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
export function useDate(
|
export function useDate(
|
||||||
iso: string | undefined,
|
iso: string | undefined,
|
||||||
locale: string = 'en-gb'
|
locale: string = 'en-gb',
|
||||||
): string {
|
options: Intl.DateTimeFormatOptions = {
|
||||||
if (!iso) return '';
|
|
||||||
const date = new Date(iso);
|
|
||||||
const parsedLocale = locale.replace('-', '-').toLocaleUpperCase()
|
|
||||||
|
|
||||||
return new Intl.DateTimeFormat(parsedLocale, {
|
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
day: '2-digit'
|
day: '2-digit'
|
||||||
}).format(date);
|
}
|
||||||
|
): string {
|
||||||
|
if (!iso) return ''
|
||||||
|
const date = new Date(iso)
|
||||||
|
const parsedLocale = locale.replace('_', '-').toLowerCase()
|
||||||
|
|
||||||
|
return new Intl.DateTimeFormat(parsedLocale, options).format(date)
|
||||||
}
|
}
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
export * from './usePendingOrder';
|
export * from './useOrderOverwrite';
|
||||||
|
export * from './useOrders';
|
||||||
79
storefront/composables/orders/useOrders.ts
Normal file
79
storefront/composables/orders/useOrders.ts
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
import {GET_ORDERS} from "~/graphql/queries/standalone/orders";
|
||||||
|
import type {IOrdersResponse} from "~/types";
|
||||||
|
import {orderStatuses} from "~/config/constants";
|
||||||
|
|
||||||
|
interface IOrdersArguments {
|
||||||
|
userEmail: string,
|
||||||
|
status?: string,
|
||||||
|
after?: string,
|
||||||
|
search: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IOrderVars {
|
||||||
|
status: string,
|
||||||
|
userEmail: string,
|
||||||
|
first: number,
|
||||||
|
after?: string,
|
||||||
|
search: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function useOrders(args: IOrdersArguments) {
|
||||||
|
const cartStore = useCartStore();
|
||||||
|
|
||||||
|
const variables = reactive<IOrderVars>({
|
||||||
|
status: args.status || '',
|
||||||
|
userEmail: args.userEmail,
|
||||||
|
first: 10,
|
||||||
|
after: args.after,
|
||||||
|
search: args.search
|
||||||
|
});
|
||||||
|
|
||||||
|
const { pending, data, error, refresh } = await useAsyncQuery<IOrdersResponse>(
|
||||||
|
GET_ORDERS,
|
||||||
|
variables
|
||||||
|
);
|
||||||
|
|
||||||
|
const orders = ref(data.value?.orders.edges.filter((order) => order.node.status !== orderStatuses.PENDING) ?? []);
|
||||||
|
const pageInfo = computed(() => data.value?.orders.pageInfo ?? null);
|
||||||
|
|
||||||
|
if (!error.value && data.value?.orders.edges) {
|
||||||
|
if (args.status === orderStatuses.PENDING) {
|
||||||
|
cartStore.setCurrentOrders(data.value?.orders.edges[0].node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => variables.after,
|
||||||
|
async (newCursor, oldCursor) => {
|
||||||
|
if (!newCursor || newCursor === oldCursor) return;
|
||||||
|
await refresh();
|
||||||
|
const newEdges = data.value?.orders.edges ?? [];
|
||||||
|
orders.value.push(...newEdges);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[
|
||||||
|
() => variables.status,
|
||||||
|
() => variables.search
|
||||||
|
],
|
||||||
|
async () => {
|
||||||
|
variables.after = '';
|
||||||
|
await refresh();
|
||||||
|
orders.value = data.value?.orders.edges ?? [];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(error, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('useOrders error:', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
pending,
|
||||||
|
orders,
|
||||||
|
pageInfo,
|
||||||
|
variables
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
import {GET_ORDERS} from "~/graphql/queries/standalone/orders";
|
|
||||||
import type {IOrderResponse} from "~/types";
|
|
||||||
|
|
||||||
export async function usePendingOrder(userEmail: string) {
|
|
||||||
const cartStore = useCartStore();
|
|
||||||
|
|
||||||
const { data, error } = await useAsyncQuery<IOrderResponse>(
|
|
||||||
GET_ORDERS,
|
|
||||||
{
|
|
||||||
status: "PENDING",
|
|
||||||
userEmail
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!error.value && data.value?.orders.edges[0].node) {
|
|
||||||
cartStore.setCurrentOrders(data.value?.orders.edges[0].node);
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(error, (err) => {
|
|
||||||
if (err) {
|
|
||||||
console.error('usePendingOrder error:', err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -7,6 +7,7 @@ export async function usePromocodes () {
|
||||||
const { data, error } = await useAsyncQuery<IPromocodesResponse>(
|
const { data, error } = await useAsyncQuery<IPromocodesResponse>(
|
||||||
GET_PROMOCODES
|
GET_PROMOCODES
|
||||||
);
|
);
|
||||||
|
console.log(data.value)
|
||||||
|
|
||||||
if (!error.value && data.value?.promocodes.edges) {
|
if (!error.value && data.value?.promocodes.edges) {
|
||||||
promocodesStore.setPromocodes(data.value.promocodes.edges);
|
promocodesStore.setPromocodes(data.value.promocodes.edges);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { GET_PRODUCTS } from '~/graphql/queries/standalone/products';
|
import { GET_PRODUCTS } from '~/graphql/queries/standalone/products';
|
||||||
import type {IProductResponse} from '~/types';
|
import type {IProductResponse} from '~/types';
|
||||||
|
|
||||||
interface ProdVars {
|
interface IProdVars {
|
||||||
first: number,
|
first: number,
|
||||||
categoriesSlugs: string,
|
categoriesSlugs: string,
|
||||||
attributes?: string,
|
attributes?: string,
|
||||||
|
|
@ -19,7 +19,7 @@ export async function useStore(
|
||||||
maxPrice?: number,
|
maxPrice?: number,
|
||||||
productAfter?: string
|
productAfter?: string
|
||||||
) {
|
) {
|
||||||
const variables = reactive<ProdVars>({
|
const variables = reactive<IProdVars>({
|
||||||
first: 15,
|
first: 15,
|
||||||
categoriesSlugs: slug,
|
categoriesSlugs: slug,
|
||||||
attributes,
|
attributes,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
export * from './useUserActivation';
|
export * from './useUserActivation';
|
||||||
export * from './useAvatarUpload';
|
export * from './useAvatarUpload';
|
||||||
export * from './useUserUpdating';
|
export * from './useUserUpdating';
|
||||||
export * from './useDeposit';
|
export * from './useDeposit';
|
||||||
|
export * from './useUserBaseData';
|
||||||
|
|
@ -6,17 +6,19 @@ import {useNotification} from "~/composables/notification";
|
||||||
export function useAvatarUpload() {
|
export function useAvatarUpload() {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const { mutate, onDone, loading, error } = useMutation<IAvatarUploadResponse>(UPLOAD_AVATAR, {
|
const { mutate, onDone, loading, error } = useMutation<IAvatarUploadResponse>(
|
||||||
context: {
|
UPLOAD_AVATAR,
|
||||||
hasUpload: true
|
// {
|
||||||
}}
|
// context: { hasUpload: true }
|
||||||
|
// }
|
||||||
);
|
);
|
||||||
|
|
||||||
async function uploadAvatar(event: Event) {
|
async function uploadAvatar(event: Event) {
|
||||||
const file = (event.target as HTMLInputElement).files?.[0];
|
const file = (event.target as HTMLInputElement).files?.[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
console.log(file)
|
||||||
|
|
||||||
await mutate({ avatar: file });
|
await mutate({ file });
|
||||||
}
|
}
|
||||||
|
|
||||||
onDone(({ data }) => {
|
onDone(({ data }) => {
|
||||||
|
|
|
||||||
41
storefront/composables/user/useUserBaseData.ts
Normal file
41
storefront/composables/user/useUserBaseData.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import type {IUserBaseDataResponse} from "~/types";
|
||||||
|
import {getUserBaseData} from "~/graphql/queries/combined/userBaseData";
|
||||||
|
import {orderStatuses} from "~/config/constants";
|
||||||
|
|
||||||
|
export async function useUserBaseData(userEmail: string) {
|
||||||
|
const wishlistStore = useWishlistStore();
|
||||||
|
const cartStore = useCartStore();
|
||||||
|
const promocodesStore = usePromocodeStore();
|
||||||
|
|
||||||
|
const { document, variables } = getUserBaseData(
|
||||||
|
{
|
||||||
|
userEmail,
|
||||||
|
status: orderStatuses.PENDING
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data, error } = await useAsyncQuery<IUserBaseDataResponse>(
|
||||||
|
document,
|
||||||
|
variables
|
||||||
|
);
|
||||||
|
|
||||||
|
if (data.value?.wishlists.edges) {
|
||||||
|
wishlistStore.setWishlist(data.value.wishlists.edges[0].node);
|
||||||
|
}
|
||||||
|
if (data.value?.orders.edges) {
|
||||||
|
cartStore.setCurrentOrders(data.value?.orders.edges[0].node);
|
||||||
|
}
|
||||||
|
if (data.value?.promocodes.edges) {
|
||||||
|
promocodesStore.setPromocodes(data.value.promocodes.edges);
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(error, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('useUserBaseData error:', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -87,4 +87,14 @@ export const SUPPORTED_LOCALES: LocaleDefinition[] = [
|
||||||
|
|
||||||
export const DEFAULT_LOCALE = SUPPORTED_LOCALES.find(locale => locale.default)?.code || 'en-gb';
|
export const DEFAULT_LOCALE = SUPPORTED_LOCALES.find(locale => locale.default)?.code || 'en-gb';
|
||||||
|
|
||||||
export const CURRENCY = '$'
|
export const CURRENCY = '$';
|
||||||
|
|
||||||
|
export enum orderStatuses {
|
||||||
|
PENDING = 'PENDING',
|
||||||
|
FAILED = 'FAILED',
|
||||||
|
PAYMENT = 'PAYMENT',
|
||||||
|
CREATED = 'CREATED',
|
||||||
|
DELIVERING = 'DELIVERING',
|
||||||
|
FINISHED = 'FINISHED',
|
||||||
|
MOMENTAL = 'MOMENTAL'
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ export const ORDER_FRAGMENT = gql`
|
||||||
status
|
status
|
||||||
buyTime
|
buyTime
|
||||||
humanReadableId
|
humanReadableId
|
||||||
|
notifications
|
||||||
orderProducts {
|
orderProducts {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,7 @@ export const PROMOCODE_FRAGMENT = gql`
|
||||||
discount
|
discount
|
||||||
discountType
|
discountType
|
||||||
endTime
|
endTime
|
||||||
id
|
|
||||||
startTime
|
startTime
|
||||||
usedOn
|
|
||||||
uuid
|
uuid
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
@ -43,10 +43,10 @@ export const UPDATE_USER = gql`
|
||||||
|
|
||||||
export const UPLOAD_AVATAR = gql`
|
export const UPLOAD_AVATAR = gql`
|
||||||
mutation uploadAvatar(
|
mutation uploadAvatar(
|
||||||
$avatar: Upload!
|
$file: Upload!
|
||||||
) {
|
) {
|
||||||
uploadAvatar(
|
uploadAvatar(
|
||||||
avatar: $avatar
|
file: $file
|
||||||
) {
|
) {
|
||||||
user {
|
user {
|
||||||
...User
|
...User
|
||||||
|
|
|
||||||
18
storefront/graphql/queries/combined/userBaseData.ts
Normal file
18
storefront/graphql/queries/combined/userBaseData.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import combineQuery from 'graphql-combine-query'
|
||||||
|
import {GET_WISHLIST} from "~/graphql/queries/standalone/wishlist";
|
||||||
|
import {GET_PROMOCODES} from "~/graphql/queries/standalone/promocodes";
|
||||||
|
import {GET_ORDERS} from "~/graphql/queries/standalone/orders";
|
||||||
|
|
||||||
|
export const getUserBaseData = (
|
||||||
|
orderVariables?: {
|
||||||
|
userEmail?: string;
|
||||||
|
status?: string;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
const { document, variables } = combineQuery('getUserBaseData')
|
||||||
|
.add(GET_WISHLIST)
|
||||||
|
.add(GET_PROMOCODES)
|
||||||
|
.add(GET_ORDERS, orderVariables || {})
|
||||||
|
|
||||||
|
return { document, variables };
|
||||||
|
};
|
||||||
|
|
@ -3,18 +3,28 @@ import {ORDER_FRAGMENT} from "@/graphql/fragments/orders.fragment.js";
|
||||||
export const GET_ORDERS = gql`
|
export const GET_ORDERS = gql`
|
||||||
query getOrders(
|
query getOrders(
|
||||||
$status: String!,
|
$status: String!,
|
||||||
$userEmail: String!
|
$userEmail: String!,
|
||||||
|
$first: Int,
|
||||||
|
$after: String,
|
||||||
|
$search: String,
|
||||||
) {
|
) {
|
||||||
orders(
|
orders(
|
||||||
status: $status,
|
status: $status,
|
||||||
orderBy: "-buyTime",
|
orderBy: "-buyTime",
|
||||||
userEmail: $userEmail
|
userEmail: $userEmail,
|
||||||
|
first: $first,
|
||||||
|
after: $after,
|
||||||
|
search: $search
|
||||||
) {
|
) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
...Order
|
...Order
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pageInfo {
|
||||||
|
hasNextPage
|
||||||
|
endCursor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
${ORDER_FRAGMENT}
|
${ORDER_FRAGMENT}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"search": "Search",
|
"search": "Search",
|
||||||
|
"searchOrder": "Search order",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"firstName": "First name",
|
"firstName": "First name",
|
||||||
"lastName": "Last name",
|
"lastName": "Last name",
|
||||||
|
|
@ -126,6 +127,7 @@
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"catalog": "Catalog",
|
"catalog": "Catalog",
|
||||||
"contact": "Contact",
|
"contact": "Contact",
|
||||||
|
"orders": "Orders",
|
||||||
"wishlist": "Wishlist",
|
"wishlist": "Wishlist",
|
||||||
"cart": "Cart",
|
"cart": "Cart",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
|
|
@ -163,23 +165,42 @@
|
||||||
"referralTooltip": "You will get a referral link after a successful purchase"
|
"referralTooltip": "You will get a referral link after a successful purchase"
|
||||||
},
|
},
|
||||||
"orders": {
|
"orders": {
|
||||||
"title": "Orders"
|
"title": "Orders",
|
||||||
|
"chooseStatus": "Choose status",
|
||||||
|
"id": "ID",
|
||||||
|
"price": "Price",
|
||||||
|
"total": "Total",
|
||||||
|
"empty": "There are not any orders by this parameters.",
|
||||||
|
"statuses": {
|
||||||
|
"all": "All",
|
||||||
|
"failed": "Failed",
|
||||||
|
"payment": "Payment",
|
||||||
|
"created": "Created",
|
||||||
|
"delivering": "Delivering",
|
||||||
|
"finished": "Finished",
|
||||||
|
"momental": "Momental"
|
||||||
|
},
|
||||||
|
"searchTooltip": "Enter order id or product name"
|
||||||
},
|
},
|
||||||
"wishlist": {
|
"wishlist": {
|
||||||
"title": "Wishlist",
|
"title": "Wishlist",
|
||||||
"total": "{quantity} items worth {amount}",
|
"total": "{quantity} items worth {amount}",
|
||||||
"deleteTooltip": "Delete all from wishlist"
|
"deleteTooltip": "Delete all from wishlist",
|
||||||
|
"empty": "Your wishlist is empty."
|
||||||
},
|
},
|
||||||
"cart": {
|
"cart": {
|
||||||
"title": "Cart",
|
"title": "Cart",
|
||||||
"quantity": "Quantity: ",
|
"quantity": "Quantity: ",
|
||||||
"total": "Total: "
|
"total": "Total",
|
||||||
|
"empty": "Your cart is empty."
|
||||||
},
|
},
|
||||||
"balance": {
|
"balance": {
|
||||||
"title": "Balance"
|
"title": "Balance"
|
||||||
},
|
},
|
||||||
"promocodes": {
|
"promocodes": {
|
||||||
"title": "Promocodes"
|
"title": "Promocodes",
|
||||||
|
"until": "Until",
|
||||||
|
"empty": "You don't have any promocodes."
|
||||||
},
|
},
|
||||||
"logout": "Logout"
|
"logout": "Logout"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
storefront/middleware/auth.ts
Normal file
11
storefront/middleware/auth.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
export default defineNuxtRouteMiddleware(() => {
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const localePath = useLocalePath();
|
||||||
|
|
||||||
|
if (!userStore.isAuthenticated) {
|
||||||
|
appStore.setActiveState('login');
|
||||||
|
|
||||||
|
return navigateTo(localePath('/'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
@ -2,6 +2,7 @@ import { defineNuxtConfig } from 'nuxt/config';
|
||||||
import { i18nConfig } from './config/i18n';
|
import { i18nConfig } from './config/i18n';
|
||||||
import {fileURLToPath, URL} from "node:url";
|
import {fileURLToPath, URL} from "node:url";
|
||||||
import { resolve } from 'node:path';
|
import { resolve } from 'node:path';
|
||||||
|
import createUploadLink from "apollo-upload-client/createUploadLink.mjs";
|
||||||
|
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
ssr: true,
|
ssr: true,
|
||||||
|
|
@ -92,6 +93,20 @@ export default defineNuxtConfig({
|
||||||
file: resolve(__dirname, 'pages/index.vue')
|
file: resolve(__dirname, 'pages/index.vue')
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
// 'apollo:client:created'(apolloClient, { key }) {
|
||||||
|
// console.log(key)
|
||||||
|
// console.log('log')
|
||||||
|
// if ( key !== 'default' ) return
|
||||||
|
// const runtime = useRuntimeConfig()
|
||||||
|
// const uploadLink = createUploadLink({
|
||||||
|
// uri: `https://api.${runtime.public.evibesBaseDomain}/graphql/`,
|
||||||
|
// credentials: 'include',
|
||||||
|
// headers: {
|
||||||
|
// 'X-EVIBES-AUTH': useCookie(`${runtime.public.evibesProjectName?.toLowerCase()}-access`).value || ''
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// apolloClient.setLink(uploadLink)
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
37
storefront/package-lock.json
generated
37
storefront/package-lock.json
generated
|
|
@ -15,7 +15,7 @@
|
||||||
"@vueuse/integrations": "^13.3.0",
|
"@vueuse/integrations": "^13.3.0",
|
||||||
"@vueuse/nuxt": "^13.3.0",
|
"@vueuse/nuxt": "^13.3.0",
|
||||||
"@vueuse/router": "^13.3.0",
|
"@vueuse/router": "^13.3.0",
|
||||||
"apollo-upload-client": "17.0.0",
|
"apollo-upload-client": "^18.0.1",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"graphql-combine-query": "^1.2.4",
|
"graphql-combine-query": "^1.2.4",
|
||||||
"graphql-tag": "^2.12.6",
|
"graphql-tag": "^2.12.6",
|
||||||
|
|
@ -3674,21 +3674,21 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/apollo-upload-client": {
|
"node_modules/apollo-upload-client": {
|
||||||
"version": "17.0.0",
|
"version": "18.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/apollo-upload-client/-/apollo-upload-client-17.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/apollo-upload-client/-/apollo-upload-client-18.0.1.tgz",
|
||||||
"integrity": "sha512-pue33bWVbdlXAGFPkgz53TTmxVMrKeQr0mdRcftNY+PoHIdbGZD0hoaXHvO6OePJAkFz7OiCFUf98p1G/9+Ykw==",
|
"integrity": "sha512-OQvZg1rK05VNI79D658FUmMdoI2oB/KJKb6QGMa2Si25QXOaAvLMBFUEwJct7wf+19U8vk9ILhidBOU1ZWv6QA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"extract-files": "^11.0.0"
|
"extract-files": "^13.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.22.0 || ^14.17.0 || >= 16.0.0"
|
"node": "^18.15.0 || >=20.4.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/jaydenseric"
|
"url": "https://github.com/sponsors/jaydenseric"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@apollo/client": "^3.0.0",
|
"@apollo/client": "^3.8.0",
|
||||||
"graphql": "14 - 16"
|
"graphql": "14 - 16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -6223,17 +6223,32 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/extract-files": {
|
"node_modules/extract-files": {
|
||||||
"version": "11.0.0",
|
"version": "13.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/extract-files/-/extract-files-11.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/extract-files/-/extract-files-13.0.0.tgz",
|
||||||
"integrity": "sha512-FuoE1qtbJ4bBVvv94CC7s0oTnKUGvQs+Rjf1L2SJFfS+HTVVjhPFtehPdQ0JiGPqVNfSSZvL5yzHHQq2Z4WNhQ==",
|
"integrity": "sha512-FXD+2Tsr8Iqtm3QZy1Zmwscca7Jx3mMC5Crr+sEP1I303Jy1CYMuYCm7hRTplFNg3XdUavErkxnTzpaqdSoi6g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-plain-obj": "^4.1.0"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.20 || >= 14.13"
|
"node": "^14.17.0 || ^16.0.0 || >= 18.0.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/jaydenseric"
|
"url": "https://github.com/sponsors/jaydenseric"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/extract-files/node_modules/is-plain-obj": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/extract-zip": {
|
"node_modules/extract-zip": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
"@vueuse/integrations": "^13.3.0",
|
"@vueuse/integrations": "^13.3.0",
|
||||||
"@vueuse/nuxt": "^13.3.0",
|
"@vueuse/nuxt": "^13.3.0",
|
||||||
"@vueuse/router": "^13.3.0",
|
"@vueuse/router": "^13.3.0",
|
||||||
"apollo-upload-client": "17.0.0",
|
"apollo-upload-client": "^18.0.1",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"graphql-combine-query": "^1.2.4",
|
"graphql-combine-query": "^1.2.4",
|
||||||
"graphql-tag": "^2.12.6",
|
"graphql-tag": "^2.12.6",
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ useHead({
|
||||||
|
|
||||||
const token = useRouteQuery('token', '');
|
const token = useRouteQuery('token', '');
|
||||||
const uid = useRouteQuery('uid', '');
|
const uid = useRouteQuery('uid', '');
|
||||||
|
const referrer = useRouteQuery('referrer', '');
|
||||||
|
|
||||||
const { activateUser } = useUserActivation();
|
const { activateUser } = useUserActivation();
|
||||||
|
|
||||||
|
|
@ -32,6 +33,10 @@ onMounted( async () => {
|
||||||
if (route.path.includes('reset-password') && token.value && uid.value) {
|
if (route.path.includes('reset-password') && token.value && uid.value) {
|
||||||
appStore.setActiveState('new-password');
|
appStore.setActiveState('new-password');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (referrer.value) {
|
||||||
|
appStore.setActiveState('register');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@ import 'swiper/css/navigation';
|
||||||
import {Navigation} from "swiper/modules";
|
import {Navigation} from "swiper/modules";
|
||||||
import {CURRENCY} from "~/config/constants";
|
import {CURRENCY} from "~/config/constants";
|
||||||
import {useWishlistOverwrite} from "~/composables/wishlist";
|
import {useWishlistOverwrite} from "~/composables/wishlist";
|
||||||
import {useOrderOverwrite} from "~/composables/orders/useOrderOverwrite";
|
import {useOrderOverwrite} from "~/composables/orders";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const {t} = useI18n();
|
const {t} = useI18n();
|
||||||
|
|
|
||||||
|
|
@ -12,35 +12,24 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
definePageMeta({
|
||||||
|
middleware: 'auth'
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.profile {
|
.profile {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-top: 50px;
|
padding-top: 50px;
|
||||||
height: calc(100vh - 125px);
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
& .container {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__wrapper {
|
&__wrapper {
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
gap: 100px;
|
gap: 100px;
|
||||||
min-height: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__inner {
|
&__inner {
|
||||||
flex: 1;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 0;
|
|
||||||
display: flex;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -3,37 +3,41 @@
|
||||||
<div class="cart__top">
|
<div class="cart__top">
|
||||||
<div class="cart__top-left">
|
<div class="cart__top-left">
|
||||||
<p><span>{{ t('profile.cart.quantity') }}</span> {{ productsInCartQuantity }}</p>
|
<p><span>{{ t('profile.cart.quantity') }}</span> {{ productsInCartQuantity }}</p>
|
||||||
<p><span>{{ t('profile.cart.total') }}</span> {{ totalPrice }}</p>
|
<p><span>{{ t('profile.cart.total') }}: </span> {{ totalPrice }}{{ CURRENCY }}</p>
|
||||||
</div>
|
</div>
|
||||||
<ui-button class="cart__top-button">{{ t('buttons.checkout') }}</ui-button>
|
<ui-button class="cart__top-button">{{ t('buttons.checkout') }}</ui-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="cart__list">
|
<div class="cart__list">
|
||||||
<cards-product
|
<div class="cart__list-inner" v-if="productsInCart.length">
|
||||||
v-for="product in productsInCart"
|
<cards-product
|
||||||
:key="product.node.uuid"
|
v-for="product in productsInCart"
|
||||||
:product="product.node.product"
|
:key="product.node.uuid"
|
||||||
:isList="true"
|
:product="product.node.product"
|
||||||
:isToolsVisible="true"
|
:isList="true"
|
||||||
/>
|
:isToolsVisible="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p class="cart__empty">{{ t('profile.cart.empty') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {usePageTitle} from "~/composables/utils";
|
import {usePageTitle} from "~/composables/utils";
|
||||||
|
import {CURRENCY} from "~/config/constants";
|
||||||
|
|
||||||
const {t} = useI18n();
|
const {t} = useI18n();
|
||||||
const cartStore = useCartStore();
|
const cartStore = useCartStore();
|
||||||
|
|
||||||
const productsInCart = computed(() => {
|
const productsInCart = computed(() => {
|
||||||
return cartStore.currentOrder.orderProducts ? cartStore.currentOrder.orderProducts.edges : [];
|
return cartStore.currentOrder ? cartStore.currentOrder.orderProducts.edges : [];
|
||||||
});
|
});
|
||||||
const totalPrice = computed(() => {
|
const totalPrice = computed(() => {
|
||||||
return cartStore.currentOrder.totalPrice ? cartStore.currentOrder.totalPrice : [];
|
return cartStore.currentOrder ? cartStore.currentOrder.totalPrice : 0;
|
||||||
});
|
});
|
||||||
const productsInCartQuantity = computed(() => {
|
const productsInCartQuantity = computed(() => {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
cartStore.currentOrder.orderProducts?.edges.forEach((el) => {
|
cartStore.currentOrder?.orderProducts?.edges.forEach((el) => {
|
||||||
count = count + el.node.quantity;
|
count = count + el.node.quantity;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -51,8 +55,6 @@ setPageTitle(t('breadcrumbs.cart'));
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 50px;
|
gap: 50px;
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
|
|
||||||
&__top {
|
&__top {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
@ -78,13 +80,18 @@ setPageTitle(t('breadcrumbs.cart'));
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
background-color: $white;
|
background-color: $white;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.2);
|
||||||
border-radius: $default_border_radius;
|
border-radius: $default_border_radius;
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
&-inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__empty {
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -1,15 +1,228 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="settings">
|
<div class="orders">
|
||||||
<p>orders</p>
|
<div class="orders__top">
|
||||||
|
<h2>{{ t('profile.orders.title') }}</h2>
|
||||||
|
<el-tooltip
|
||||||
|
:visible="isSearchFocused"
|
||||||
|
:content="t('profile.orders.searchTooltip')"
|
||||||
|
placement="top-start"
|
||||||
|
>
|
||||||
|
<form class="orders__search" @submit.prevent="submitSearch">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
inputmode="search"
|
||||||
|
v-model="searchInput"
|
||||||
|
:placeholder="t('fields.searchOrder')"
|
||||||
|
@focus="isSearchFocused = true"
|
||||||
|
@blur="isSearchFocused = false"
|
||||||
|
>
|
||||||
|
<button type="submit">
|
||||||
|
<icon name="tabler:search" size="16" />
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-select
|
||||||
|
v-model="status"
|
||||||
|
size="large"
|
||||||
|
style="width: 240px"
|
||||||
|
:placeholder="t('profile.orders.chooseStatus')"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in options"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="orders__inner" v-if="orders.length">
|
||||||
|
<el-collapse
|
||||||
|
v-model="collapse"
|
||||||
|
class="orders__list"
|
||||||
|
>
|
||||||
|
<cards-order
|
||||||
|
v-for="order in orders"
|
||||||
|
:key="order.node.uuid"
|
||||||
|
:order="order.node"
|
||||||
|
/>
|
||||||
|
</el-collapse>
|
||||||
|
<div class="orders__list" v-if="pending">
|
||||||
|
<skeletons-cards-order
|
||||||
|
v-for="idx in 5"
|
||||||
|
:key="idx"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="orders__list-observer" ref="observer"></div>
|
||||||
|
</div>
|
||||||
|
<div class="orders__empty" v-if="!orders.length && !pending">
|
||||||
|
<p>{{ t('profile.orders.empty') }}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import {usePageTitle} from "~/composables/utils";
|
||||||
|
import {useOrders} from "~/composables/orders";
|
||||||
|
import {CURRENCY, orderStatuses} from "~/config/constants";
|
||||||
|
import {useDate} from "~/composables/date";
|
||||||
|
import {useRouteQuery} from "@vueuse/router";
|
||||||
|
|
||||||
|
const {t} = useI18n();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
const userEmail = computed(() => {
|
||||||
|
return userStore.user ? userStore.user.email : '';
|
||||||
|
});
|
||||||
|
|
||||||
|
const status = useRouteQuery<string>('status', '');
|
||||||
|
const search = useRouteQuery<string>('search', '');
|
||||||
|
const searchInput = ref<string>('');
|
||||||
|
|
||||||
|
const { pending, orders, pageInfo, variables } = await useOrders({
|
||||||
|
userEmail: userEmail.value,
|
||||||
|
status: status.value,
|
||||||
|
search: search.value,
|
||||||
|
after: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
value: '',
|
||||||
|
label: t('profile.orders.statuses.all')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: orderStatuses.FAILED,
|
||||||
|
label: t('profile.orders.statuses.failed')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: orderStatuses.PAYMENT,
|
||||||
|
label: t('profile.orders.statuses.payment')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: orderStatuses.CREATED,
|
||||||
|
label: t('profile.orders.statuses.created')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: orderStatuses.DELIVERING,
|
||||||
|
label: t('profile.orders.statuses.delivering')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: orderStatuses.FINISHED,
|
||||||
|
label: t('profile.orders.statuses.finished')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: orderStatuses.MOMENTAL,
|
||||||
|
label: t('profile.orders.statuses.momental')
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const collapse = ref([]);
|
||||||
|
const isSearchFocused = ref<boolean>(false);
|
||||||
|
|
||||||
|
const observer = ref(null);
|
||||||
|
useIntersectionObserver(
|
||||||
|
observer,
|
||||||
|
async ([{ isIntersecting }]) => {
|
||||||
|
if (isIntersecting && pageInfo.value?.hasNextPage && !pending.value) {
|
||||||
|
variables.after = pageInfo.value.endCursor;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const submitSearch = () => {
|
||||||
|
search.value = searchInput.value;
|
||||||
|
variables.search = searchInput.value || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(status, newVal => {
|
||||||
|
variables.status = newVal || '';
|
||||||
|
});
|
||||||
|
|
||||||
|
const { setPageTitle } = usePageTitle();
|
||||||
|
|
||||||
|
setPageTitle(t('breadcrumbs.orders'));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.settings {
|
.orders {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 50px;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
&__top {
|
||||||
|
width: 100%;
|
||||||
|
background-color: $white;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 50px;
|
||||||
|
box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: $default_border_radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__search {
|
||||||
|
border-radius: $default_border_radius;
|
||||||
|
border: 1px solid #dedede;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: $white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
& input {
|
||||||
|
background-color: transparent;
|
||||||
|
height: 100%;
|
||||||
|
padding-inline: 15px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
& button {
|
||||||
|
height: 100%;
|
||||||
|
padding-inline: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: $default_border_radius;
|
||||||
|
background-color: rgba($accent, 0.2);
|
||||||
|
border: 1px solid $accent;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
transition: 0.2s;
|
||||||
|
|
||||||
|
color: $accent;
|
||||||
|
|
||||||
|
@include hover {
|
||||||
|
background-color: $accent;
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 30px;
|
||||||
|
|
||||||
|
&-observer {
|
||||||
|
background-color: transparent;
|
||||||
|
width: 100%;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__empty {
|
||||||
|
background-color: $white;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: $default_border_radius;
|
||||||
|
|
||||||
|
& p {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -1,29 +1,38 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="promocodes">
|
<div class="promocodes">
|
||||||
<h2>{{ t('profile.promocodes.title') }}</h2>
|
<h2>{{ t('profile.promocodes.title') }}</h2>
|
||||||
<div class="promocodes__list">
|
<div class="promocodes__list" v-if="promocodes?.length">
|
||||||
<div
|
<div
|
||||||
class="promocodes__item"
|
class="promocodes__item"
|
||||||
v-for="promocode in promocodes"
|
v-for="promocode in promocodes"
|
||||||
:key="promocode.node.uuid"
|
:key="promocode.node.uuid"
|
||||||
>
|
>
|
||||||
<icon
|
<div class="promocodes__item-left">
|
||||||
name="material-symbols:content-copy"
|
<icon
|
||||||
size="20"
|
name="material-symbols:content-copy"
|
||||||
class="promocodes__item-button"
|
size="20"
|
||||||
@click="copyCode(promocode.node.code)"
|
class="promocodes__item-button"
|
||||||
/>
|
@click="copyCode(promocode.node.code)"
|
||||||
<p>{{ promocode.node.code }}</p>
|
/>
|
||||||
|
<p>{{ promocode.node.code }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="promocodes__item-right">
|
||||||
|
<p class="promocodes__item-text">{{ promocode.node.discount }} {{ promocode.node.discountType === 'percent' ? '%' : CURRENCY }}</p>
|
||||||
|
<div class="promocodes__item-expire">{{ t('profile.promocodes.until') }} {{ useDate(promocode.node.endTime, locale) }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="promocodes__empty">{{ t('profile.promocodes.empty') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {usePageTitle} from "~/composables/utils";
|
import {usePageTitle} from "~/composables/utils";
|
||||||
import {useNotification} from "~/composables/notification/index.js";
|
import {useNotification} from "~/composables/notification/index.js";
|
||||||
|
import {CURRENCY} from "~/config/constants";
|
||||||
|
import {useDate} from "~/composables/date";
|
||||||
|
|
||||||
const {t} = useI18n();
|
const {t, locale} = useI18n();
|
||||||
const promocodesStore = usePromocodeStore();
|
const promocodesStore = usePromocodeStore();
|
||||||
|
|
||||||
const promocodes = computed(() => promocodesStore.promocodes);
|
const promocodes = computed(() => promocodesStore.promocodes);
|
||||||
|
|
@ -40,7 +49,7 @@ const copyCode = (code: string) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// TODO: display more info about promo
|
// TODO: display time for expire date
|
||||||
const { setPageTitle } = usePageTitle();
|
const { setPageTitle } = usePageTitle();
|
||||||
|
|
||||||
setPageTitle(t('breadcrumbs.promocodes'));
|
setPageTitle(t('breadcrumbs.promocodes'));
|
||||||
|
|
@ -65,10 +74,20 @@ setPageTitle(t('breadcrumbs.promocodes'));
|
||||||
&__item {
|
&__item {
|
||||||
border-radius: $default_border_radius;
|
border-radius: $default_border_radius;
|
||||||
border: 1px solid $accent;
|
border: 1px solid $accent;
|
||||||
padding: 7px 15px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: stretch;
|
||||||
gap: 20px;
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
padding: 7px 25px;
|
||||||
|
|
||||||
|
& p {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&-button {
|
&-button {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
@ -80,9 +99,31 @@ setPageTitle(t('breadcrumbs.promocodes'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& p {
|
&-right {
|
||||||
font-weight: 600;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-expire {
|
||||||
|
height: 100%;
|
||||||
|
background-color: $accent;
|
||||||
|
padding-inline: 15px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__empty {
|
||||||
|
margin-top: 50px;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="wishlist__list">
|
<div class="wishlist__list">
|
||||||
<div class="wishlist__list-inner">
|
<div class="wishlist__list-inner" v-if="productsInWishlist.length">
|
||||||
<div
|
<div
|
||||||
class="wishlist__item"
|
class="wishlist__item"
|
||||||
v-for="product in productsInWishlist"
|
v-for="product in productsInWishlist"
|
||||||
|
|
@ -43,6 +43,7 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="wishlist__empty">{{ t('profile.wishlist.empty') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -112,8 +113,6 @@ setPageTitle(t('breadcrumbs.wishlist'));
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 50px;
|
gap: 50px;
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
|
|
||||||
&__top {
|
&__top {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
@ -162,8 +161,6 @@ setPageTitle(t('breadcrumbs.wishlist'));
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.2);
|
||||||
border-radius: $default_border_radius;
|
border-radius: $default_border_radius;
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
&-inner {
|
&-inner {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -178,5 +175,9 @@ setPageTitle(t('breadcrumbs.wishlist'));
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__empty {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
21
storefront/plugins/apollo-upload-link.ts
Normal file
21
storefront/plugins/apollo-upload-link.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import createUploadLink from "apollo-upload-client/createUploadLink.mjs";
|
||||||
|
|
||||||
|
export default defineNuxtPlugin(async () => {
|
||||||
|
const { client } = useApolloClient()
|
||||||
|
|
||||||
|
if (client.name !== 'default') return
|
||||||
|
|
||||||
|
const runtime = useRuntimeConfig()
|
||||||
|
const token = useCookie(
|
||||||
|
`${runtime.public.evibesProjectName?.toLowerCase()}-access`
|
||||||
|
).value || ''
|
||||||
|
|
||||||
|
const uploadLink = createUploadLink({
|
||||||
|
uri: `https://api.${runtime.public.evibesBaseDomain}/graphql/`,
|
||||||
|
credentials: 'include',
|
||||||
|
headers: { 'X-EVIBES-AUTH': token }
|
||||||
|
})
|
||||||
|
|
||||||
|
client.setLink(uploadLink)
|
||||||
|
console.log('✅ apollo link replaced to uploadLink')
|
||||||
|
})
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import type {IUser} from "~/types";
|
import type {IUser} from "~/types";
|
||||||
import {useAppConfig} from "~/composables/config";
|
import {useAppConfig} from "~/composables/config";
|
||||||
|
import {orderStatuses} from "~/config/constants";
|
||||||
|
|
||||||
export const useUserStore = defineStore('user', () => {
|
export const useUserStore = defineStore('user', () => {
|
||||||
const { COOKIES_ACCESS_TOKEN_KEY } = useAppConfig();
|
const { COOKIES_ACCESS_TOKEN_KEY } = useAppConfig();
|
||||||
|
|
@ -15,7 +16,7 @@ export const useUserStore = defineStore('user', () => {
|
||||||
|
|
||||||
const isAuthenticated = computed(() => Boolean(cookieAccess.value && user.value));
|
const isAuthenticated = computed(() => Boolean(cookieAccess.value && user.value));
|
||||||
const finishedOrdersQuantity = computed(() => {
|
const finishedOrdersQuantity = computed(() => {
|
||||||
return user.value?.orders.filter((order) => order.status === 'FINISHED').length || 0;
|
return user.value?.orders.filter((order) => order.status === orderStatuses.FINISHED).length || 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
const setUser = (data: IUser | null) => {
|
const setUser = (data: IUser | null) => {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
import type {IOrder} from "~/types";
|
import type {IOrder} from "~/types";
|
||||||
|
|
||||||
export interface IOrderResponse {
|
export interface IOrdersResponse {
|
||||||
orders: {
|
orders: {
|
||||||
edges: {
|
edges: {
|
||||||
node: IOrder
|
node: IOrder
|
||||||
}[]
|
}[],
|
||||||
|
pageInfo: {
|
||||||
|
hasNextPage: boolean
|
||||||
|
endCursor: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
import type {IUser} from "~/types";
|
import type {
|
||||||
|
IOrdersResponse,
|
||||||
|
IPromocodesResponse,
|
||||||
|
IUser,
|
||||||
|
IWishlistResponse
|
||||||
|
} from "~/types";
|
||||||
|
|
||||||
export interface IUserResponse {
|
export interface IUserResponse {
|
||||||
updateUser: {
|
updateUser: {
|
||||||
|
|
@ -6,6 +11,11 @@ export interface IUserResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IUserBaseDataResponse
|
||||||
|
extends IWishlistResponse,
|
||||||
|
IOrdersResponse,
|
||||||
|
IPromocodesResponse {}
|
||||||
|
|
||||||
export interface IUserActivationResponse {
|
export interface IUserActivationResponse {
|
||||||
activateUser: {
|
activateUser: {
|
||||||
success: boolean
|
success: boolean
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ export interface IOrder {
|
||||||
status: string,
|
status: string,
|
||||||
buyTime: string | null,
|
buyTime: string | null,
|
||||||
humanReadableId: string,
|
humanReadableId: string,
|
||||||
|
notifications: string | null,
|
||||||
orderProducts: {
|
orderProducts: {
|
||||||
edges: {
|
edges: {
|
||||||
node: {
|
node: {
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@ export interface IPromocode {
|
||||||
discount: string,
|
discount: string,
|
||||||
discountType: string,
|
discountType: string,
|
||||||
endTime: string,
|
endTime: string,
|
||||||
id: string,
|
|
||||||
startTime: string,
|
startTime: string,
|
||||||
usedOn: string,
|
|
||||||
uuid: string
|
uuid: string
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue