Features: 1) Add SEO-related fragments to GraphQL queries including SEOMETA_FRAGMENT usage in brands, categories, and products queries; 2) Enable localized and dynamic SEO metadata handling in category pages with Vue composables and useSeoMeta; 3) Replace obsolete client-only wrapper with native Nuxt components like nuxt-marquee for enhanced rendering;
Fixes: 1) Correct file path imports by removing `.js` extensions in GraphQL fragments; 2) Resolve typo in `usePromocodeStore` composables to ensure consistent store usage; 3) Add missing `:type="submit"` to login form button for proper form submission handling; Extra: 1) Remove unused `.idea` and `README.md` files for repository cleanup; 2) Delete extraneous dependencies from `package-lock.json` for streamlined package management; 3) Refactor category slug handling with improved composable logic for cleaner route parameters and SEO alignment.
This commit is contained in:
parent
408dee727e
commit
40ae24a04c
65 changed files with 648 additions and 660 deletions
|
|
@ -1,35 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="PYTHON_MODULE" version="4">
|
|
||||||
<component name="FacetManager">
|
|
||||||
<facet type="django" name="Django">
|
|
||||||
<configuration>
|
|
||||||
<option name="rootFolder" value="$MODULE_DIR$" />
|
|
||||||
<option name="settingsModule" value="evibes/settings/__init__.py" />
|
|
||||||
<option name="manageScript" value="$MODULE_DIR$/manage.py" />
|
|
||||||
<option name="environment" value="<map/>" />
|
|
||||||
<option name="doNotUseTestRunner" value="false" />
|
|
||||||
<option name="trackFilePattern" value="migrations" />
|
|
||||||
</configuration>
|
|
||||||
</facet>
|
|
||||||
</component>
|
|
||||||
<component name="NewModuleRootManager">
|
|
||||||
<content url="file://$MODULE_DIR$" />
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
<component name="PyDocumentationSettings">
|
|
||||||
<option name="format" value="PLAIN" />
|
|
||||||
<option name="myDocStringFormat" value="Plain" />
|
|
||||||
</component>
|
|
||||||
<component name="TemplatesService">
|
|
||||||
<option name="TEMPLATE_CONFIGURATION" value="Django" />
|
|
||||||
<option name="TEMPLATE_FOLDERS">
|
|
||||||
<list>
|
|
||||||
<option value="$MODULE_DIR$/blog/templates" />
|
|
||||||
<option value="$MODULE_DIR$/core/templates" />
|
|
||||||
<option value="$MODULE_DIR$/payments/templates" />
|
|
||||||
<option value="$MODULE_DIR$/vibes_auth/templates" />
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
# Nuxt Minimal Starter
|
|
||||||
|
|
||||||
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
Make sure to install dependencies:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# npm
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# pnpm
|
|
||||||
pnpm install
|
|
||||||
|
|
||||||
# yarn
|
|
||||||
yarn install
|
|
||||||
|
|
||||||
# bun
|
|
||||||
bun install
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development Server
|
|
||||||
|
|
||||||
Start the development server on `http://localhost:3000`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# npm
|
|
||||||
npm run dev
|
|
||||||
|
|
||||||
# pnpm
|
|
||||||
pnpm dev
|
|
||||||
|
|
||||||
# yarn
|
|
||||||
yarn dev
|
|
||||||
|
|
||||||
# bun
|
|
||||||
bun run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Production
|
|
||||||
|
|
||||||
Build the application for production:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# npm
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
# pnpm
|
|
||||||
pnpm build
|
|
||||||
|
|
||||||
# yarn
|
|
||||||
yarn build
|
|
||||||
|
|
||||||
# bun
|
|
||||||
bun run build
|
|
||||||
```
|
|
||||||
|
|
||||||
Locally preview production build:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# npm
|
|
||||||
npm run preview
|
|
||||||
|
|
||||||
# pnpm
|
|
||||||
pnpm preview
|
|
||||||
|
|
||||||
# yarn
|
|
||||||
yarn preview
|
|
||||||
|
|
||||||
# bun
|
|
||||||
bun run preview
|
|
||||||
```
|
|
||||||
|
|
||||||
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
|
||||||
|
|
@ -23,7 +23,6 @@ import {useRefresh} from "~/composables/auth";
|
||||||
import {useLanguages} from "~/composables/languages";
|
import {useLanguages} from "~/composables/languages";
|
||||||
import {useCompanyInfo} from "~/composables/company";
|
import {useCompanyInfo} from "~/composables/company";
|
||||||
import {useCategories} from "~/composables/categories";
|
import {useCategories} from "~/composables/categories";
|
||||||
import {useNotification} from "~/composables/notification";
|
|
||||||
|
|
||||||
const { locale } = useI18n();
|
const { locale } = useI18n();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
@ -38,7 +37,8 @@ const showBreadcrumbs = computed(() => {
|
||||||
'brand',
|
'brand',
|
||||||
'search',
|
'search',
|
||||||
'profile',
|
'profile',
|
||||||
'activate-user'
|
'activate-user',
|
||||||
|
'reset-password'
|
||||||
].some(prefix => name.startsWith(prefix));
|
].some(prefix => name.startsWith(prefix));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,8 @@
|
||||||
<nuxt-img
|
<nuxt-img
|
||||||
:src="product.node.product.images.edges[0].node.image"
|
:src="product.node.product.images.edges[0].node.image"
|
||||||
:alt="product.node.product.name"
|
:alt="product.node.product.name"
|
||||||
|
format="webp"
|
||||||
|
densities="x1"
|
||||||
/>
|
/>
|
||||||
<p>{{ product.node.product.name }}</p>
|
<p>{{ product.node.product.name }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@
|
||||||
productUuid: product.uuid,
|
productUuid: product.uuid,
|
||||||
productName: product.name
|
productName: product.name
|
||||||
})"
|
})"
|
||||||
|
:type="'button'"
|
||||||
:isLoading="removeLoading"
|
:isLoading="removeLoading"
|
||||||
>
|
>
|
||||||
{{ t('buttons.removeFromCart') }}
|
{{ t('buttons.removeFromCart') }}
|
||||||
|
|
@ -79,6 +80,7 @@
|
||||||
productUuid: product.uuid,
|
productUuid: product.uuid,
|
||||||
productName: product.name
|
productName: product.name
|
||||||
})"
|
})"
|
||||||
|
:type="'button'"
|
||||||
:isLoading="addLoading"
|
:isLoading="addLoading"
|
||||||
>
|
>
|
||||||
{{ t('buttons.addToCart') }}
|
{{ t('buttons.addToCart') }}
|
||||||
|
|
@ -106,7 +108,7 @@
|
||||||
>
|
>
|
||||||
-
|
-
|
||||||
</button>
|
</button>
|
||||||
<span class="tools__item tools__item-count" v-text="'X' + productinCartQuantity" />
|
<span class="tools__item tools__item-count" v-text="'X' + productInCartQuantity" />
|
||||||
<button
|
<button
|
||||||
class="tools__item tools__item-button"
|
class="tools__item tools__item-button"
|
||||||
@click="overwriteOrder({
|
@click="overwriteOrder({
|
||||||
|
|
@ -156,7 +158,7 @@ const isProductInWishlist = computed(() => {
|
||||||
const isProductInCart = computed(() => {
|
const isProductInCart = computed(() => {
|
||||||
return cartStore.currentOrder?.orderProducts?.edges.find((prod) => prod.node.product.uuid === props.product?.uuid);
|
return cartStore.currentOrder?.orderProducts?.edges.find((prod) => prod.node.product.uuid === props.product?.uuid);
|
||||||
});
|
});
|
||||||
const productinCartQuantity = computed(() => {
|
const productInCartQuantity = computed(() => {
|
||||||
return cartStore.currentOrder?.orderProducts?.edges.filter((prod) => prod.node.product.uuid === props.product.uuid)[0].node.quantity;
|
return cartStore.currentOrder?.orderProducts?.edges.filter((prod) => prod.node.product.uuid === props.product.uuid)[0].node.quantity;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@
|
||||||
v-model="message"
|
v-model="message"
|
||||||
/>
|
/>
|
||||||
<ui-button
|
<ui-button
|
||||||
|
:type="'submit'"
|
||||||
class="form__button"
|
class="form__button"
|
||||||
:isDisabled="!isFormValid"
|
:isDisabled="!isFormValid"
|
||||||
:isLoading="loading"
|
:isLoading="loading"
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ui-button
|
<ui-button
|
||||||
|
:type="'submit'"
|
||||||
class="form__button"
|
class="form__button"
|
||||||
:isDisabled="!isFormValid"
|
:isDisabled="!isFormValid"
|
||||||
:isLoading="loading"
|
:isLoading="loading"
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@
|
||||||
{{ t('forms.login.forgot') }}
|
{{ t('forms.login.forgot') }}
|
||||||
</ui-link>
|
</ui-link>
|
||||||
<ui-button
|
<ui-button
|
||||||
|
:type="'submit'"
|
||||||
class="form__button"
|
class="form__button"
|
||||||
:isDisabled="!isFormValid"
|
:isDisabled="!isFormValid"
|
||||||
:isLoading="loading"
|
:isLoading="loading"
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
v-model="confirmPassword"
|
v-model="confirmPassword"
|
||||||
/>
|
/>
|
||||||
<ui-button
|
<ui-button
|
||||||
|
:type="'submit'"
|
||||||
class="form__button"
|
class="form__button"
|
||||||
:isDisabled="!isFormValid"
|
:isDisabled="!isFormValid"
|
||||||
:isLoading="loading"
|
:isLoading="loading"
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@
|
||||||
v-model="confirmPassword"
|
v-model="confirmPassword"
|
||||||
/>
|
/>
|
||||||
<ui-button
|
<ui-button
|
||||||
|
:type="'submit'"
|
||||||
class="form__button"
|
class="form__button"
|
||||||
:isDisabled="!isFormValid"
|
:isDisabled="!isFormValid"
|
||||||
:isLoading="loading"
|
:isLoading="loading"
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
:inputMode="'email'"
|
:inputMode="'email'"
|
||||||
/>
|
/>
|
||||||
<ui-button
|
<ui-button
|
||||||
|
:type="'submit'"
|
||||||
class="form__button"
|
class="form__button"
|
||||||
:isDisabled="!isFormValid"
|
:isDisabled="!isFormValid"
|
||||||
:isLoading="loading"
|
:isLoading="loading"
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ui-button
|
<ui-button
|
||||||
|
:type="'submit'"
|
||||||
class="form__button"
|
class="form__button"
|
||||||
:isLoading="loading"
|
:isLoading="loading"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,39 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="brands">
|
<div class="brands">
|
||||||
<client-only>
|
<nuxt-marquee
|
||||||
<NuxtMarquee
|
class="brand__marquee"
|
||||||
class="brand__marquee"
|
id="marquee-slider"
|
||||||
id="marquee-slider"
|
:speed="50"
|
||||||
:speed="50"
|
:pauseOnHover="true"
|
||||||
:pauseOnHover="true"
|
>
|
||||||
|
<nuxt-link-locale
|
||||||
|
v-for="brand in brands"
|
||||||
|
:key="brand.node.uuid"
|
||||||
|
:to="`/brand/${brand.node.slug}`"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="brands__item"
|
class="brands__item"
|
||||||
v-for="brand in brands"
|
|
||||||
:key="brand.node.uuid"
|
|
||||||
|
|
||||||
>
|
>
|
||||||
<nuxt-link-locale
|
<nuxt-img
|
||||||
:to="`/brand/${brand.node.uuid}`"
|
densities="x1"
|
||||||
>
|
:src="brand.node.smallLogo"
|
||||||
<nuxt-img
|
:alt="brand.node.name"
|
||||||
densities="x1"
|
loading="lazy"
|
||||||
:src="brand.node.smallLogo"
|
class="brands__item-image"
|
||||||
:alt="brand.node.name"
|
/>
|
||||||
loading="lazy"
|
</div>
|
||||||
class="brands__item-image"
|
</nuxt-link-locale>
|
||||||
/>
|
</nuxt-marquee>
|
||||||
</nuxt-link-locale>
|
|
||||||
</div>
|
|
||||||
</NuxtMarquee>
|
|
||||||
</client-only>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {useBrands} from "~/composables/brands";
|
import type {IBrand} from "~/types";
|
||||||
|
|
||||||
const { brands } = await useBrands();
|
defineProps<{
|
||||||
|
brands: { node: IBrand }[]
|
||||||
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="tags">
|
<div
|
||||||
|
class="tags"
|
||||||
|
v-if="tags.length > 0"
|
||||||
|
>
|
||||||
<home-category-tags-block
|
<home-category-tags-block
|
||||||
v-for="tag in tags"
|
v-for="tag in tags"
|
||||||
:key="tag.node.uuid"
|
:key="tag.node.uuid"
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,16 @@
|
||||||
:key="tag.node.uuid"
|
:key="tag.node.uuid"
|
||||||
:tag="tag.node"
|
:tag="tag.node"
|
||||||
/>
|
/>
|
||||||
<home-collection-inner
|
<client-only>
|
||||||
v-if="newProducts.length > 0"
|
<home-collection-inner
|
||||||
:tag="newProductsTag"
|
v-if="newProducts.length > 0"
|
||||||
/>
|
:tag="newProductsTag"
|
||||||
<home-collection-inner
|
/>
|
||||||
v-if="priceProducts.length > 0"
|
<home-collection-inner
|
||||||
:tag="priceProductsTag"
|
v-if="priceProducts.length > 0"
|
||||||
/>
|
:tag="priceProductsTag"
|
||||||
|
/>
|
||||||
|
</client-only>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -24,20 +26,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {useProducts, useProductTags} from "@/composables/products";
|
import type {IProduct, IProductTag} from "~/types";
|
||||||
|
|
||||||
const { t } = useI18n()
|
const props = defineProps<{
|
||||||
|
tags: { node: IProductTag }[],
|
||||||
|
newProducts: { cursor: string; node: IProduct }[],
|
||||||
|
priceProducts: { cursor: string; node: IProduct }[]
|
||||||
|
}>()
|
||||||
|
|
||||||
const { tags } = await useProductTags();
|
const { t } = useI18n();
|
||||||
const {
|
|
||||||
products: newProducts,
|
|
||||||
getProducts: getNewProducts
|
|
||||||
} = await useProducts();
|
|
||||||
|
|
||||||
const {
|
|
||||||
products: priceProducts,
|
|
||||||
getProducts: getPriceProducts
|
|
||||||
} = await useProducts();
|
|
||||||
|
|
||||||
const newProductsTag = computed(() => {
|
const newProductsTag = computed(() => {
|
||||||
return {
|
return {
|
||||||
|
|
@ -45,7 +42,7 @@ const newProductsTag = computed(() => {
|
||||||
tagName: t('home.collection.newTag'),
|
tagName: t('home.collection.newTag'),
|
||||||
uuid: 'new-products',
|
uuid: 'new-products',
|
||||||
productSet: {
|
productSet: {
|
||||||
edges: newProducts.value
|
edges: props.newProducts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -56,19 +53,10 @@ const priceProductsTag = computed(() => {
|
||||||
tagName: t('home.collection.cheapTag'),
|
tagName: t('home.collection.cheapTag'),
|
||||||
uuid: 'price-products',
|
uuid: 'price-products',
|
||||||
productSet: {
|
productSet: {
|
||||||
edges: priceProducts.value
|
edges: props.priceProducts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
getNewProducts({
|
|
||||||
orderBy: '-modified'
|
|
||||||
}),
|
|
||||||
getPriceProducts({
|
|
||||||
orderBy: '-price'
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@
|
||||||
{{ t('store.filters.reset') }}
|
{{ t('store.filters.reset') }}
|
||||||
</button>
|
</button>
|
||||||
<ui-button
|
<ui-button
|
||||||
|
:type="'button'"
|
||||||
@click="onApply"
|
@click="onApply"
|
||||||
>
|
>
|
||||||
{{ t('store.filters.apply') }}
|
{{ t('store.filters.apply') }}
|
||||||
|
|
|
||||||
|
|
@ -10,28 +10,31 @@
|
||||||
<store-top
|
<store-top
|
||||||
v-model="orderBy"
|
v-model="orderBy"
|
||||||
@toggle-filter="onFilterToggle"
|
@toggle-filter="onFilterToggle"
|
||||||
|
:isFiltersVisible="filters.length > 0"
|
||||||
/>
|
/>
|
||||||
<div
|
<client-only>
|
||||||
class="store__list"
|
<div
|
||||||
:class="[
|
class="store__list"
|
||||||
|
:class="[
|
||||||
{ 'store__list-grid': productView === 'grid' },
|
{ 'store__list-grid': productView === 'grid' },
|
||||||
{ 'store__list-list': productView === 'list' }
|
{ 'store__list-list': productView === 'list' }
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<cards-product
|
<cards-product
|
||||||
v-if="products.length"
|
v-if="products.length && !pending"
|
||||||
v-for="product in products"
|
v-for="product in products"
|
||||||
:key="product.node.uuid"
|
:key="product.node.uuid"
|
||||||
:product="product.node"
|
:product="product.node"
|
||||||
:isList="productView === 'list'"
|
:isList="productView === 'list'"
|
||||||
/>
|
/>
|
||||||
<skeletons-cards-product
|
<skeletons-cards-product
|
||||||
v-if="pending"
|
v-if="pending"
|
||||||
v-for="idx in 12"
|
v-for="idx in 12"
|
||||||
:key="idx"
|
:key="idx"
|
||||||
:isList="productView === 'list'"
|
:isList="productView === 'list'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</client-only>
|
||||||
<div class="store__list-observer" ref="observer"></div>
|
<div class="store__list-observer" ref="observer"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -42,8 +45,11 @@ import {useRouteQuery} from "@vueuse/router";
|
||||||
import {useRouteParams} from "@vueuse/router";
|
import {useRouteParams} from "@vueuse/router";
|
||||||
import {useCategoryBySlug} from "~/composables/categories";
|
import {useCategoryBySlug} from "~/composables/categories";
|
||||||
import {useAppConfig} from '~/composables/config';
|
import {useAppConfig} from '~/composables/config';
|
||||||
|
import {useDefaultSeo} from "~/composables/seo";
|
||||||
|
|
||||||
const { COOKIES_PRODUCT_VIEW_KEY } = useAppConfig();
|
const { locale } = useI18n();
|
||||||
|
|
||||||
|
const { COOKIES_PRODUCT_VIEW_KEY, APP_NAME } = useAppConfig();
|
||||||
const productView = useCookie<string>(
|
const productView = useCookie<string>(
|
||||||
COOKIES_PRODUCT_VIEW_KEY as string,
|
COOKIES_PRODUCT_VIEW_KEY as string,
|
||||||
{
|
{
|
||||||
|
|
@ -59,7 +65,39 @@ const minPrice = useRouteQuery<number>('minPrice', 0);
|
||||||
const maxPrice = useRouteQuery<number>('maxPrice', 50000);
|
const maxPrice = useRouteQuery<number>('maxPrice', 50000);
|
||||||
const observer = ref(null);
|
const observer = ref(null);
|
||||||
|
|
||||||
const { category, filters } = await useCategoryBySlug(slug.value);
|
const { category, seoMeta, filters } = await useCategoryBySlug(slug.value);
|
||||||
|
|
||||||
|
const meta = useDefaultSeo(seoMeta.value || null);
|
||||||
|
|
||||||
|
if (meta) {
|
||||||
|
useSeoMeta({
|
||||||
|
title: meta.title || APP_NAME,
|
||||||
|
description: meta.description || meta.title || APP_NAME,
|
||||||
|
ogTitle: meta.og.title || undefined,
|
||||||
|
ogDescription: meta.og.description || meta.title || APP_NAME,
|
||||||
|
ogType: meta.og.type || undefined,
|
||||||
|
ogUrl: meta.og.url || undefined,
|
||||||
|
ogImage: meta.og.image || undefined,
|
||||||
|
twitterCard: meta.twitter.card || undefined,
|
||||||
|
twitterTitle: meta.twitter.title || undefined,
|
||||||
|
twitterDescription: meta.twitter.description || undefined,
|
||||||
|
robots: meta.robots,
|
||||||
|
});
|
||||||
|
|
||||||
|
useHead({
|
||||||
|
link: [
|
||||||
|
meta.canonical ? { rel: 'canonical', href: meta.canonical } : {},
|
||||||
|
].filter(Boolean) as any,
|
||||||
|
meta: [{ property: 'og:locale', content: locale.value }],
|
||||||
|
script: meta.jsonLd.map((obj: any) => ({
|
||||||
|
type: 'application/ld+json',
|
||||||
|
innerHTML: JSON.stringify(obj),
|
||||||
|
})),
|
||||||
|
__dangerouslyDisableSanitizersByTagID: Object.fromEntries(
|
||||||
|
meta.jsonLd.map((_, i: number) => [`ldjson-${i}`, ['innerHTML']])
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => category.value,
|
() => category.value,
|
||||||
|
|
@ -71,7 +109,7 @@ watch(
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
const { pending, products, pageInfo, variables } = await useStore(
|
const { pending, products, pageInfo, variables } = useStore(
|
||||||
slug.value,
|
slug.value,
|
||||||
attributes.value,
|
attributes.value,
|
||||||
orderBy.value,
|
orderBy.value,
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="top__filter">
|
<div class="top__filter" v-if="isFiltersVisible">
|
||||||
<button
|
<button
|
||||||
class="top__filter-button"
|
class="top__filter-button"
|
||||||
@click="$emit('toggle-filter')"
|
@click="$emit('toggle-filter')"
|
||||||
|
|
@ -53,6 +53,7 @@ import { useAppConfig } from '~/composables/config';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: string
|
modelValue: string
|
||||||
|
isFiltersVisible: boolean
|
||||||
}>()
|
}>()
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:modelValue', value: string): void
|
(e: 'update:modelValue', value: string): void
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
class="button"
|
class="button"
|
||||||
:disabled="isDisabled"
|
:disabled="isDisabled"
|
||||||
:class="[{active: isLoading}]"
|
:class="[{active: isLoading}]"
|
||||||
type="submit"
|
:type="type"
|
||||||
>
|
>
|
||||||
<ui-loader class="button__loader" v-if="isLoading" />
|
<ui-loader class="button__loader" v-if="isLoading" />
|
||||||
<slot v-else />
|
<slot v-else />
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
type: 'submit' | 'button',
|
||||||
isDisabled?: boolean,
|
isDisabled?: boolean,
|
||||||
isLoading?: boolean
|
isLoading?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,23 @@
|
||||||
<template>
|
<template>
|
||||||
<div @click="redirect" class="link">
|
<div
|
||||||
|
v-on="props.routePath ? { click: redirect } : {}"
|
||||||
|
class="link"
|
||||||
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
routePath: string
|
routePath?: string
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const redirect = () => {
|
const redirect = () => {
|
||||||
if (props.routePath) {
|
router.push({
|
||||||
router.push({
|
path: props.routePath
|
||||||
path: props.routePath
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,6 @@ export function useLogin() {
|
||||||
userStore.setUser(authData.user);
|
userStore.setUser(authData.user);
|
||||||
cookieAccess.value = authData.accessToken;
|
cookieAccess.value = authData.accessToken;
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
appStore.unsetActiveState();
|
appStore.unsetActiveState();
|
||||||
|
|
||||||
useNotification({
|
useNotification({
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,6 @@ export function useRefresh() {
|
||||||
// });
|
// });
|
||||||
// await usePromocodes();
|
// await usePromocodes();
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
await useUserBaseData(data.user.email);
|
await useUserBaseData(data.user.email);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
export * from './useBrands';
|
export * from './useBrands';
|
||||||
export * from './useBrandByUuid';
|
export * from './useBrandBySlug';
|
||||||
22
storefront/composables/brands/useBrandBySlug.ts
Normal file
22
storefront/composables/brands/useBrandBySlug.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import {GET_BRAND_BY_SLUG} from "~/graphql/queries/standalone/brands";
|
||||||
|
import type {IBrandsResponse} from "~/types";
|
||||||
|
|
||||||
|
export async function useBrandBySlug(slug: string) {
|
||||||
|
const brand = computed(() => data.value?.brands.edges[0]?.node ?? null);
|
||||||
|
|
||||||
|
const { data, error } = await useAsyncQuery<IBrandsResponse>(
|
||||||
|
GET_BRAND_BY_SLUG,
|
||||||
|
{ slug }
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(error, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('useBrandsBySlug error:', err)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
brand,
|
||||||
|
seoMeta: computed(() => brand.value?.seoMeta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
import {GET_BRAND_BY_UUID} from "~/graphql/queries/standalone/brands";
|
|
||||||
import type {IBrandsResponse} from "~/types";
|
|
||||||
|
|
||||||
export async function useBrandByUuid(uuid: string) {
|
|
||||||
const brand = computed(() => data.value?.brands.edges[0].node ?? []);
|
|
||||||
|
|
||||||
const { data, error } = await useAsyncQuery<IBrandsResponse>(
|
|
||||||
GET_BRAND_BY_UUID,
|
|
||||||
{ uuid }
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(error, (err) => {
|
|
||||||
if (err) {
|
|
||||||
console.error('useBrandsByUuid error:', err)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
brand
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import {GET_BRANDS} from "~/graphql/queries/standalone/brands";
|
import {GET_BRANDS} from "~/graphql/queries/standalone/brands";
|
||||||
import type {IBrandsResponse} from "~/types";
|
import type {IBrandsResponse} from "~/types";
|
||||||
|
|
||||||
export async function useBrands() {
|
export function useBrands() {
|
||||||
const brands = computed(() => data.value?.brands.edges ?? []);
|
const { data, error } = useAsyncQuery<IBrandsResponse>(
|
||||||
|
|
||||||
const { data, error } = await useAsyncQuery<IBrandsResponse>(
|
|
||||||
GET_BRANDS
|
GET_BRANDS
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const brands = computed(() => data.value?.brands.edges ?? []);
|
||||||
|
|
||||||
watch(error, (err) => {
|
watch(error, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('useBrands error:', err)
|
console.error('useBrands error:', err)
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ export async function useCategoryBySlug(slug: string) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
category,
|
category,
|
||||||
|
seoMeta: computed(() => category.value?.seoMeta),
|
||||||
filters
|
filters
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
21
storefront/composables/categories/useCategoryBySlugSeo.ts
Normal file
21
storefront/composables/categories/useCategoryBySlugSeo.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import type {ICategoryBySlugSeoResponse} from '~/types';
|
||||||
|
import {GET_CATEGORY_BY_SLUG_SEO} from "~/graphql/queries/standalone/categories";
|
||||||
|
|
||||||
|
export async function useCategoryBySlugSeo(slug: string) {
|
||||||
|
const category = computed(() => data.value?.categories.edges[0]?.node ?? null);
|
||||||
|
|
||||||
|
const { data, error } = await useAsyncQuery<ICategoryBySlugSeoResponse>(
|
||||||
|
GET_CATEGORY_BY_SLUG_SEO,
|
||||||
|
{ categorySlug: slug }
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(error, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('useCategoryBySlugSeo error:', err)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
seoMeta: computed(() => category.value?.seoMeta)
|
||||||
|
};
|
||||||
|
}
|
||||||
1
storefront/composables/posts/index.ts
Normal file
1
storefront/composables/posts/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './usePostBySlug'
|
||||||
38
storefront/composables/posts/usePostBySlug.ts
Normal file
38
storefront/composables/posts/usePostBySlug.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import type {IPostResponse} from '~/types';
|
||||||
|
import {GET_POST_BY_SLUG} from "~/graphql/queries/standalone/blog";
|
||||||
|
|
||||||
|
export async function usePostBySlug(slug: string) {
|
||||||
|
const { locale } = useI18n();
|
||||||
|
|
||||||
|
const variables = reactive({
|
||||||
|
slug: slug,
|
||||||
|
first: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data, error, refresh } = await useAsyncQuery<IPostResponse>(
|
||||||
|
GET_POST_BY_SLUG,
|
||||||
|
variables
|
||||||
|
);
|
||||||
|
|
||||||
|
const post = computed(() => data.value?.posts.edges[0]?.node ?? null);
|
||||||
|
|
||||||
|
watch(locale, async () => {
|
||||||
|
if (variables.first >= 100) {
|
||||||
|
variables.first = 0;
|
||||||
|
} else {
|
||||||
|
variables.first += 1;
|
||||||
|
}
|
||||||
|
await refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(error, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('usePostBySlug error:', err)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
post,
|
||||||
|
refresh
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@ export async function useProductBySlug(slug: string) {
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
product
|
product,
|
||||||
|
seoMeta: computed(() => product.value?.seoMeta)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import {GET_PRODUCT_TAGS} from "~/graphql/queries/standalone/products.js";
|
import {GET_PRODUCT_TAGS} from "~/graphql/queries/standalone/products.js";
|
||||||
import type {IProductTagsResponse} from "~/types";
|
import type {IProductTagsResponse} from "~/types";
|
||||||
|
|
||||||
export async function useProductTags() {
|
export function useProductTags() {
|
||||||
const tags = computed(() => data.value?.productTags?.edges ?? []);
|
const { data, error } = useAsyncQuery<IProductTagsResponse>(
|
||||||
|
|
||||||
const { data, error } = await useAsyncQuery<IProductTagsResponse>(
|
|
||||||
GET_PRODUCT_TAGS
|
GET_PRODUCT_TAGS
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const tags = computed(() => data.value?.productTags?.edges ?? []);
|
||||||
|
|
||||||
watch(error, (err) => {
|
watch(error, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('useProductTags error:', err)
|
console.error('useProductTags error:', err)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
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 async function useProducts() {
|
export function useProducts() {
|
||||||
const variables = ref({ first: 12 });
|
const variables = ref({ first: 12 });
|
||||||
|
|
||||||
const { data, error, refresh } = await useAsyncQuery<IProductResponse>(
|
const { data, error, refresh } = useAsyncQuery<IProductResponse>(
|
||||||
GET_PRODUCTS,
|
GET_PRODUCTS,
|
||||||
variables
|
variables
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import type {IPromocodesResponse} from "~/types";
|
||||||
import {GET_PROMOCODES} from "~/graphql/queries/standalone/promocodes";
|
import {GET_PROMOCODES} from "~/graphql/queries/standalone/promocodes";
|
||||||
|
|
||||||
export async function usePromocodes () {
|
export async function usePromocodes () {
|
||||||
const promocodesStore = usePromocodeStore();
|
const promocodeStore = usePromocodeStore();
|
||||||
|
|
||||||
const { data, error } = await useAsyncQuery<IPromocodesResponse>(
|
const { data, error } = await useAsyncQuery<IPromocodesResponse>(
|
||||||
GET_PROMOCODES
|
GET_PROMOCODES
|
||||||
|
|
@ -10,7 +10,7 @@ export async function usePromocodes () {
|
||||||
console.log(data.value)
|
console.log(data.value)
|
||||||
|
|
||||||
if (!error.value && data.value?.promocodes.edges) {
|
if (!error.value && data.value?.promocodes.edges) {
|
||||||
promocodesStore.setPromocodes(data.value.promocodes.edges);
|
promocodeStore.setPromocodes(data.value.promocodes.edges);
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(error, (err) => {
|
watch(error, (err) => {
|
||||||
|
|
|
||||||
1
storefront/composables/seo/index.ts
Normal file
1
storefront/composables/seo/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './useDefaultSeo';
|
||||||
27
storefront/composables/seo/useDefaultSeo.ts
Normal file
27
storefront/composables/seo/useDefaultSeo.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import type {ISEOMeta} from "~/types";
|
||||||
|
|
||||||
|
export function useDefaultSeo (meta: ISEOMeta | null) {
|
||||||
|
if (meta === null) return meta
|
||||||
|
|
||||||
|
const norm = (s?: string) => (s ?? '').toString().trim()
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: norm(meta.title),
|
||||||
|
description: norm(meta.description),
|
||||||
|
canonical: norm(meta.canonical),
|
||||||
|
robots: norm(meta.robots || 'index,follow'),
|
||||||
|
og: {
|
||||||
|
title: norm(meta.openGraph?.title || meta.title),
|
||||||
|
description: norm(meta.openGraph?.description || meta.description),
|
||||||
|
type: norm(meta.openGraph?.type || 'product'),
|
||||||
|
url: norm(meta.openGraph?.url || meta.canonical),
|
||||||
|
image: norm(meta.openGraph?.image),
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: norm(meta.twitter?.card || 'summary_large_image'),
|
||||||
|
title: norm(meta.twitter?.title || meta.title),
|
||||||
|
description: norm(meta.twitter?.description || meta.description),
|
||||||
|
},
|
||||||
|
jsonLd: Array.isArray(meta.jsonLd) ? meta.jsonLd : [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,7 @@ interface IProdVars {
|
||||||
productAfter?: string
|
productAfter?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function useStore(
|
export function useStore(
|
||||||
slug: string,
|
slug: string,
|
||||||
attributes?: string,
|
attributes?: string,
|
||||||
orderBy?: string,
|
orderBy?: string,
|
||||||
|
|
@ -29,12 +29,12 @@ export async function useStore(
|
||||||
productAfter
|
productAfter
|
||||||
});
|
});
|
||||||
|
|
||||||
const { pending, data, error, refresh } = await useAsyncQuery<IProductResponse>(
|
const { pending, data, error, refresh } = useAsyncQuery<IProductResponse>(
|
||||||
GET_PRODUCTS,
|
GET_PRODUCTS,
|
||||||
variables
|
variables
|
||||||
);
|
);
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
watch(error, e => e && console.error('useStore products error', e));
|
watch(error, e => e && console.error('useStore products error', e));
|
||||||
|
|
@ -45,7 +45,7 @@ export async function useStore(
|
||||||
if (!newCursor || newCursor === oldCursor) return;
|
if (!newCursor || newCursor === oldCursor) return;
|
||||||
await refresh();
|
await refresh();
|
||||||
const newEdges = data.value?.products.edges ?? [];
|
const newEdges = data.value?.products.edges ?? [];
|
||||||
products.value.push(...newEdges);
|
products.value = [...products.value, ...newEdges];
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -59,7 +59,7 @@ export async function useStore(
|
||||||
async () => {
|
async () => {
|
||||||
variables.productAfter = '';
|
variables.productAfter = '';
|
||||||
await refresh();
|
await refresh();
|
||||||
products.value = data.value?.products.edges ?? [];
|
products.value = [...(data.value?.products.edges ?? [])];
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,11 @@ export function useDeposit() {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result?.data?.deposit) {
|
if (result?.data?.deposit) {
|
||||||
window.open(result?.data.deposit.transaction.process.url)
|
if (result?.data.deposit?.transaction?.process?.url) {
|
||||||
|
window.location.href = result?.data.deposit.transaction.process.url
|
||||||
|
} else {
|
||||||
|
console.log(result?.data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import {orderStatuses} from "~/config/constants";
|
||||||
export async function useUserBaseData(userEmail: string) {
|
export async function useUserBaseData(userEmail: string) {
|
||||||
const wishlistStore = useWishlistStore();
|
const wishlistStore = useWishlistStore();
|
||||||
const cartStore = useCartStore();
|
const cartStore = useCartStore();
|
||||||
const promocodesStore = usePromocodeStore();
|
const promocodeStore = usePromocodeStore();
|
||||||
|
|
||||||
const { document, variables } = getUserBaseData(
|
const { document, variables } = getUserBaseData(
|
||||||
{
|
{
|
||||||
|
|
@ -14,19 +14,19 @@ export async function useUserBaseData(userEmail: string) {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data, error } = await useAsyncQuery<IUserBaseDataResponse>(
|
const { mutate, error } = useMutation<IUserBaseDataResponse>(document);
|
||||||
document,
|
|
||||||
variables
|
|
||||||
);
|
|
||||||
|
|
||||||
if (data.value?.wishlists.edges) {
|
const result = await mutate(variables);
|
||||||
wishlistStore.setWishlist(data.value.wishlists.edges[0].node);
|
const data = result?.data;
|
||||||
|
|
||||||
|
if (data?.wishlists.edges) {
|
||||||
|
wishlistStore.setWishlist(data.wishlists.edges[0].node);
|
||||||
}
|
}
|
||||||
if (data.value?.orders.edges) {
|
if (data?.orders.edges) {
|
||||||
cartStore.setCurrentOrders(data.value?.orders.edges[0].node);
|
cartStore.setCurrentOrders(data.orders.edges[0].node);
|
||||||
}
|
}
|
||||||
if (data.value?.promocodes.edges) {
|
if (data?.promocodes.edges) {
|
||||||
promocodesStore.setPromocodes(data.value.promocodes.edges);
|
promocodeStore.setPromocodes(data.promocodes.edges);
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(error, (err) => {
|
watch(error, (err) => {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
export const BRAND_FRAGMENT = gql`
|
export const BRAND_FRAGMENT = gql`
|
||||||
fragment Brand on BrandType {
|
fragment Brand on BrandType {
|
||||||
uuid
|
uuid
|
||||||
|
slug
|
||||||
name
|
name
|
||||||
smallLogo
|
smallLogo
|
||||||
|
bigLogo
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
12
storefront/graphql/fragments/seometa.fragment.ts
Normal file
12
storefront/graphql/fragments/seometa.fragment.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
export const SEOMETA_FRAGMENT = gql`
|
||||||
|
fragment SEOMeta on SEOMetaType {
|
||||||
|
canonical
|
||||||
|
description
|
||||||
|
hreflang
|
||||||
|
jsonLd
|
||||||
|
openGraph
|
||||||
|
robots
|
||||||
|
title
|
||||||
|
twitter
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import {ORDER_FRAGMENT} from "@/graphql/fragments/orders.fragment.js";
|
import {ORDER_FRAGMENT} from "@/graphql/fragments/orders.fragment";
|
||||||
|
|
||||||
export const ADD_TO_CART = gql`
|
export const ADD_TO_CART = gql`
|
||||||
mutation addToCart(
|
mutation addToCart(
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
export const DEPOSIT = gql`
|
export const DEPOSIT = gql`
|
||||||
mutation deposit(
|
mutation deposit(
|
||||||
$amount: Number!
|
$amount: Float!
|
||||||
) {
|
) {
|
||||||
contactUs(
|
deposit(
|
||||||
amount: $amount,
|
amount: $amount,
|
||||||
) {
|
) {
|
||||||
error
|
transaction {
|
||||||
received
|
process
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import {WISHLIST_FRAGMENT} from "@/graphql/fragments/wishlist.fragment.js";
|
import {WISHLIST_FRAGMENT} from "@/graphql/fragments/wishlist.fragment";
|
||||||
|
|
||||||
export const ADD_TO_WISHLIST = gql`
|
export const ADD_TO_WISHLIST = gql`
|
||||||
mutation addToWishlist(
|
mutation addToWishlist(
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,12 @@ export const GET_POSTS = gql`
|
||||||
|
|
||||||
export const GET_POST_BY_SLUG = gql`
|
export const GET_POST_BY_SLUG = gql`
|
||||||
query getPostBySlug(
|
query getPostBySlug(
|
||||||
$slug: String!
|
$slug: String!,
|
||||||
|
$first: Int
|
||||||
) {
|
) {
|
||||||
posts(
|
posts(
|
||||||
slug: $slug
|
slug: $slug,
|
||||||
|
first: $first,
|
||||||
) {
|
) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import {BRAND_FRAGMENT} from "@/graphql/fragments/brands.fragment.js";
|
import {BRAND_FRAGMENT} from "@/graphql/fragments/brands.fragment";
|
||||||
import {CATEGORY_FRAGMENT} from "@/graphql/fragments/categories.fragment.js";
|
import {CATEGORY_FRAGMENT} from "@/graphql/fragments/categories.fragment";
|
||||||
|
import {SEOMETA_FRAGMENT} from "@/graphql/fragments/seometa.fragment";
|
||||||
|
|
||||||
export const GET_BRANDS = gql`
|
export const GET_BRANDS = gql`
|
||||||
query getBrands (
|
query getBrands (
|
||||||
|
|
@ -18,12 +19,12 @@ export const GET_BRANDS = gql`
|
||||||
${BRAND_FRAGMENT}
|
${BRAND_FRAGMENT}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const GET_BRAND_BY_UUID = gql`
|
export const GET_BRAND_BY_SLUG = gql`
|
||||||
query getBrandbyUuid(
|
query getBrandBySlug(
|
||||||
$uuid: UUID!
|
$slug: String!
|
||||||
) {
|
) {
|
||||||
brands(
|
brands(
|
||||||
uuid: $uuid
|
slug: $slug
|
||||||
) {
|
) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
|
|
@ -31,10 +32,14 @@ export const GET_BRAND_BY_UUID = gql`
|
||||||
categories {
|
categories {
|
||||||
...Category
|
...Category
|
||||||
}
|
}
|
||||||
|
seoMeta {
|
||||||
|
...SEOMeta
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
${BRAND_FRAGMENT}
|
${BRAND_FRAGMENT}
|
||||||
${CATEGORY_FRAGMENT}
|
${CATEGORY_FRAGMENT}
|
||||||
|
${SEOMETA_FRAGMENT}
|
||||||
`
|
`
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import {CATEGORY_FRAGMENT} from "~/graphql/fragments/categories.fragment.js";
|
import {CATEGORY_FRAGMENT} from "~/graphql/fragments/categories.fragment";
|
||||||
|
import {SEOMETA_FRAGMENT} from "~/graphql/fragments/seometa.fragment";
|
||||||
|
|
||||||
export const GET_CATEGORIES = gql`
|
export const GET_CATEGORIES = gql`
|
||||||
query getCategories (
|
query getCategories (
|
||||||
|
|
@ -55,11 +56,34 @@ export const GET_CATEGORY_BY_SLUG = gql`
|
||||||
maxPrice
|
maxPrice
|
||||||
minPrice
|
minPrice
|
||||||
}
|
}
|
||||||
|
seoMeta {
|
||||||
|
...SEOMeta
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
${CATEGORY_FRAGMENT}
|
${CATEGORY_FRAGMENT}
|
||||||
|
${SEOMETA_FRAGMENT}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const GET_CATEGORY_BY_SLUG_SEO = gql`
|
||||||
|
query getCategoryBySlug(
|
||||||
|
$categorySlug: String!
|
||||||
|
) {
|
||||||
|
categories(
|
||||||
|
slug: $categorySlug
|
||||||
|
) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
seoMeta {
|
||||||
|
...SEOMeta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${SEOMETA_FRAGMENT}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const GET_CATEGORY_TAGS = gql`
|
export const GET_CATEGORY_TAGS = gql`
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import {ORDER_FRAGMENT} from "@/graphql/fragments/orders.fragment.js";
|
import {ORDER_FRAGMENT} from "@/graphql/fragments/orders.fragment";
|
||||||
|
|
||||||
export const GET_ORDERS = gql`
|
export const GET_ORDERS = gql`
|
||||||
query getOrders(
|
query getOrders(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import {PRODUCT_FRAGMENT} from "@/graphql/fragments/products.fragment.js";
|
import {PRODUCT_FRAGMENT} from "@/graphql/fragments/products.fragment";
|
||||||
|
import {SEOMETA_FRAGMENT} from "~/graphql/fragments/seometa.fragment";
|
||||||
|
|
||||||
export const GET_PRODUCTS = gql`
|
export const GET_PRODUCTS = gql`
|
||||||
query getProducts(
|
query getProducts(
|
||||||
|
|
@ -46,11 +47,15 @@ export const GET_PRODUCT_BY_SLUG = gql`
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
...Product
|
...Product
|
||||||
|
seoMeta {
|
||||||
|
...SEOMeta
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
${PRODUCT_FRAGMENT}
|
${PRODUCT_FRAGMENT}
|
||||||
|
${SEOMETA_FRAGMENT}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const GET_PRODUCT_TAGS = gql`
|
export const GET_PRODUCT_TAGS = gql`
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import {WISHLIST_FRAGMENT} from "@/graphql/fragments/wishlist.fragment.js";
|
import {WISHLIST_FRAGMENT} from "@/graphql/fragments/wishlist.fragment";
|
||||||
|
|
||||||
export const GET_WISHLIST = gql`
|
export const GET_WISHLIST = gql`
|
||||||
query getWishlist {
|
query getWishlist {
|
||||||
|
|
|
||||||
|
|
@ -203,5 +203,8 @@
|
||||||
"empty": "You don't have any promocodes."
|
"empty": "You don't have any promocodes."
|
||||||
},
|
},
|
||||||
"logout": "Logout"
|
"logout": "Logout"
|
||||||
|
},
|
||||||
|
"catalog": {
|
||||||
|
"title": "Catalog"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
270
storefront/package-lock.json
generated
270
storefront/package-lock.json
generated
|
|
@ -1439,17 +1439,6 @@
|
||||||
"integrity": "sha512-4wMPu9iN3/HL97QblBsBay3E1etIciR84izI3U+4iALY+JHCrI+a2jO0qbAZ/nxKoegypYEaiiqWXylm+/zfrw==",
|
"integrity": "sha512-4wMPu9iN3/HL97QblBsBay3E1etIciR84izI3U+4iALY+JHCrI+a2jO0qbAZ/nxKoegypYEaiiqWXylm+/zfrw==",
|
||||||
"license": "Apache 2"
|
"license": "Apache 2"
|
||||||
},
|
},
|
||||||
"node_modules/@netlify/blobs": {
|
|
||||||
"version": "8.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@netlify/blobs/-/blobs-8.2.0.tgz",
|
|
||||||
"integrity": "sha512-9djLZHBKsoKk8XCgwWSEPK9QnT8qqxEQGuYh48gFIcNLvpBKkLnHbDZuyUxmNemCfDz7h0HnMXgSPnnUVgARhg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": "^14.16.0 || >=16.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@netlify/dev-utils": {
|
"node_modules/@netlify/dev-utils": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@netlify/dev-utils/-/dev-utils-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@netlify/dev-utils/-/dev-utils-2.2.0.tgz",
|
||||||
|
|
@ -4351,14 +4340,6 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/change-case": {
|
|
||||||
"version": "5.4.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz",
|
|
||||||
"integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||||
|
|
@ -7876,257 +7857,6 @@
|
||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss": {
|
|
||||||
"version": "1.30.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
|
|
||||||
"integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"detect-libc": "^2.0.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"lightningcss-darwin-arm64": "1.30.1",
|
|
||||||
"lightningcss-darwin-x64": "1.30.1",
|
|
||||||
"lightningcss-freebsd-x64": "1.30.1",
|
|
||||||
"lightningcss-linux-arm-gnueabihf": "1.30.1",
|
|
||||||
"lightningcss-linux-arm64-gnu": "1.30.1",
|
|
||||||
"lightningcss-linux-arm64-musl": "1.30.1",
|
|
||||||
"lightningcss-linux-x64-gnu": "1.30.1",
|
|
||||||
"lightningcss-linux-x64-musl": "1.30.1",
|
|
||||||
"lightningcss-win32-arm64-msvc": "1.30.1",
|
|
||||||
"lightningcss-win32-x64-msvc": "1.30.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lightningcss-darwin-arm64": {
|
|
||||||
"version": "1.30.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz",
|
|
||||||
"integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lightningcss-darwin-x64": {
|
|
||||||
"version": "1.30.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz",
|
|
||||||
"integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lightningcss-freebsd-x64": {
|
|
||||||
"version": "1.30.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz",
|
|
||||||
"integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"freebsd"
|
|
||||||
],
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
|
||||||
"version": "1.30.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz",
|
|
||||||
"integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==",
|
|
||||||
"cpu": [
|
|
||||||
"arm"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lightningcss-linux-arm64-gnu": {
|
|
||||||
"version": "1.30.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz",
|
|
||||||
"integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lightningcss-linux-arm64-musl": {
|
|
||||||
"version": "1.30.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz",
|
|
||||||
"integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lightningcss-linux-x64-gnu": {
|
|
||||||
"version": "1.30.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz",
|
|
||||||
"integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lightningcss-linux-x64-musl": {
|
|
||||||
"version": "1.30.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz",
|
|
||||||
"integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lightningcss-win32-arm64-msvc": {
|
|
||||||
"version": "1.30.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz",
|
|
||||||
"integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lightningcss-win32-x64-msvc": {
|
|
||||||
"version": "1.30.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz",
|
|
||||||
"integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lightningcss/node_modules/detect-libc": {
|
|
||||||
"version": "2.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
|
||||||
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lilconfig": {
|
"node_modules/lilconfig": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||||
|
|
|
||||||
76
storefront/pages/brand/[slug].vue
Normal file
76
storefront/pages/brand/[slug].vue
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
<template>
|
||||||
|
<div class="brand">
|
||||||
|
<ui-title>{{ brand?.name }}</ui-title>
|
||||||
|
<div class="brand__categories">
|
||||||
|
<cards-category
|
||||||
|
v-for="category in brand?.categories"
|
||||||
|
:key="category.uuid"
|
||||||
|
:category="category"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {useBrandBySlug} from "~/composables/brands";
|
||||||
|
import {usePageTitle} from "~/composables/utils";
|
||||||
|
import {useDefaultSeo} from "~/composables/seo/index.js";
|
||||||
|
import {useRouteParams} from "@vueuse/router";
|
||||||
|
import {useAppConfig} from "~/composables/config";
|
||||||
|
|
||||||
|
const { locale } = useI18n();
|
||||||
|
|
||||||
|
const slug = useRouteParams<string>('slug');
|
||||||
|
|
||||||
|
const { APP_NAME } = useAppConfig();
|
||||||
|
const { setPageTitle } = usePageTitle();
|
||||||
|
|
||||||
|
const { brand, seoMeta } = await useBrandBySlug(slug.value);
|
||||||
|
|
||||||
|
const meta = useDefaultSeo(seoMeta.value || null);
|
||||||
|
|
||||||
|
if (meta) {
|
||||||
|
useSeoMeta({
|
||||||
|
title: meta.title || APP_NAME,
|
||||||
|
description: meta.description || meta.title || APP_NAME,
|
||||||
|
ogTitle: meta.og.title || undefined,
|
||||||
|
ogDescription: meta.og.description || meta.title || APP_NAME,
|
||||||
|
ogType: meta.og.type || undefined,
|
||||||
|
ogUrl: meta.og.url || undefined,
|
||||||
|
ogImage: meta.og.image || undefined,
|
||||||
|
twitterCard: meta.twitter.card || undefined,
|
||||||
|
twitterTitle: meta.twitter.title || undefined,
|
||||||
|
twitterDescription: meta.twitter.description || undefined,
|
||||||
|
robots: meta.robots,
|
||||||
|
});
|
||||||
|
|
||||||
|
useHead({
|
||||||
|
link: [
|
||||||
|
meta.canonical ? { rel: 'canonical', href: meta.canonical } : {},
|
||||||
|
].filter(Boolean) as any,
|
||||||
|
meta: [{ property: 'og:locale', content: locale.value }],
|
||||||
|
script: meta.jsonLd.map((obj: any) => ({
|
||||||
|
type: 'application/ld+json',
|
||||||
|
innerHTML: JSON.stringify(obj),
|
||||||
|
})),
|
||||||
|
__dangerouslyDisableSanitizersByTagID: Object.fromEntries(
|
||||||
|
meta.jsonLd.map((_, i: number) => [`ldjson-${i}`, ['innerHTML']])
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setPageTitle(brand.value?.name ?? 'Brand');
|
||||||
|
// TODO: add product by this brand
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.brand {
|
||||||
|
&__categories {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, 275px);
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="brand">
|
|
||||||
<ui-title>{{ brand.name }}</ui-title>
|
|
||||||
<div class="brand__categories">
|
|
||||||
<cards-category
|
|
||||||
v-for="category in brand.categories"
|
|
||||||
:key="category.uuid"
|
|
||||||
:category="category"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {useBrandByUuid} from "~/composables/brands";
|
|
||||||
import {usePageTitle} from "~/composables/utils";
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
const slug = computed(() => route.params.uuid)
|
|
||||||
|
|
||||||
const { setPageTitle } = usePageTitle();
|
|
||||||
|
|
||||||
const { brand } = await useBrandByUuid(slug.value);
|
|
||||||
|
|
||||||
setPageTitle(brand.value?.name ?? 'Brand');
|
|
||||||
// TODO: add product by this brand
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.brand {
|
|
||||||
&__categories {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, 275px);
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 50px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -19,25 +19,64 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type {ICategory} from "~/types";
|
import type {ICategory} from "~/types";
|
||||||
import {usePageTitle} from "~/composables/utils";
|
import {usePageTitle} from "~/composables/utils";
|
||||||
|
import {useRouteParams} from "@vueuse/router";
|
||||||
|
import {useDefaultSeo} from "~/composables/seo";
|
||||||
|
import {useCategoryBySlugSeo} from "~/composables/categories/useCategoryBySlugSeo";
|
||||||
|
import {useAppConfig} from "~/composables/config";
|
||||||
|
|
||||||
const route = useRoute()
|
const { locale } = useI18n();
|
||||||
const categoryStore = useCategoryStore()
|
const categoryStore = useCategoryStore();
|
||||||
|
|
||||||
const { setPageTitle } = usePageTitle()
|
const { APP_NAME } = useAppConfig();
|
||||||
|
const { setPageTitle } = usePageTitle();
|
||||||
|
|
||||||
const slug = computed(() => route.params.slug as string)
|
const slug = useRouteParams<string>('slug');
|
||||||
const roots = computed(() => categoryStore.categories.map(e => e.node))
|
const roots = computed(() => categoryStore.categories.map(e => e.node));
|
||||||
|
|
||||||
const category = computed(() => findBySlug(roots.value, slug.value))
|
const category = computed(() => findBySlug(roots.value, slug.value));
|
||||||
|
|
||||||
setPageTitle(category.value?.name ?? 'Category')
|
const seoMeta = useCategoryBySlugSeo(category.value.slug)
|
||||||
|
|
||||||
|
const meta = useDefaultSeo(seoMeta || null);
|
||||||
|
|
||||||
|
if (meta) {
|
||||||
|
useSeoMeta({
|
||||||
|
title: meta.title || APP_NAME,
|
||||||
|
description: meta.description || meta.title || APP_NAME,
|
||||||
|
ogTitle: meta.og.title || undefined,
|
||||||
|
ogDescription: meta.og.description || meta.title || APP_NAME,
|
||||||
|
ogType: meta.og.type || undefined,
|
||||||
|
ogUrl: meta.og.url || undefined,
|
||||||
|
ogImage: meta.og.image || undefined,
|
||||||
|
twitterCard: meta.twitter.card || undefined,
|
||||||
|
twitterTitle: meta.twitter.title || undefined,
|
||||||
|
twitterDescription: meta.twitter.description || undefined,
|
||||||
|
robots: meta.robots,
|
||||||
|
});
|
||||||
|
|
||||||
|
useHead({
|
||||||
|
link: [
|
||||||
|
meta.canonical ? { rel: 'canonical', href: meta.canonical } : {},
|
||||||
|
].filter(Boolean) as any,
|
||||||
|
meta: [{ property: 'og:locale', content: locale.value }],
|
||||||
|
script: meta.jsonLd.map((obj: any) => ({
|
||||||
|
type: 'application/ld+json',
|
||||||
|
innerHTML: JSON.stringify(obj),
|
||||||
|
})),
|
||||||
|
__dangerouslyDisableSanitizersByTagID: Object.fromEntries(
|
||||||
|
meta.jsonLd.map((_, i: number) => [`ldjson-${i}`, ['innerHTML']])
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setPageTitle(category.value?.name ?? 'Category');
|
||||||
|
|
||||||
function findBySlug(nodes: ICategory[], slug: string): ICategory | undefined {
|
function findBySlug(nodes: ICategory[], slug: string): ICategory | undefined {
|
||||||
for (const n of nodes) {
|
for (const n of nodes) {
|
||||||
if (n.slug === slug) return n
|
if (n.slug === slug) return n;
|
||||||
if (n.children?.length) {
|
if (n.children?.length) {
|
||||||
const found = findBySlug(n.children, slug)
|
const found = findBySlug(n.children, slug);
|
||||||
if (found) return found
|
if (found) return found;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="home">
|
<div class="home">
|
||||||
<home-hero />
|
<home-hero />
|
||||||
<home-brands />
|
<home-brands :brands="brands" />
|
||||||
<home-collection />
|
<home-collection
|
||||||
|
:tags="tags"
|
||||||
|
:newProducts="newProducts"
|
||||||
|
:priceProducts="priceProducts"
|
||||||
|
/>
|
||||||
<home-category-tags />
|
<home-category-tags />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -10,6 +14,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {useUserActivation} from "~/composables/user";
|
import {useUserActivation} from "~/composables/user";
|
||||||
import { useRouteQuery } from '@vueuse/router';
|
import { useRouteQuery } from '@vueuse/router';
|
||||||
|
import {useBrands} from "~/composables/brands";
|
||||||
|
import {useProducts, useProductTags} from "~/composables/products";
|
||||||
|
import type {IProduct} from "~/types";
|
||||||
|
|
||||||
const {t} = useI18n();
|
const {t} = useI18n();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
|
@ -25,6 +32,18 @@ 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 { tags } = useProductTags();
|
||||||
|
const { products, getProducts } = useProducts();
|
||||||
|
|
||||||
|
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) {
|
||||||
await activateUser(token.value, uid.value);
|
await activateUser(token.value, uid.value);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="product" v-if="product">
|
<div class="product">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="product__wrapper">
|
<div class="product__wrapper">
|
||||||
<h1 class="product__title">{{ product.name }}</h1>
|
<h1 class="product__title">{{ product?.name }}</h1>
|
||||||
<div class="product__block">
|
<div class="product__block">
|
||||||
<div class="product__images">
|
<div class="product__images">
|
||||||
<div class="product__images-gallery">
|
<div class="product__images-gallery">
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
>
|
>
|
||||||
<nuxt-img
|
<nuxt-img
|
||||||
:src="image"
|
:src="image"
|
||||||
:alt="product.name"
|
:alt="product?.name"
|
||||||
format="webp"
|
format="webp"
|
||||||
densities="x1"
|
densities="x1"
|
||||||
/>
|
/>
|
||||||
|
|
@ -22,28 +22,31 @@
|
||||||
</div>
|
</div>
|
||||||
<nuxt-img
|
<nuxt-img
|
||||||
:src="selectedImage"
|
:src="selectedImage"
|
||||||
:alt="product.name"
|
:alt="product?.name"
|
||||||
class="product__images-main"
|
class="product__images-main"
|
||||||
format="webp"
|
format="webp"
|
||||||
densities="x1"
|
densities="x1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="product__center">
|
<div class="product__center">
|
||||||
<p class="product__center-description">{{ product.description }}</p>
|
<p class="product__center-description">{{ product?.description }}</p>
|
||||||
<p
|
<client-only>
|
||||||
class="product__center-characteristic"
|
<p
|
||||||
@click="scrollTo('characteristics')"
|
v-if="attributes.length"
|
||||||
>
|
class="product__center-characteristic"
|
||||||
{{ t('product.characteristics') }}
|
@click="scrollTo('characteristics')"
|
||||||
</p>
|
>
|
||||||
|
{{ t('product.characteristics') }}
|
||||||
|
</p>
|
||||||
|
</client-only>
|
||||||
</div>
|
</div>
|
||||||
<div class="product__info">
|
<div class="product__info">
|
||||||
<div class="product__info-inner">
|
<div class="product__info-inner">
|
||||||
<div class="product__info-top">
|
<div class="product__info-top">
|
||||||
<p>{{ t('cards.product.stock') }} {{ product.quantity }}</p>
|
<p>{{ t('cards.product.stock') }} {{ product?.quantity }}</p>
|
||||||
<nuxt-img
|
<nuxt-img
|
||||||
:src="product.brand.smallLogo"
|
:src="product?.brand.smallLogo"
|
||||||
:alt="product.brand.name"
|
:alt="product?.brand.name"
|
||||||
format="webp"
|
format="webp"
|
||||||
densities="x1"
|
densities="x1"
|
||||||
/>
|
/>
|
||||||
|
|
@ -54,44 +57,48 @@
|
||||||
allow-half
|
allow-half
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
<div class="product__info-price">{{ product.price }} {{ CURRENCY }}</div>
|
<div class="product__info-price">{{ product?.price }} {{ CURRENCY }}</div>
|
||||||
<div class="product__info-bottom">
|
<client-only>
|
||||||
<ui-button
|
<div class="product__info-bottom">
|
||||||
class="product__info-button"
|
<ui-button
|
||||||
v-if="isProductInCart"
|
class="product__info-button"
|
||||||
@click="overwriteOrder({
|
v-if="isProductInCart"
|
||||||
type: 'remove',
|
@click="overwriteOrder({
|
||||||
productUuid: product.uuid,
|
type: 'remove',
|
||||||
productName: product.name
|
productUuid: product?.uuid,
|
||||||
})"
|
productName: product?.name
|
||||||
:isLoading="removeLoading"
|
})"
|
||||||
>
|
:type="'button'"
|
||||||
{{ t('buttons.removeFromCart') }}
|
:isLoading="removeLoading"
|
||||||
</ui-button>
|
>
|
||||||
<ui-button
|
{{ t('buttons.removeFromCart') }}
|
||||||
class="product__info-button"
|
</ui-button>
|
||||||
v-else
|
<ui-button
|
||||||
@click="overwriteOrder({
|
class="product__info-button"
|
||||||
type: 'add',
|
v-else
|
||||||
productUuid: product.uuid,
|
@click="overwriteOrder({
|
||||||
productName: product.name
|
type: 'add',
|
||||||
})"
|
productUuid: product?.uuid,
|
||||||
:isLoading="addLoading"
|
productName: product?.name
|
||||||
>
|
})"
|
||||||
{{ t('buttons.addToCart') }}
|
:type="'button'"
|
||||||
</ui-button>
|
:isLoading="addLoading"
|
||||||
<div
|
>
|
||||||
class="product__info-wishlist"
|
{{ t('buttons.addToCart') }}
|
||||||
@click="overwriteWishlist({
|
</ui-button>
|
||||||
type: (isProductInWishlist ? 'remove' : 'add'),
|
<div
|
||||||
productUuid: product.uuid,
|
class="product__info-wishlist"
|
||||||
productName: product.name
|
@click="overwriteWishlist({
|
||||||
})"
|
type: (isProductInWishlist ? 'remove' : 'add'),
|
||||||
>
|
productUuid: product?.uuid,
|
||||||
<icon name="mdi:cards-heart" size="28" v-if="isProductInWishlist" />
|
productName: product?.name
|
||||||
<icon name="mdi:cards-heart-outline" size="28" v-else />
|
})"
|
||||||
|
>
|
||||||
|
<icon name="mdi:cards-heart" size="28" v-if="isProductInWishlist" />
|
||||||
|
<icon name="mdi:cards-heart-outline" size="28" v-else />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</client-only>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -167,9 +174,11 @@ 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";
|
import {useOrderOverwrite} from "~/composables/orders";
|
||||||
|
import {useDefaultSeo} from "~/composables/seo";
|
||||||
|
import {useAppConfig} from "~/composables/config";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const {t} = useI18n();
|
const {t, locale} = useI18n();
|
||||||
const wishlistStore = useWishlistStore();
|
const wishlistStore = useWishlistStore();
|
||||||
const cartStore = useCartStore();
|
const cartStore = useCartStore();
|
||||||
|
|
||||||
|
|
@ -178,10 +187,44 @@ const { scrollTo } = useScrollTo();
|
||||||
|
|
||||||
const slug = useRouteParams<string>('slug');
|
const slug = useRouteParams<string>('slug');
|
||||||
|
|
||||||
|
const { APP_NAME } = useAppConfig();
|
||||||
const { overwriteWishlist } = useWishlistOverwrite();
|
const { overwriteWishlist } = useWishlistOverwrite();
|
||||||
const { addLoading, removeLoading, overwriteOrder } = useOrderOverwrite();
|
const { addLoading, removeLoading, overwriteOrder } = useOrderOverwrite();
|
||||||
const { product } = await useProductBySlug(slug.value);
|
const { product, seoMeta } = await useProductBySlug(slug.value);
|
||||||
const { products, getProducts } = await useProducts();
|
|
||||||
|
const meta = useDefaultSeo(seoMeta.value || null);
|
||||||
|
|
||||||
|
if (meta) {
|
||||||
|
useSeoMeta({
|
||||||
|
title: meta.title || APP_NAME,
|
||||||
|
description: meta.description || meta.title || APP_NAME,
|
||||||
|
ogTitle: meta.og.title || undefined,
|
||||||
|
ogDescription: meta.og.description || meta.title || APP_NAME,
|
||||||
|
ogType: meta.og.type || undefined,
|
||||||
|
ogUrl: meta.og.url || undefined,
|
||||||
|
ogImage: meta.og.image || undefined,
|
||||||
|
twitterCard: meta.twitter.card || undefined,
|
||||||
|
twitterTitle: meta.twitter.title || undefined,
|
||||||
|
twitterDescription: meta.twitter.description || undefined,
|
||||||
|
robots: meta.robots,
|
||||||
|
});
|
||||||
|
|
||||||
|
useHead({
|
||||||
|
link: [
|
||||||
|
meta.canonical ? { rel: 'canonical', href: meta.canonical } : {},
|
||||||
|
].filter(Boolean) as any,
|
||||||
|
meta: [{ property: 'og:locale', content: locale.value }],
|
||||||
|
script: meta.jsonLd.map((obj: any) => ({
|
||||||
|
type: 'application/ld+json',
|
||||||
|
innerHTML: JSON.stringify(obj),
|
||||||
|
})),
|
||||||
|
__dangerouslyDisableSanitizersByTagID: Object.fromEntries(
|
||||||
|
meta.jsonLd.map((_, i: number) => [`ldjson-${i}`, ['innerHTML']])
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { products, getProducts } = useProducts();
|
||||||
await getProducts({
|
await getProducts({
|
||||||
categoriesSlugs: product.value?.category.slug
|
categoriesSlugs: product.value?.category.slug
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,12 @@
|
||||||
<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 }}{{ CURRENCY }}</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
|
||||||
|
:type="'button'"
|
||||||
|
class="cart__top-button"
|
||||||
|
>
|
||||||
|
{{ t('buttons.checkout') }}
|
||||||
|
</ui-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="cart__list">
|
<div class="cart__list">
|
||||||
<div class="cart__list-inner" v-if="productsInCart.length">
|
<div class="cart__list-inner" v-if="productsInCart.length">
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,9 @@ import {CURRENCY} from "~/config/constants";
|
||||||
import {useDate} from "~/composables/date";
|
import {useDate} from "~/composables/date";
|
||||||
|
|
||||||
const {t, locale} = useI18n();
|
const {t, locale} = useI18n();
|
||||||
const promocodesStore = usePromocodeStore();
|
const promocodeStore = usePromocodeStore();
|
||||||
|
|
||||||
const promocodes = computed(() => promocodesStore.promocodes);
|
const promocodes = computed(() => promocodeStore.promocodes);
|
||||||
|
|
||||||
const copyCode = (code: string) => {
|
const copyCode = (code: string) => {
|
||||||
navigator.clipboard.writeText(code)
|
navigator.clipboard.writeText(code)
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,6 @@ import { useAppConfig } from '~/composables/config';
|
||||||
|
|
||||||
export default defineNuxtPlugin((nuxtApp) => {
|
export default defineNuxtPlugin((nuxtApp) => {
|
||||||
const runtime = useRuntimeConfig();
|
const runtime = useRuntimeConfig();
|
||||||
const localeCookie = useCookie(useAppConfig().COOKIES_LOCALE_KEY);
|
|
||||||
const token = useCookie(useAppConfig().COOKIES_ACCESS_TOKEN_KEY);
|
|
||||||
const { $apollo } = nuxtApp as any;
|
const { $apollo } = nuxtApp as any;
|
||||||
|
|
||||||
const errorLink = onError((err) => {
|
const errorLink = onError((err) => {
|
||||||
|
|
@ -16,6 +14,9 @@ export default defineNuxtPlugin((nuxtApp) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const authLink = setContext(async (_, { headers }) => {
|
const authLink = setContext(async (_, { headers }) => {
|
||||||
|
const localeCookie = useCookie(useAppConfig().COOKIES_LOCALE_KEY);
|
||||||
|
const token = useCookie(useAppConfig().COOKIES_ACCESS_TOKEN_KEY);
|
||||||
|
|
||||||
const hdrs: Record<string,string> = {
|
const hdrs: Record<string,string> = {
|
||||||
...headers,
|
...headers,
|
||||||
'Accept-Language': localeCookie.value || 'en-gb'
|
'Accept-Language': localeCookie.value || 'en-gb'
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type {ICategory, ICategoryTag, IStoreFilters} from "~/types";
|
import type {ICategory, ICategoryTag, ISEOMeta, IStoreFilters} from "~/types";
|
||||||
|
|
||||||
export interface ICategoriesResponse {
|
export interface ICategoriesResponse {
|
||||||
categories: {
|
categories: {
|
||||||
|
|
@ -31,6 +31,16 @@ export interface ICategoryBySlugResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ICategoryBySlugSeoResponse {
|
||||||
|
categories: {
|
||||||
|
edges: {
|
||||||
|
node: {
|
||||||
|
seoMeta: ISEOMeta
|
||||||
|
}
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface ICategoryTagsResponse {
|
export interface ICategoryTagsResponse {
|
||||||
categoryTags: {
|
categoryTags: {
|
||||||
edges: {
|
edges: {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import type {ICategory} from "~/types";
|
import type {ICategory, ISEOMeta} from "~/types";
|
||||||
|
|
||||||
export interface IBrand {
|
export interface IBrand {
|
||||||
name: string,
|
name: string,
|
||||||
|
slug: string,
|
||||||
uuid: string,
|
uuid: string,
|
||||||
smallLogo: string,
|
smallLogo: string,
|
||||||
bigLogo: string,
|
bigLogo: string,
|
||||||
|
seoMeta: ISEOMeta,
|
||||||
categories: ICategory[]
|
categories: ICategory[]
|
||||||
}
|
}
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import type {ISEOMeta} from "~/types";
|
||||||
|
|
||||||
export interface IProduct {
|
export interface IProduct {
|
||||||
uuid: string,
|
uuid: string,
|
||||||
name: string,
|
name: string,
|
||||||
|
|
@ -5,6 +7,7 @@ export interface IProduct {
|
||||||
quantity: number,
|
quantity: number,
|
||||||
slug: string,
|
slug: string,
|
||||||
description: string,
|
description: string,
|
||||||
|
seoMeta: ISEOMeta,
|
||||||
brand: {
|
brand: {
|
||||||
smallLogo: string,
|
smallLogo: string,
|
||||||
uuid: string,
|
uuid: string,
|
||||||
|
|
|
||||||
20
storefront/types/app/seometa.ts
Normal file
20
storefront/types/app/seometa.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
export interface ISEOMeta {
|
||||||
|
canonical: string,
|
||||||
|
description: string,
|
||||||
|
hreflang: string,
|
||||||
|
jsonLd: string,
|
||||||
|
openGraph: {
|
||||||
|
title: string,
|
||||||
|
description: string,
|
||||||
|
type: string,
|
||||||
|
url: string,
|
||||||
|
image: string
|
||||||
|
},
|
||||||
|
robots: string,
|
||||||
|
title: string,
|
||||||
|
twitter: {
|
||||||
|
card: string,
|
||||||
|
title: string,
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,7 @@ export * from './app/orders'
|
||||||
export * from './app/category'
|
export * from './app/category'
|
||||||
export * from './app/store'
|
export * from './app/store'
|
||||||
export * from './app/promocodes'
|
export * from './app/promocodes'
|
||||||
|
export * from './app/seometa'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue