schon/storefront/components/store/index.vue
Alexandr SaVBaD Waltz 129ad1a6fa Features: 1) Build standalone pages for search, contact, catalog, category, brand, product, and home with localized metadata and scoped styles; 2) Add extensive TypeScript definitions for API and app-level structures, including products, orders, brands, and categories; 3) Implement i18n configuration with dynamic browser language detection and fallback system;
Fixes: None;

Extra: 1) Create Pinia stores for app, user, category, and company management; 2) Add utility functions for error handling and category slug lookups; 3) Include German locale file and robots.txt for improved SEO and accessibility; 4) Add SVG assets and improve general folder structure for better maintainability.
2025-06-27 00:10:35 +03:00

146 lines
No EOL
3.2 KiB
Vue

<template>
<div class="store">
<store-filter
v-if="filters.length"
:filterableAttributes="filters"
:isOpen="showFilter"
@update:selected="onFiltersChange"
@close="showFilter = false"
/>
<store-top
v-model="orderBy"
@toggle-filter="onFilterToggle"
/>
<div
class="store__list"
:class="[
{ 'store__list-grid': productView === 'grid' },
{ 'store__list-list': productView === 'list' }
]"
>
<cards-product
v-if="products.length"
v-for="product in products"
:key="product.node.uuid"
:product="product.node"
/>
<skeletons-cards-product
v-if="pending"
v-for="idx in 12"
:key="idx"
:isList="productView === 'list'"
/>
</div>
<div class="store__list-observer" ref="observer"></div>
</div>
</template>
<script setup lang="ts">
import {useFilters, useStore} from "~/composables/store";
import {useRouteQuery} from "@vueuse/router";
import {useCategoryBySlug} from "~/composables/categories";
import {useAppConfig} from '~/composables/config';
const { COOKIES_PRODUCT_VIEW_KEY } = useAppConfig();
const productView = useCookie<string>(
COOKIES_PRODUCT_VIEW_KEY as string,
{
default: () => 'grid',
path: '/',
}
);
const slug = useRouteParams<string>('slug');
const attributes = useRouteQuery<string>('attributes', '');
const orderBy = useRouteQuery<string>('orderBy', 'created');
const minPrice = useRouteQuery<number>('minPrice', 0);
const maxPrice = useRouteQuery<number>('maxPrice', 50000);
const observer = ref(null);
const { category, filters } = await useCategoryBySlug(slug.value);
watch(
() => category.value,
(cat) => {
if (cat && !useRoute().query.maxPrice) {
maxPrice.value = cat.minMaxPrices.maxPrice;
}
},
{ immediate: true }
);
const { pending, products, pageInfo, prodVars } = await useStore(
slug.value,
attributes.value,
orderBy.value,
minPrice.value,
maxPrice.value,
''
);
const { buildAttributesString } = useFilters(filters);
const showFilter = ref<boolean>(false);
function onFilterToggle() {
showFilter.value = true;
}
function onFiltersChange(newFilters: Record<string, string[]>) {
attributes.value = buildAttributesString(newFilters);
}
useIntersectionObserver(
observer,
async ([{ isIntersecting }]) => {
if (isIntersecting && pageInfo.value?.hasNextPage) {
prodVars.productAfter = pageInfo.value.endCursor;
}
},
);
watch(orderBy, newVal => {
prodVars.orderBy = newVal || '';
});
watch(attributes, newVal => {
prodVars.attributes = newVal || '';
});
watch(minPrice, newVal => {
prodVars.minPrice = newVal || 0;
});
watch(maxPrice, newVal => {
prodVars.maxPrice = newVal || 500000;
});
</script>
<style scoped lang="scss">
.store {
position: relative;
&__inner {
width: 100%;
}
&__list {
margin-top: 50px;
&-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 25px;
}
&-list {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 20px;
}
&-observer {
background-color: transparent;
width: 100%;
height: 10px;
}
}
}
</style>