Features: 1) Add product rating support in types, GraphQL fragments, and UI components; 2) Implement feedback management including GraphQL mutations, composables, and notification handling; 3) Enhance locale switching with improved reactivity, Apollo query clearing, and supported locale validation; 4) Introduce useOrderBuy composable for order purchasing workflow.

Fixes: 1) Correct mutation name from `setlanguage` to `setLanguage` for consistency; 2) Improve product listing reactivity by addressing missing initialization in `useStore`; 3) Replace generic product queries with parametrized `useProducts` for modularity; 4) Resolve minor typos, missing semicolons, and code formatting inconsistencies.

Extra: 1) Refactor feedback-related types, composables, and GraphQL utilities for modularity; 2) Update styles, Vue templates, and related scripts with enhanced formatting; 3) Remove unused methods like `getProducts`, standardizing query reactivity; 4) Cleanup and organize imports across multiple files.
This commit is contained in:
Alexandr SaVBaD Waltz 2025-10-06 18:19:19 +03:00
parent 949e077942
commit c9807bd6d4
34 changed files with 418 additions and 154 deletions

View file

@ -20,7 +20,7 @@
import {useAppConfig} from "~/composables/config"; import {useAppConfig} from "~/composables/config";
import { DEFAULT_LOCALE } from '~/config/constants'; import { DEFAULT_LOCALE } from '~/config/constants';
import {useRefresh} from "~/composables/auth"; import {useRefresh} from "~/composables/auth";
import {useLanguages} from "~/composables/languages"; import {useLanguages, useLocaleRedirect} from "~/composables/languages";
import {useCompanyInfo} from "~/composables/company"; import {useCompanyInfo} from "~/composables/company";
import {useCategories} from "~/composables/categories"; import {useCategories} from "~/composables/categories";
@ -55,6 +55,7 @@ const cookieLocale = useCookie(
const { refresh } = useRefresh(); const { refresh } = useRefresh();
const { getCategories } = await useCategories(); const { getCategories } = await useCategories();
const { isSupportedLocale } = useLocaleRedirect();
let refreshInterval: NodeJS.Timeout; let refreshInterval: NodeJS.Timeout;
@ -83,20 +84,32 @@ watch(locale, () => {
let stopWatcher: VoidFunction = () => {}; let stopWatcher: VoidFunction = () => {};
if (!cookieLocale.value) {
cookieLocale.value = DEFAULT_LOCALE;
await router.push({path: switchLocalePath(cookieLocale.value)});
}
if (locale.value !== cookieLocale.value) {
if (isSupportedLocale(cookieLocale.value)) {
await router.push({
path: switchLocalePath(cookieLocale.value),
query: route.query
});
} else {
cookieLocale.value = DEFAULT_LOCALE
await router.push({
path: switchLocalePath(DEFAULT_LOCALE),
query: route.query
});
}
}
onMounted( async () => { onMounted( async () => {
refreshInterval = setInterval(async () => { refreshInterval = setInterval(async () => {
await refresh(); await refresh();
}, 600000); }, 600000);
if (!cookieLocale.value) {
cookieLocale.value = DEFAULT_LOCALE;
await router.push({path: switchLocalePath(cookieLocale.value)});
}
if (locale.value !== cookieLocale.value) {
await router.push({path: switchLocalePath(cookieLocale.value)});
}
stopWatcher = watch( stopWatcher = watch(
() => appStore.isOverflowHidden, () => appStore.isOverflowHidden,
(hidden) => { (hidden) => {

View file

@ -30,14 +30,14 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const companyStore = useCompanyStore() const companyStore = useCompanyStore();
const { t } = useI18n() const { t } = useI18n();
const companyInfo = computed(() => companyStore.companyInfo) const companyInfo = computed(() => companyStore.companyInfo);
const encodedCompanyAddress = computed(() => { const encodedCompanyAddress = computed(() => {
return companyInfo.value?.companyAddress ? encodeURIComponent(companyInfo.value?.companyAddress) : '' return companyInfo.value?.companyAddress ? encodeURIComponent(companyInfo.value?.companyAddress) : '';
}) });
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View file

@ -59,25 +59,25 @@ import {onClickOutside} from "@vueuse/core";
import type {ICategory} from "~/types"; import type {ICategory} from "~/types";
import {useCategoryStore} from "~/stores/category"; import {useCategoryStore} from "~/stores/category";
const { t } = useI18n() const { t } = useI18n();
const categoryStore = useCategoryStore(); const categoryStore = useCategoryStore();
const categories = computed(() => categoryStore.categories) const categories = computed(() => categoryStore.categories);
const isBlockOpen = ref<boolean>(false) const isBlockOpen = ref<boolean>(false);
const setBlock = (state: boolean) => { const setBlock = (state: boolean) => {
isBlockOpen.value = state isBlockOpen.value = state;
} };
// TODO: add loading state // TODO: add loading state
const blockRef = ref(null) const blockRef = ref(null);
onClickOutside(blockRef, () => setBlock(false)) onClickOutside(blockRef, () => setBlock(false));
const activeCategory = ref<ICategory>(categories.value[0]?.node) const activeCategory = ref<ICategory>(categories.value[0]?.node);
const setActiveCategory = (category: ICategory) => { const setActiveCategory = (category: ICategory) => {
activeCategory.value = category activeCategory.value = category;
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -42,19 +42,19 @@
</form> </form>
</template> </template>
<script setup> <script setup lang="ts">
import {useValidators} from "~/composables/rules"; import {useValidators} from "~/composables/rules";
import {useContactUs} from "~/composables/contact/index.js"; import {useContactUs} from "~/composables/contact/index.js";
const { t } = useI18n() const { t } = useI18n();
const { required } = useValidators() const { required } = useValidators();
const name = ref('') const name = ref<string>('');
const email = ref('') const email = ref<string>('');
const phoneNumber = ref('') const phoneNumber = ref<string>('');
const subject = ref('') const subject = ref<string>('');
const message = ref('') const message = ref<string>('');
const isFormValid = computed(() => { const isFormValid = computed(() => {
return ( return (
@ -63,8 +63,8 @@ const isFormValid = computed(() => {
required(phoneNumber.value) === true && required(phoneNumber.value) === true &&
required(subject.value) === true && required(subject.value) === true &&
required(message.value) === true required(message.value) === true
) );
}) });
const { contactUs, loading } = useContactUs(); const { contactUs, loading } = useContactUs();

View file

@ -24,28 +24,28 @@
</form> </form>
</template> </template>
<script setup> <script setup lang="ts">
import {useValidators} from "~/composables/rules/index.js"; import {useValidators} from "~/composables/rules/index.js";
import {useNewPassword} from "@/composables/auth"; import {useNewPassword} from "@/composables/auth";
const { t } = useI18n() const { t } = useI18n();
const { isPasswordValid } = useValidators() const { isPasswordValid } = useValidators();
const password = ref('') const password = ref<string>('');
const confirmPassword = ref('') const confirmPassword = ref<string>('');
const compareStrings = (v) => { const compareStrings = (v: string) => {
if (v === password.value) return true if (v === password.value) return true;
return t('errors.compare') return t('errors.compare');
} };
const isFormValid = computed(() => { const isFormValid = computed(() => {
return ( return (
isPasswordValid(password.value) === true && isPasswordValid(password.value) === true &&
compareStrings(confirmPassword.value) === true compareStrings(confirmPassword.value) === true
) );
}) });
const { newPassword, loading } = useNewPassword(); const { newPassword, loading } = useNewPassword();

View file

@ -19,21 +19,21 @@
</form> </form>
</template> </template>
<script setup> <script setup lang="ts">
import {useValidators} from "~/composables/rules"; import {useValidators} from "~/composables/rules";
import {usePasswordReset} from "@/composables/auth"; import {usePasswordReset} from "@/composables/auth";
const { t } = useI18n() const { t } = useI18n();
const { isEmail } = useValidators() const { isEmail } = useValidators();
const email = ref('') const email = ref<string>('');
const isFormValid = computed(() => { const isFormValid = computed(() => {
return ( return (
isEmail(email.value) === true isEmail(email.value) === true
) );
}) });
const { resetPassword, loading } = usePasswordReset(); const { resetPassword, loading } = usePasswordReset();

View file

@ -9,13 +9,13 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const img = useImage() const img = useImage();
const backgroundStyles = computed(() => { const backgroundStyles = computed(() => {
const imgUrl = img('/images/homeBg.png', { format: 'webp', densities: 'x1 x2' }) const imgUrl = img('/images/homeBg.png', { format: 'webp', densities: 'x1 x2' });
return { backgroundImage: `url('${imgUrl}')` } return { backgroundImage: `url('${imgUrl}')` };
}) });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -1,22 +1,22 @@
<template> <template>
<client-only> <client-only>
<el-breadcrumb separator="/" class="breadcrumbs"> <el-breadcrumb separator="/" class="breadcrumbs">
<el-breadcrumb-item <el-breadcrumb-item
v-for="(crumb, idx) in breadcrumbs" v-for="(crumb, idx) in breadcrumbs"
:key="idx" :key="idx"
>
<nuxt-link-locale
v-if="idx !== breadcrumbs.length - 1"
:to="crumb.link"
class="breadcrumbs__link"
> >
{{ crumb.text }} <nuxt-link-locale
</nuxt-link-locale> v-if="idx !== breadcrumbs.length - 1"
<span v-else class="breadcrumbs__current"> :to="crumb.link"
{{ crumb.text }} class="breadcrumbs__link"
</span> >
</el-breadcrumb-item> {{ crumb.text }}
</el-breadcrumb> </nuxt-link-locale>
<span v-else class="breadcrumbs__current">
{{ crumb.text }}
</span>
</el-breadcrumb-item>
</el-breadcrumb>
</client-only> </client-only>
</template> </template>

View file

@ -4,6 +4,7 @@ import {GET_CATEGORIES} from "~/graphql/queries/standalone/categories";
export async function useCategories() { export async function useCategories() {
const categoryStore = useCategoryStore(); const categoryStore = useCategoryStore();
const { locale } = useI18n();
const getCategories = async (cursor?: string): Promise<void> => { const getCategories = async (cursor?: string): Promise<void> => {
const {data, error} = await useAsyncQuery<ICategoriesResponse>( const {data, error} = await useAsyncQuery<ICategoriesResponse>(
@ -31,6 +32,11 @@ export async function useCategories() {
if (error.value) console.error('useCategories error:', error.value); if (error.value) console.error('useCategories error:', error.value);
} }
watch(locale, async () => {
categoryStore.setCategories([]);
await getCategories();
});
return { return {
getCategories getCategories
}; };

View file

@ -3,25 +3,37 @@ import type {IContactUsResponse} from "~/types";
import {CONTACT_US} from "~/graphql/mutations/contact"; import {CONTACT_US} from "~/graphql/mutations/contact";
import {useNotification} from "~/composables/notification"; import {useNotification} from "~/composables/notification";
interface IContactUsArguments {
name: string,
email: string,
phoneNumber?: string,
subject?: string,
message: string
}
export function useContactUs() { export function useContactUs() {
const {t} = useI18n(); const {t} = useI18n();
const { mutate, loading, error } = useMutation<IContactUsResponse>(CONTACT_US); const { mutate, loading, error } = useMutation<IContactUsResponse>(CONTACT_US);
async function contactUs( async function contactUs(
name: string, args: IContactUsArguments
email: string,
phoneNumber: string,
subject: string,
message: string
) { ) {
const result = await mutate({ const variables: Record<string, any> = {
name, name: args.name,
email, email: args.email,
phoneNumber, message: args.message
subject, };
message
}); if (args.phoneNumber) {
variables.phoneNumber = args.phoneNumber;
}
if (args.subject) {
variables.subject = args.subject;
}
const result = await mutate(variables);
if (result?.data?.contactUs.received) { if (result?.data?.contactUs.received) {
useNotification({ useNotification({

View file

@ -0,0 +1 @@
export * from './useFeedbackAction'

View file

@ -0,0 +1,74 @@
import {useNotification} from "~/composables/notification";
import type {IFeedbackActionResponse} from "~/types";
import {MANAGE_FEEDBACK} from "~/graphql/mutations/feedbacks";
import {isGraphQLError} from "~/utils/error";
interface IFeedbackActionArguments {
action: string,
orderProductUuid: string,
comment?: string,
rating?: number
}
export function useFeedbackAction() {
const {t} = useI18n();
const userStore = useUserStore();
const isAuthenticated = computed(() => userStore.isAuthenticated);
const { mutate, loading, error } = useMutation<IFeedbackActionResponse>(MANAGE_FEEDBACK);
async function manageFeedback(
args: IFeedbackActionArguments
) {
if (isAuthenticated.value) {
const variables: Record<string, any> = {
action: args.action,
orderProductUuid: args.orderProductUuid,
};
if (args.comment) {
variables.comment = args.comment;
}
if (args.rating) {
variables.rating = args.rating;
}
const result = await mutate(variables);
if (result?.data?.feedback) {
useNotification({
message: t('popup.success.addFeedback'),
type: 'success'
});
}
} else {
useNotification({
message: t('popup.errors.loginFirst'),
type: 'error'
});
}
}
watch(error, (err) => {
if (!err) return;
console.error('useFeedbackAction 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{
loading,
manageFeedback
};
}

View file

@ -6,6 +6,7 @@ import {DEFAULT_LOCALE} from "@intlify/core-base";
export function useLanguageSwitch() { export function useLanguageSwitch() {
const userStore = useUserStore(); const userStore = useUserStore();
const router = useRouter(); const router = useRouter();
const { $apollo } = useNuxtApp() as any;
const { COOKIES_LOCALE_KEY } = useAppConfig(); const { COOKIES_LOCALE_KEY } = useAppConfig();
const switchLocalePath = useSwitchLocalePath(); const switchLocalePath = useSwitchLocalePath();
@ -23,21 +24,42 @@ export function useLanguageSwitch() {
const { mutate, loading, error } = useMutation<IUserResponse>(SWITCH_LANGUAGE); const { mutate, loading, error } = useMutation<IUserResponse>(SWITCH_LANGUAGE);
let isSwitching = false;
async function switchLanguage( async function switchLanguage(
locale: string locale: string
) { ) {
cookieLocale.value = locale; if (isSwitching || cookieLocale.value === locale) return;
await router.push({path: switchLocalePath(cookieLocale.value as LocaleDefinition['code'])})
if (isAuthenticated.value) { try {
const result = await mutate({ isSwitching = true;
uuid: userUuid.value,
locale cookieLocale.value = locale;
await $apollo.defaultClient.clearStore();
await router.push({path: switchLocalePath(cookieLocale.value as LocaleDefinition['code'])});
if (isAuthenticated.value) {
const result = await mutate({
uuid: userUuid.value,
locale
});
if (result?.data?.updateUser) {
userStore.setUser(result.data.updateUser.user);
}
}
await new Promise(resolve => setTimeout(resolve, 100));
await $apollo.defaultClient.refetchQueries({
include: 'active'
}); });
if (result?.data?.updateUser) { } catch (error) {
userStore.setUser(result.data.updateUser.user) console.error('Error switching language:', error);
} } finally {
isSwitching = false;
} }
} }

View file

@ -32,6 +32,7 @@ export function useLocaleRedirect() {
} }
return { return {
isSupportedLocale,
checkAndRedirect checkAndRedirect
}; };
} }

View file

@ -23,10 +23,14 @@ export function useNotification(
const messageVNode = h('div', [bodyContent, progressBar]) const messageVNode = h('div', [bodyContent, progressBar])
ElNotification({ const notification = ElNotification({
title: args.title, title: args.title,
duration, duration: 0,
message: messageVNode, message: messageVNode,
type: args.type type: args.type
} as NotificationOptions) } as NotificationOptions)
setTimeout(() => {
notification.close()
}, duration)
} }

View file

@ -1,2 +1,3 @@
export * from './useOrderOverwrite'; export * from './useOrderOverwrite';
export * from './useOrders'; export * from './useOrders';
export * from './useOrderBuy';

View file

@ -0,0 +1,46 @@
import {isGraphQLError} from "~/utils/error";
import type {IBuyOrderResponse} from "~/types";
import {useNotification} from "~/composables/notification";
import {BUY_CART} from "~/graphql/mutations/cart";
export function useOrderBuy() {
const {t} = useI18n();
const { mutate, loading, error } = useMutation<IBuyOrderResponse>(BUY_CART);
async function buyOrder(
orderUuid: string,
) {
const result = await mutate({
orderUuid,
forcePayment: true,
});
if (result?.data?.buyOrder?.transaction?.process?.redirect_url) {
window.location.href = result.data.buyOrder.transaction.process.redirect_url
} else {
console.log(result?.data)
}
}
watch(error, (err) => {
if (!err) return;
console.error('useOrderBuy 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 {
buyOrder,
loading
};
}

View file

@ -1,29 +1,34 @@
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';
export function useProducts() { export function useProducts(params: Record<string, any> = {}) {
const variables = ref({ first: 12 }); const variables = ref({
first: 12,
...params
});
const { data, error, refresh } = useAsyncQuery<IProductResponse>( const { data, error } = useAsyncQuery<IProductResponse>(
GET_PRODUCTS, GET_PRODUCTS,
variables variables
); );
const products = computed(() => data.value?.products?.edges ?? []); const products = computed(() => data.value?.products?.edges ?? []);
const pageInfo = computed(() => data.value?.products?.pageInfo ?? {}); const pageInfo = computed(() => data.value?.products?.pageInfo ?? {});
const getProducts = async (params: Record<string, any> = {}) => {
variables.value = { ...variables.value, ...params };
await refresh();
};
watch(error, (e) => { watch(error, (e) => {
if (e) console.error('useProducts error:', e); if (e) console.error('useProducts error:', e);
}); });
const updateVariables = (newParams: Record<string, any>) => {
variables.value = {
first: 12,
...newParams
};
};
return { return {
products, products,
pageInfo, pageInfo,
getProducts updateVariables
}; };
} }

View file

@ -12,12 +12,12 @@ interface IProdVars {
} }
export function useStore( export function useStore(
slug: string, slug: string,
attributes?: string, attributes?: string,
orderBy?: string, orderBy?: string,
minPrice?: number, minPrice?: number,
maxPrice?: number, maxPrice?: number,
productAfter?: string productAfter?: string
) { ) {
const variables = reactive<IProdVars>({ const variables = reactive<IProdVars>({
first: 15, first: 15,
@ -36,31 +36,47 @@ export function useStore(
const products = ref([...(data.value?.products.edges ?? [])]); const products = ref([...(data.value?.products.edges ?? [])]);
const pageInfo = computed(() => data.value?.products.pageInfo ?? null); const pageInfo = computed(() => data.value?.products.pageInfo ?? null);
const isInitialized = ref(false);
watch(error, e => e && console.error('useStore products error', e)); watch(error, e => e && console.error('useStore products error', e));
watch( watch(
() => variables.productAfter, data,
async (newCursor, oldCursor) => { (newData) => {
if (!newCursor || newCursor === oldCursor) return; if (!newData?.products?.edges) return;
await refresh();
const newEdges = data.value?.products.edges ?? []; const newEdges = newData.products.edges;
products.value = [...products.value, ...newEdges];
} if (!isInitialized.value || !variables.productAfter) {
products.value = [...newEdges];
isInitialized.value = true;
} else {
products.value = [...products.value, ...newEdges];
}
},
{ immediate: true }
); );
watch( watch(
[ () => variables.productAfter,
() => variables.attributes, async (newCursor, oldCursor) => {
() => variables.orderBy, if (!newCursor || newCursor === oldCursor || !isInitialized.value) return;
() => variables.minPrice, await refresh();
() => variables.maxPrice }
], );
async () => {
variables.productAfter = ''; watch(
await refresh(); [
products.value = [...(data.value?.products.edges ?? [])]; () => variables.categoriesSlugs,
} () => variables.attributes,
() => variables.orderBy,
() => variables.minPrice,
() => variables.maxPrice
],
async () => {
variables.productAfter = '';
await refresh();
}
); );
return { return {

View file

@ -0,0 +1,7 @@
export const FEEDBACK_FRAGMENT = gql`
fragment Feedback on FeedbackType {
comment
rating
uuid
}
`

View file

@ -4,6 +4,7 @@ export const PRODUCT_FRAGMENT = gql`
name name
price price
quantity quantity
rating
slug slug
description description
brand { brand {

View file

@ -83,4 +83,22 @@ export const BULK_CART = gql`
} }
} }
${ORDER_FRAGMENT} ${ORDER_FRAGMENT}
`
export const BUY_CART = gql`
mutation buyOrder(
$orderUuid: String!,
$forcePayment: Boolean!
) {
buyOrder(
orderUuid: $orderUuid
forcePayment: $forcePayment
) {
transaction {
amount
process
paymentMethod
}
}
}
` `

View file

@ -0,0 +1,22 @@
import {FEEDBACK_FRAGMENT} from "~/graphql/fragments/feedback.fragment";
export const MANAGE_FEEDBACK = gql`
mutation addFeedback(
$action: String!,
$comment: String,
$orderProductUuid: UUID!,
$rating: Int
) {
feedbackProductAction(
action: $action,
comment: $comment,
orderProductUuid: $orderProductUuid,
rating: $rating
) {
feedback {
...Feedback
}
}
}
${FEEDBACK_FRAGMENT}
`

View file

@ -1,7 +1,7 @@
import {USER_FRAGMENT} from "~/graphql/fragments/user.fragment"; import {USER_FRAGMENT} from "~/graphql/fragments/user.fragment";
export const SWITCH_LANGUAGE = gql` export const SWITCH_LANGUAGE = gql`
mutation setlanguage( mutation setLanguage(
$uuid: UUID!, $uuid: UUID!,
$language: String, $language: String,
) { ) {

View file

@ -66,7 +66,8 @@
"avatarUpload": "You have successfully uploaded an avatar!", "avatarUpload": "You have successfully uploaded an avatar!",
"userUpdate": "Profile successfully updated!", "userUpdate": "Profile successfully updated!",
"referralCopy": "You copied your referal link!", "referralCopy": "You copied your referal link!",
"promocodeCopy": "You copied your promocode!" "promocodeCopy": "You copied your promocode!",
"addFeedback": "Your feedback has been saved!"
}, },
"addToCartLimit": "Total quantity limit is {quantity}!", "addToCartLimit": "Total quantity limit is {quantity}!",
"failAdd": "Please log in to make a purchase", "failAdd": "Please log in to make a purchase",

View file

@ -44,7 +44,7 @@ export default defineNuxtConfig({
title: process.env.EVIBES_PROJECT_NAME, title: process.env.EVIBES_PROJECT_NAME,
titleTemplate: `${process.env.EVIBES_PROJECT_NAME} | %s`, titleTemplate: `${process.env.EVIBES_PROJECT_NAME} | %s`,
link: [ link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }, { rel: 'icon', type: 'image/x-icon', href: `https://${process.env.EVIBES_BASE_DOMAIN}/favicon.ico` },
] ]
}, },
pageTransition: { pageTransition: {

View file

@ -16,7 +16,6 @@ import {useUserActivation} from "~/composables/user";
import { useRouteQuery } from '@vueuse/router'; import { useRouteQuery } from '@vueuse/router';
import {useBrands} from "~/composables/brands"; import {useBrands} from "~/composables/brands";
import {useProducts, useProductTags} from "~/composables/products"; import {useProducts, useProductTags} from "~/composables/products";
import type {IProduct} from "~/types";
const {t} = useI18n(); const {t} = useI18n();
const appStore = useAppStore(); const appStore = useAppStore();
@ -24,7 +23,7 @@ const route = useRoute();
useHead({ useHead({
title: t('breadcrumbs.home'), title: t('breadcrumbs.home'),
}) });
const token = useRouteQuery('token', ''); const token = useRouteQuery('token', '');
const uid = useRouteQuery('uid', ''); const uid = useRouteQuery('uid', '');
@ -32,17 +31,10 @@ const referrer = useRouteQuery('referrer', '');
const { activateUser } = useUserActivation(); const { activateUser } = useUserActivation();
const newProducts = ref<{ cursor: string; node: IProduct }[]>([]);
const priceProducts = ref<{ cursor: string; node: IProduct }[]>([]);
const { brands } = useBrands(); const { brands } = useBrands();
const { tags } = useProductTags(); const { tags } = useProductTags();
const { products, getProducts } = useProducts(); const { products: newProducts } = useProducts({ orderBy: '-modified' });
const { products: priceProducts } = useProducts({ orderBy: '-price' });
await getProducts({ orderBy: '-modified' });
newProducts.value = products.value;
await getProducts({ orderBy: '-price' });
priceProducts.value = products.value;
onMounted( async () => { onMounted( async () => {
if (route.path.includes('activate-user') && token.value && uid.value) { if (route.path.includes('activate-user') && token.value && uid.value) {

View file

@ -53,7 +53,7 @@
</div> </div>
<el-rate <el-rate
class="white" class="white"
v-model="rating" :model-value="product?.rating"
allow-half allow-half
disabled disabled
/> />
@ -224,10 +224,7 @@ if (meta) {
}); });
} }
const { products, getProducts } = useProducts(); const { products } = useProducts({ categoriesSlugs: product.value?.category.slug });
await getProducts({
categoriesSlugs: product.value?.category.slug
});
const isProductInWishlist = computed(() => { const isProductInWishlist = computed(() => {
const el = wishlistStore.wishlist?.products?.edges.find( const el = wishlistStore.wishlist?.products?.edges.find(
@ -246,10 +243,6 @@ const images = computed<string[]>(() =>
: [] : []
); );
const rating = computed(() => {
return product.value?.feedbacks.edges[0]?.node?.rating ?? 3;
});
const attributes = computed(() => { const attributes = computed(() => {
const edges = product.value?.attributeGroups.edges ?? []; const edges = product.value?.attributeGroups.edges ?? [];

View file

@ -0,0 +1,5 @@
import type {IFeedback} from "~/types";
export interface IFeedbackActionResponse {
feedback: IFeedback
}

View file

@ -40,4 +40,16 @@ export interface IBulkOrderResponse {
bulkOrderAction: { bulkOrderAction: {
order: IOrder order: IOrder
} }
}
export interface IBuyOrderResponse {
buyOrder: {
transaction: {
amount: number,
process: {
invoice_id: number,
redirect_url: string
}
}
}
} }

View file

@ -0,0 +1,5 @@
export interface IFeedback {
comment: string,
rating: number,
uuid: string
}

View file

@ -15,6 +15,10 @@ export interface IOrder {
attributes: string, attributes: string,
quantity: number, quantity: number,
status: string, status: string,
feedback: {
uuid: string,
rating: number
},
product: IProduct product: IProduct
} }
}[] }[]

View file

@ -5,6 +5,7 @@ export interface IProduct {
name: string, name: string,
price: number, price: number,
quantity: number, quantity: number,
rating: number,
slug: string, slug: string,
description: string, description: string,
seoMeta: ISEOMeta, seoMeta: ISEOMeta,

View file

@ -12,6 +12,7 @@ export * from './app/category'
export * from './app/store' export * from './app/store'
export * from './app/promocodes' export * from './app/promocodes'
export * from './app/seometa' export * from './app/seometa'
export * from './app/feedbacks'
@ -29,4 +30,5 @@ export * from './api/categories'
export * from './api/brands' export * from './api/brands'
export * from './api/contact' export * from './api/contact'
export * from './api/store' export * from './api/store'
export * from './api/promocodes' export * from './api/promocodes'
export * from './api/feedbacks'