Merge remote-tracking branch 'origin/storefront-nuxt' into storefront-nuxt
This commit is contained in:
commit
dc19e1f0a0
67 changed files with 658 additions and 673 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,11 +0,0 @@
|
|||
User-agent: *
|
||||
Disallow: /admin/
|
||||
Disallow: /static/
|
||||
Disallow: /media/
|
||||
Disallow: /cart/
|
||||
Disallow: /account/
|
||||
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://evibes.com/sitemap.xml
|
||||
Host: evibes.com
|
||||
|
|
@ -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 {useCompanyInfo} from "~/composables/company";
|
||||
import {useCategories} from "~/composables/categories";
|
||||
import {useNotification} from "~/composables/notification";
|
||||
|
||||
const { locale } = useI18n();
|
||||
const route = useRoute();
|
||||
|
|
@ -38,7 +37,8 @@ const showBreadcrumbs = computed(() => {
|
|||
'brand',
|
||||
'search',
|
||||
'profile',
|
||||
'activate-user'
|
||||
'activate-user',
|
||||
'reset-password'
|
||||
].some(prefix => name.startsWith(prefix));
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@
|
|||
<nuxt-img
|
||||
:src="product.node.product.images.edges[0].node.image"
|
||||
:alt="product.node.product.name"
|
||||
format="webp"
|
||||
densities="x1"
|
||||
/>
|
||||
<p>{{ product.node.product.name }}</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@
|
|||
productUuid: product.uuid,
|
||||
productName: product.name
|
||||
})"
|
||||
:type="'button'"
|
||||
:isLoading="removeLoading"
|
||||
>
|
||||
{{ t('buttons.removeFromCart') }}
|
||||
|
|
@ -79,6 +80,7 @@
|
|||
productUuid: product.uuid,
|
||||
productName: product.name
|
||||
})"
|
||||
:type="'button'"
|
||||
:isLoading="addLoading"
|
||||
>
|
||||
{{ t('buttons.addToCart') }}
|
||||
|
|
@ -106,7 +108,7 @@
|
|||
>
|
||||
-
|
||||
</button>
|
||||
<span class="tools__item tools__item-count" v-text="'X' + productinCartQuantity" />
|
||||
<span class="tools__item tools__item-count" v-text="'X' + productInCartQuantity" />
|
||||
<button
|
||||
class="tools__item tools__item-button"
|
||||
@click="overwriteOrder({
|
||||
|
|
@ -156,7 +158,7 @@ const isProductInWishlist = computed(() => {
|
|||
const isProductInCart = computed(() => {
|
||||
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;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
v-model="message"
|
||||
/>
|
||||
<ui-button
|
||||
:type="'submit'"
|
||||
class="form__button"
|
||||
:isDisabled="!isFormValid"
|
||||
:isLoading="loading"
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
/>
|
||||
</div>
|
||||
<ui-button
|
||||
:type="'submit'"
|
||||
class="form__button"
|
||||
:isDisabled="!isFormValid"
|
||||
:isLoading="loading"
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
{{ t('forms.login.forgot') }}
|
||||
</ui-link>
|
||||
<ui-button
|
||||
:type="'submit'"
|
||||
class="form__button"
|
||||
:isDisabled="!isFormValid"
|
||||
:isLoading="loading"
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
v-model="confirmPassword"
|
||||
/>
|
||||
<ui-button
|
||||
:type="'submit'"
|
||||
class="form__button"
|
||||
:isDisabled="!isFormValid"
|
||||
:isLoading="loading"
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
v-model="confirmPassword"
|
||||
/>
|
||||
<ui-button
|
||||
:type="'submit'"
|
||||
class="form__button"
|
||||
:isDisabled="!isFormValid"
|
||||
:isLoading="loading"
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
:inputMode="'email'"
|
||||
/>
|
||||
<ui-button
|
||||
:type="'submit'"
|
||||
class="form__button"
|
||||
:isDisabled="!isFormValid"
|
||||
:isLoading="loading"
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
/>
|
||||
</div>
|
||||
<ui-button
|
||||
:type="'submit'"
|
||||
class="form__button"
|
||||
:isLoading="loading"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,19 @@
|
|||
<template>
|
||||
<div class="brands">
|
||||
<client-only>
|
||||
<NuxtMarquee
|
||||
<nuxt-marquee
|
||||
class="brand__marquee"
|
||||
id="marquee-slider"
|
||||
:speed="50"
|
||||
:pauseOnHover="true"
|
||||
>
|
||||
<div
|
||||
class="brands__item"
|
||||
<nuxt-link-locale
|
||||
v-for="brand in brands"
|
||||
:key="brand.node.uuid"
|
||||
|
||||
:to="`/brand/${brand.node.slug}`"
|
||||
>
|
||||
<nuxt-link-locale
|
||||
:to="`/brand/${brand.node.uuid}`"
|
||||
<div
|
||||
class="brands__item"
|
||||
|
||||
>
|
||||
<nuxt-img
|
||||
densities="x1"
|
||||
|
|
@ -23,17 +22,18 @@
|
|||
loading="lazy"
|
||||
class="brands__item-image"
|
||||
/>
|
||||
</nuxt-link-locale>
|
||||
</div>
|
||||
</NuxtMarquee>
|
||||
</client-only>
|
||||
</nuxt-link-locale>
|
||||
</nuxt-marquee>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useBrands} from "~/composables/brands";
|
||||
import type {IBrand} from "~/types";
|
||||
|
||||
const { brands } = await useBrands();
|
||||
defineProps<{
|
||||
brands: { node: IBrand }[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
<template>
|
||||
<div class="tags">
|
||||
<div
|
||||
class="tags"
|
||||
v-if="tags.length > 0"
|
||||
>
|
||||
<home-category-tags-block
|
||||
v-for="tag in tags"
|
||||
:key="tag.node.uuid"
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
:key="tag.node.uuid"
|
||||
:tag="tag.node"
|
||||
/>
|
||||
<client-only>
|
||||
<home-collection-inner
|
||||
v-if="newProducts.length > 0"
|
||||
:tag="newProductsTag"
|
||||
|
|
@ -17,6 +18,7 @@
|
|||
v-if="priceProducts.length > 0"
|
||||
:tag="priceProductsTag"
|
||||
/>
|
||||
</client-only>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -24,20 +26,15 @@
|
|||
</template>
|
||||
|
||||
<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 {
|
||||
products: newProducts,
|
||||
getProducts: getNewProducts
|
||||
} = await useProducts();
|
||||
|
||||
const {
|
||||
products: priceProducts,
|
||||
getProducts: getPriceProducts
|
||||
} = await useProducts();
|
||||
const { t } = useI18n();
|
||||
|
||||
const newProductsTag = computed(() => {
|
||||
return {
|
||||
|
|
@ -45,7 +42,7 @@ const newProductsTag = computed(() => {
|
|||
tagName: t('home.collection.newTag'),
|
||||
uuid: 'new-products',
|
||||
productSet: {
|
||||
edges: newProducts.value
|
||||
edges: props.newProducts
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -56,19 +53,10 @@ const priceProductsTag = computed(() => {
|
|||
tagName: t('home.collection.cheapTag'),
|
||||
uuid: 'price-products',
|
||||
productSet: {
|
||||
edges: priceProducts.value
|
||||
edges: props.priceProducts
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
getNewProducts({
|
||||
orderBy: '-modified'
|
||||
}),
|
||||
getPriceProducts({
|
||||
orderBy: '-price'
|
||||
})
|
||||
]);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
{{ t('store.filters.reset') }}
|
||||
</button>
|
||||
<ui-button
|
||||
:type="'button'"
|
||||
@click="onApply"
|
||||
>
|
||||
{{ t('store.filters.apply') }}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@
|
|||
<store-top
|
||||
v-model="orderBy"
|
||||
@toggle-filter="onFilterToggle"
|
||||
:isFiltersVisible="filters.length > 0"
|
||||
/>
|
||||
<client-only>
|
||||
<div
|
||||
class="store__list"
|
||||
:class="[
|
||||
|
|
@ -19,7 +21,7 @@
|
|||
]"
|
||||
>
|
||||
<cards-product
|
||||
v-if="products.length"
|
||||
v-if="products.length && !pending"
|
||||
v-for="product in products"
|
||||
:key="product.node.uuid"
|
||||
:product="product.node"
|
||||
|
|
@ -32,6 +34,7 @@
|
|||
:isList="productView === 'list'"
|
||||
/>
|
||||
</div>
|
||||
</client-only>
|
||||
<div class="store__list-observer" ref="observer"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -42,8 +45,11 @@ import {useRouteQuery} from "@vueuse/router";
|
|||
import {useRouteParams} from "@vueuse/router";
|
||||
import {useCategoryBySlug} from "~/composables/categories";
|
||||
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>(
|
||||
COOKIES_PRODUCT_VIEW_KEY as string,
|
||||
{
|
||||
|
|
@ -59,7 +65,39 @@ const minPrice = useRouteQuery<number>('minPrice', 0);
|
|||
const maxPrice = useRouteQuery<number>('maxPrice', 50000);
|
||||
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(
|
||||
() => category.value,
|
||||
|
|
@ -71,7 +109,7 @@ watch(
|
|||
{ immediate: true }
|
||||
);
|
||||
|
||||
const { pending, products, pageInfo, variables } = await useStore(
|
||||
const { pending, products, pageInfo, variables } = useStore(
|
||||
slug.value,
|
||||
attributes.value,
|
||||
orderBy.value,
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="top__filter">
|
||||
<div class="top__filter" v-if="isFiltersVisible">
|
||||
<button
|
||||
class="top__filter-button"
|
||||
@click="$emit('toggle-filter')"
|
||||
|
|
@ -53,6 +53,7 @@ import { useAppConfig } from '~/composables/config';
|
|||
|
||||
const props = defineProps<{
|
||||
modelValue: string
|
||||
isFiltersVisible: boolean
|
||||
}>()
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: string): void
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
class="button"
|
||||
:disabled="isDisabled"
|
||||
:class="[{active: isLoading}]"
|
||||
type="submit"
|
||||
:type="type"
|
||||
>
|
||||
<ui-loader class="button__loader" v-if="isLoading" />
|
||||
<slot v-else />
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
type: 'submit' | 'button',
|
||||
isDisabled?: boolean,
|
||||
isLoading?: boolean
|
||||
}>()
|
||||
|
|
|
|||
|
|
@ -1,22 +1,23 @@
|
|||
<template>
|
||||
<div @click="redirect" class="link">
|
||||
<div
|
||||
v-on="props.routePath ? { click: redirect } : {}"
|
||||
class="link"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
routePath: string
|
||||
routePath?: string
|
||||
}>();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const redirect = () => {
|
||||
if (props.routePath) {
|
||||
router.push({
|
||||
path: props.routePath
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -57,8 +57,6 @@ export function useLogin() {
|
|||
userStore.setUser(authData.user);
|
||||
cookieAccess.value = authData.accessToken;
|
||||
|
||||
await nextTick();
|
||||
|
||||
appStore.unsetActiveState();
|
||||
|
||||
useNotification({
|
||||
|
|
|
|||
|
|
@ -64,8 +64,6 @@ export function useRefresh() {
|
|||
// });
|
||||
// await usePromocodes();
|
||||
|
||||
await nextTick();
|
||||
|
||||
await useUserBaseData(data.user.email);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
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 type {IBrandsResponse} from "~/types";
|
||||
|
||||
export async function useBrands() {
|
||||
const brands = computed(() => data.value?.brands.edges ?? []);
|
||||
|
||||
const { data, error } = await useAsyncQuery<IBrandsResponse>(
|
||||
export function useBrands() {
|
||||
const { data, error } = useAsyncQuery<IBrandsResponse>(
|
||||
GET_BRANDS
|
||||
);
|
||||
|
||||
const brands = computed(() => data.value?.brands.edges ?? []);
|
||||
|
||||
watch(error, (err) => {
|
||||
if (err) {
|
||||
console.error('useBrands error:', err)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export async function useCategoryBySlug(slug: string) {
|
|||
|
||||
return {
|
||||
category,
|
||||
seoMeta: computed(() => category.value?.seoMeta),
|
||||
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 {
|
||||
product
|
||||
product,
|
||||
seoMeta: computed(() => product.value?.seoMeta)
|
||||
};
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
import {GET_PRODUCT_TAGS} from "~/graphql/queries/standalone/products.js";
|
||||
import type {IProductTagsResponse} from "~/types";
|
||||
|
||||
export async function useProductTags() {
|
||||
const tags = computed(() => data.value?.productTags?.edges ?? []);
|
||||
|
||||
const { data, error } = await useAsyncQuery<IProductTagsResponse>(
|
||||
export function useProductTags() {
|
||||
const { data, error } = useAsyncQuery<IProductTagsResponse>(
|
||||
GET_PRODUCT_TAGS
|
||||
);
|
||||
|
||||
const tags = computed(() => data.value?.productTags?.edges ?? []);
|
||||
|
||||
watch(error, (err) => {
|
||||
if (err) {
|
||||
console.error('useProductTags error:', err)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { GET_PRODUCTS } from '~/graphql/queries/standalone/products';
|
||||
import type { IProductResponse } from '~/types';
|
||||
|
||||
export async function useProducts() {
|
||||
export function useProducts() {
|
||||
const variables = ref({ first: 12 });
|
||||
|
||||
const { data, error, refresh } = await useAsyncQuery<IProductResponse>(
|
||||
const { data, error, refresh } = useAsyncQuery<IProductResponse>(
|
||||
GET_PRODUCTS,
|
||||
variables
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type {IPromocodesResponse} from "~/types";
|
|||
import {GET_PROMOCODES} from "~/graphql/queries/standalone/promocodes";
|
||||
|
||||
export async function usePromocodes () {
|
||||
const promocodesStore = usePromocodeStore();
|
||||
const promocodeStore = usePromocodeStore();
|
||||
|
||||
const { data, error } = await useAsyncQuery<IPromocodesResponse>(
|
||||
GET_PROMOCODES
|
||||
|
|
@ -10,7 +10,7 @@ export async function usePromocodes () {
|
|||
console.log(data.value)
|
||||
|
||||
if (!error.value && data.value?.promocodes.edges) {
|
||||
promocodesStore.setPromocodes(data.value.promocodes.edges);
|
||||
promocodeStore.setPromocodes(data.value.promocodes.edges);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
export async function useStore(
|
||||
export function useStore(
|
||||
slug: string,
|
||||
attributes?: string,
|
||||
orderBy?: string,
|
||||
|
|
@ -29,12 +29,12 @@ export async function useStore(
|
|||
productAfter
|
||||
});
|
||||
|
||||
const { pending, data, error, refresh } = await useAsyncQuery<IProductResponse>(
|
||||
const { pending, data, error, refresh } = useAsyncQuery<IProductResponse>(
|
||||
GET_PRODUCTS,
|
||||
variables
|
||||
);
|
||||
|
||||
const products = ref(data.value?.products.edges ?? []);
|
||||
const products = ref([...(data.value?.products.edges ?? [])]);
|
||||
const pageInfo = computed(() => data.value?.products.pageInfo ?? null);
|
||||
|
||||
watch(error, e => e && console.error('useStore products error', e));
|
||||
|
|
@ -45,7 +45,7 @@ export async function useStore(
|
|||
if (!newCursor || newCursor === oldCursor) return;
|
||||
await refresh();
|
||||
const newEdges = data.value?.products.edges ?? [];
|
||||
products.value.push(...newEdges);
|
||||
products.value = [...products.value, ...newEdges];
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ export async function useStore(
|
|||
async () => {
|
||||
variables.productAfter = '';
|
||||
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) {
|
||||
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) {
|
||||
const wishlistStore = useWishlistStore();
|
||||
const cartStore = useCartStore();
|
||||
const promocodesStore = usePromocodeStore();
|
||||
const promocodeStore = usePromocodeStore();
|
||||
|
||||
const { document, variables } = getUserBaseData(
|
||||
{
|
||||
|
|
@ -14,19 +14,19 @@ export async function useUserBaseData(userEmail: string) {
|
|||
},
|
||||
);
|
||||
|
||||
const { data, error } = await useAsyncQuery<IUserBaseDataResponse>(
|
||||
document,
|
||||
variables
|
||||
);
|
||||
const { mutate, error } = useMutation<IUserBaseDataResponse>(document);
|
||||
|
||||
if (data.value?.wishlists.edges) {
|
||||
wishlistStore.setWishlist(data.value.wishlists.edges[0].node);
|
||||
const result = await mutate(variables);
|
||||
const data = result?.data;
|
||||
|
||||
if (data?.wishlists.edges) {
|
||||
wishlistStore.setWishlist(data.wishlists.edges[0].node);
|
||||
}
|
||||
if (data.value?.orders.edges) {
|
||||
cartStore.setCurrentOrders(data.value?.orders.edges[0].node);
|
||||
if (data?.orders.edges) {
|
||||
cartStore.setCurrentOrders(data.orders.edges[0].node);
|
||||
}
|
||||
if (data.value?.promocodes.edges) {
|
||||
promocodesStore.setPromocodes(data.value.promocodes.edges);
|
||||
if (data?.promocodes.edges) {
|
||||
promocodeStore.setPromocodes(data.promocodes.edges);
|
||||
}
|
||||
|
||||
watch(error, (err) => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
export const BRAND_FRAGMENT = gql`
|
||||
fragment Brand on BrandType {
|
||||
uuid
|
||||
slug
|
||||
name
|
||||
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`
|
||||
mutation addToCart(
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
export const DEPOSIT = gql`
|
||||
mutation deposit(
|
||||
$amount: Number!
|
||||
$amount: Float!
|
||||
) {
|
||||
contactUs(
|
||||
deposit(
|
||||
amount: $amount,
|
||||
) {
|
||||
error
|
||||
received
|
||||
transaction {
|
||||
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`
|
||||
mutation addToWishlist(
|
||||
|
|
|
|||
|
|
@ -12,10 +12,12 @@ export const GET_POSTS = gql`
|
|||
|
||||
export const GET_POST_BY_SLUG = gql`
|
||||
query getPostBySlug(
|
||||
$slug: String!
|
||||
$slug: String!,
|
||||
$first: Int
|
||||
) {
|
||||
posts(
|
||||
slug: $slug
|
||||
slug: $slug,
|
||||
first: $first,
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import {BRAND_FRAGMENT} from "@/graphql/fragments/brands.fragment.js";
|
||||
import {CATEGORY_FRAGMENT} from "@/graphql/fragments/categories.fragment.js";
|
||||
import {BRAND_FRAGMENT} from "@/graphql/fragments/brands.fragment";
|
||||
import {CATEGORY_FRAGMENT} from "@/graphql/fragments/categories.fragment";
|
||||
import {SEOMETA_FRAGMENT} from "@/graphql/fragments/seometa.fragment";
|
||||
|
||||
export const GET_BRANDS = gql`
|
||||
query getBrands (
|
||||
|
|
@ -18,12 +19,12 @@ export const GET_BRANDS = gql`
|
|||
${BRAND_FRAGMENT}
|
||||
`
|
||||
|
||||
export const GET_BRAND_BY_UUID = gql`
|
||||
query getBrandbyUuid(
|
||||
$uuid: UUID!
|
||||
export const GET_BRAND_BY_SLUG = gql`
|
||||
query getBrandBySlug(
|
||||
$slug: String!
|
||||
) {
|
||||
brands(
|
||||
uuid: $uuid
|
||||
slug: $slug
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
|
|
@ -31,10 +32,14 @@ export const GET_BRAND_BY_UUID = gql`
|
|||
categories {
|
||||
...Category
|
||||
}
|
||||
seoMeta {
|
||||
...SEOMeta
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${BRAND_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`
|
||||
query getCategories (
|
||||
|
|
@ -55,11 +56,34 @@ export const GET_CATEGORY_BY_SLUG = gql`
|
|||
maxPrice
|
||||
minPrice
|
||||
}
|
||||
seoMeta {
|
||||
...SEOMeta
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${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`
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
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`
|
||||
query getProducts(
|
||||
|
|
@ -46,11 +47,15 @@ export const GET_PRODUCT_BY_SLUG = gql`
|
|||
edges {
|
||||
node {
|
||||
...Product
|
||||
seoMeta {
|
||||
...SEOMeta
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${PRODUCT_FRAGMENT}
|
||||
${SEOMETA_FRAGMENT}
|
||||
`
|
||||
|
||||
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`
|
||||
query getWishlist {
|
||||
|
|
|
|||
|
|
@ -203,5 +203,8 @@
|
|||
"empty": "You don't have any promocodes."
|
||||
},
|
||||
"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==",
|
||||
"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": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@netlify/dev-utils/-/dev-utils-2.2.0.tgz",
|
||||
|
|
@ -4351,14 +4340,6 @@
|
|||
"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": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
|
|
@ -7876,257 +7857,6 @@
|
|||
"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": {
|
||||
"version": "3.1.3",
|
||||
"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">
|
||||
import type {ICategory} from "~/types";
|
||||
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 categoryStore = useCategoryStore()
|
||||
const { locale } = useI18n();
|
||||
const categoryStore = useCategoryStore();
|
||||
|
||||
const { setPageTitle } = usePageTitle()
|
||||
const { APP_NAME } = useAppConfig();
|
||||
const { setPageTitle } = usePageTitle();
|
||||
|
||||
const slug = computed(() => route.params.slug as string)
|
||||
const roots = computed(() => categoryStore.categories.map(e => e.node))
|
||||
const slug = useRouteParams<string>('slug');
|
||||
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 {
|
||||
for (const n of nodes) {
|
||||
if (n.slug === slug) return n
|
||||
if (n.slug === slug) return n;
|
||||
if (n.children?.length) {
|
||||
const found = findBySlug(n.children, slug)
|
||||
if (found) return found
|
||||
const found = findBySlug(n.children, slug);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
<template>
|
||||
<div class="home">
|
||||
<home-hero />
|
||||
<home-brands />
|
||||
<home-collection />
|
||||
<home-brands :brands="brands" />
|
||||
<home-collection
|
||||
:tags="tags"
|
||||
:newProducts="newProducts"
|
||||
:priceProducts="priceProducts"
|
||||
/>
|
||||
<home-category-tags />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -10,6 +14,9 @@
|
|||
<script setup lang="ts">
|
||||
import {useUserActivation} from "~/composables/user";
|
||||
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 appStore = useAppStore();
|
||||
|
|
@ -25,6 +32,18 @@ const referrer = useRouteQuery('referrer', '');
|
|||
|
||||
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 () => {
|
||||
if (route.path.includes('activate-user') && token.value && uid.value) {
|
||||
await activateUser(token.value, uid.value);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div class="product" v-if="product">
|
||||
<div class="product">
|
||||
<div class="container">
|
||||
<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__images">
|
||||
<div class="product__images-gallery">
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
>
|
||||
<nuxt-img
|
||||
:src="image"
|
||||
:alt="product.name"
|
||||
:alt="product?.name"
|
||||
format="webp"
|
||||
densities="x1"
|
||||
/>
|
||||
|
|
@ -22,28 +22,31 @@
|
|||
</div>
|
||||
<nuxt-img
|
||||
:src="selectedImage"
|
||||
:alt="product.name"
|
||||
:alt="product?.name"
|
||||
class="product__images-main"
|
||||
format="webp"
|
||||
densities="x1"
|
||||
/>
|
||||
</div>
|
||||
<div class="product__center">
|
||||
<p class="product__center-description">{{ product.description }}</p>
|
||||
<p class="product__center-description">{{ product?.description }}</p>
|
||||
<client-only>
|
||||
<p
|
||||
v-if="attributes.length"
|
||||
class="product__center-characteristic"
|
||||
@click="scrollTo('characteristics')"
|
||||
>
|
||||
{{ t('product.characteristics') }}
|
||||
</p>
|
||||
</client-only>
|
||||
</div>
|
||||
<div class="product__info">
|
||||
<div class="product__info-inner">
|
||||
<div class="product__info-top">
|
||||
<p>{{ t('cards.product.stock') }} {{ product.quantity }}</p>
|
||||
<p>{{ t('cards.product.stock') }} {{ product?.quantity }}</p>
|
||||
<nuxt-img
|
||||
:src="product.brand.smallLogo"
|
||||
:alt="product.brand.name"
|
||||
:src="product?.brand.smallLogo"
|
||||
:alt="product?.brand.name"
|
||||
format="webp"
|
||||
densities="x1"
|
||||
/>
|
||||
|
|
@ -54,16 +57,18 @@
|
|||
allow-half
|
||||
disabled
|
||||
/>
|
||||
<div class="product__info-price">{{ product.price }} {{ CURRENCY }}</div>
|
||||
<div class="product__info-price">{{ product?.price }} {{ CURRENCY }}</div>
|
||||
<client-only>
|
||||
<div class="product__info-bottom">
|
||||
<ui-button
|
||||
class="product__info-button"
|
||||
v-if="isProductInCart"
|
||||
@click="overwriteOrder({
|
||||
type: 'remove',
|
||||
productUuid: product.uuid,
|
||||
productName: product.name
|
||||
productUuid: product?.uuid,
|
||||
productName: product?.name
|
||||
})"
|
||||
:type="'button'"
|
||||
:isLoading="removeLoading"
|
||||
>
|
||||
{{ t('buttons.removeFromCart') }}
|
||||
|
|
@ -73,9 +78,10 @@
|
|||
v-else
|
||||
@click="overwriteOrder({
|
||||
type: 'add',
|
||||
productUuid: product.uuid,
|
||||
productName: product.name
|
||||
productUuid: product?.uuid,
|
||||
productName: product?.name
|
||||
})"
|
||||
:type="'button'"
|
||||
:isLoading="addLoading"
|
||||
>
|
||||
{{ t('buttons.addToCart') }}
|
||||
|
|
@ -84,14 +90,15 @@
|
|||
class="product__info-wishlist"
|
||||
@click="overwriteWishlist({
|
||||
type: (isProductInWishlist ? 'remove' : 'add'),
|
||||
productUuid: product.uuid,
|
||||
productName: product.name
|
||||
productUuid: product?.uuid,
|
||||
productName: product?.name
|
||||
})"
|
||||
>
|
||||
<icon name="mdi:cards-heart" size="28" v-if="isProductInWishlist" />
|
||||
<icon name="mdi:cards-heart-outline" size="28" v-else />
|
||||
</div>
|
||||
</div>
|
||||
</client-only>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -167,9 +174,11 @@ import {Navigation} from "swiper/modules";
|
|||
import {CURRENCY} from "~/config/constants";
|
||||
import {useWishlistOverwrite} from "~/composables/wishlist";
|
||||
import {useOrderOverwrite} from "~/composables/orders";
|
||||
import {useDefaultSeo} from "~/composables/seo";
|
||||
import {useAppConfig} from "~/composables/config";
|
||||
|
||||
const route = useRoute();
|
||||
const {t} = useI18n();
|
||||
const {t, locale} = useI18n();
|
||||
const wishlistStore = useWishlistStore();
|
||||
const cartStore = useCartStore();
|
||||
|
||||
|
|
@ -178,10 +187,44 @@ const { scrollTo } = useScrollTo();
|
|||
|
||||
const slug = useRouteParams<string>('slug');
|
||||
|
||||
const { APP_NAME } = useAppConfig();
|
||||
const { overwriteWishlist } = useWishlistOverwrite();
|
||||
const { addLoading, removeLoading, overwriteOrder } = useOrderOverwrite();
|
||||
const { product } = await useProductBySlug(slug.value);
|
||||
const { products, getProducts } = await useProducts();
|
||||
const { product, seoMeta } = await useProductBySlug(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']])
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
const { products, getProducts } = useProducts();
|
||||
await getProducts({
|
||||
categoriesSlugs: product.value?.category.slug
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,7 +5,12 @@
|
|||
<p><span>{{ t('profile.cart.quantity') }}</span> {{ productsInCartQuantity }}</p>
|
||||
<p><span>{{ t('profile.cart.total') }}: </span> {{ totalPrice }}{{ CURRENCY }}</p>
|
||||
</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 class="cart__list">
|
||||
<div class="cart__list-inner" v-if="productsInCart.length">
|
||||
|
|
|
|||
|
|
@ -45,9 +45,9 @@ import {CURRENCY} from "~/config/constants";
|
|||
import {useDate} from "~/composables/date";
|
||||
|
||||
const {t, locale} = useI18n();
|
||||
const promocodesStore = usePromocodeStore();
|
||||
const promocodeStore = usePromocodeStore();
|
||||
|
||||
const promocodes = computed(() => promocodesStore.promocodes);
|
||||
const promocodes = computed(() => promocodeStore.promocodes);
|
||||
|
||||
const copyCode = (code: string) => {
|
||||
navigator.clipboard.writeText(code)
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ import { useAppConfig } from '~/composables/config';
|
|||
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
const runtime = useRuntimeConfig();
|
||||
const localeCookie = useCookie(useAppConfig().COOKIES_LOCALE_KEY);
|
||||
const token = useCookie(useAppConfig().COOKIES_ACCESS_TOKEN_KEY);
|
||||
const { $apollo } = nuxtApp as any;
|
||||
|
||||
const errorLink = onError((err) => {
|
||||
|
|
@ -16,6 +14,9 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|||
});
|
||||
|
||||
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> = {
|
||||
...headers,
|
||||
'Accept-Language': localeCookie.value || 'en-gb'
|
||||
|
|
|
|||
|
|
@ -1,2 +1,10 @@
|
|||
User-Agent: *
|
||||
Disallow:
|
||||
User-agent: *
|
||||
Disallow: /admin/
|
||||
Disallow: /static/
|
||||
Disallow: /media/
|
||||
Disallow: /profile/
|
||||
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://evibes.com/sitemap.xml
|
||||
Host: evibes.com
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type {ICategory, ICategoryTag, IStoreFilters} from "~/types";
|
||||
import type {ICategory, ICategoryTag, ISEOMeta, IStoreFilters} from "~/types";
|
||||
|
||||
export interface ICategoriesResponse {
|
||||
categories: {
|
||||
|
|
@ -31,6 +31,16 @@ export interface ICategoryBySlugResponse {
|
|||
}
|
||||
}
|
||||
|
||||
export interface ICategoryBySlugSeoResponse {
|
||||
categories: {
|
||||
edges: {
|
||||
node: {
|
||||
seoMeta: ISEOMeta
|
||||
}
|
||||
}[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface ICategoryTagsResponse {
|
||||
categoryTags: {
|
||||
edges: {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import type {ICategory} from "~/types";
|
||||
import type {ICategory, ISEOMeta} from "~/types";
|
||||
|
||||
export interface IBrand {
|
||||
name: string,
|
||||
slug: string,
|
||||
uuid: string,
|
||||
smallLogo: string,
|
||||
bigLogo: string,
|
||||
seoMeta: ISEOMeta,
|
||||
categories: ICategory[]
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import type {ISEOMeta} from "~/types";
|
||||
|
||||
export interface IProduct {
|
||||
uuid: string,
|
||||
name: string,
|
||||
|
|
@ -5,6 +7,7 @@ export interface IProduct {
|
|||
quantity: number,
|
||||
slug: string,
|
||||
description: string,
|
||||
seoMeta: ISEOMeta,
|
||||
brand: {
|
||||
smallLogo: 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/store'
|
||||
export * from './app/promocodes'
|
||||
export * from './app/seometa'
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue