schon/storefront/app/pages/brands.vue
2026-02-27 21:59:51 +03:00

159 lines
No EOL
3.5 KiB
Vue

<template>
<div class="brands">
<div class="brands__top">
<div class="container">
<div class="brands__top-wrapper">
<h1>{{ t('brands.title') }}</h1>
<p>{{ t('brands.text') }}</p>
<div class="brands__top-search">
<input
type="text"
inputmode="text"
:placeholder="t('fields.brandsSearch')"
v-model="searchInput"
>
<icon name="tabler:search" size="20" />
</div>
</div>
</div>
</div>
<div class="brands__main">
<div class="container">
<div class="brands__main-wrapper">
<cards-brand
v-for="brand in brands"
:key="brand.node.uuid"
:brand="brand.node"
/>
<div v-if="pending" class="brands__loading">
<skeletons-cards-brand
v-for="idx in 8"
:key="idx"
/>
</div>
<div class="brands__list-observer" ref="observer"></div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {usePageTitle} from "@composables/utils";
import {useBrands} from "@composables/brands";
const {t} = useI18n();
const observer = ref(null);
const name = useRouteQuery<string>('name', '');
const searchInput = ref<string>(name.value);
const { setPageTitle } = usePageTitle();
const { brands, pending, pageInfo, variables, getBrands } = useBrands({
brandSearch: name.value
});
await getBrands();
useIntersectionObserver(
observer,
async ([{ isIntersecting }]) => {
if (isIntersecting && pageInfo.value?.hasNextPage && !pending.value) {
variables.brandAfter = pageInfo.value.endCursor;
await getBrands();
}
},
);
const updateNameDebounced = useDebounceFn(() => {
name.value = searchInput.value;
}, 500);
watch(searchInput, newVal => {
updateNameDebounced();
});
watch(name, async (newVal) => {
variables.brandName = newVal || '';
variables.brandAfter = '';
brands.value = [];
await getBrands();
});
setPageTitle(t('breadcrumbs.brands'));
</script>
<style lang="scss" scoped>
.brands {
&__wrapper {
}
&__top {
padding-block: 70px;
background-color: #f8f8f8;
&-wrapper {
display: flex;
flex-direction: column;
align-items: center;
gap: 24px;
& h1 {
color: #1a1a1a;
font-family: "Playfair Display", sans-serif;
font-size: 48px;
font-weight: 700;
letter-spacing: -0.5px;
}
& p {
max-width: 600px;
text-align: center;
color: #4b5563;
font-size: 18px;
font-weight: 400;
letter-spacing: -0.5px;
}
}
&-search {
position: relative;
& span {
position: absolute;
top: 50%;
right: 24px;
transform: translateY(-50%);
color: #8c8c8c;
}
& input {
background-color: $white;
border: 1px solid #e5e7eb;
border-radius: 50px;
padding: 18px 50px;
color: #8c8c8c;
font-size: 16px;
font-weight: 400;
letter-spacing: -0.5px;
&::placeholder {
color: #8c8c8c;
}
}
}
}
&__main {
background-color: $white;
padding-block: 70px;
border-top: 1px solid #f3f4f6;
&-wrapper {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 24px;
}
}
}
</style>