Introduced address management functionality, including address creation, deletion, and display with full localization support. Integrated GraphQL queries, mutations, and reusable composables for backend communication. - Added `addresses.vue` to profile for managing user addresses. - Implemented `useAddressCreate`, `useAddressDelete`, and `useAddressAutocomplete` composables. - Created reusable components: `forms/address.vue` and `cards/address.vue`. - Updated `navigation.vue` to include addresses in profile navigation. - Enhanced localization files for address-related translations. This improves user experience by enabling comprehensive address management in the profile section. No breaking changes.
297 lines
No EOL
6.3 KiB
Vue
297 lines
No EOL
6.3 KiB
Vue
<template>
|
|
<div class="search">
|
|
<div class="container">
|
|
<div class="search__inner">
|
|
<div
|
|
@click="toggleSearch(true)"
|
|
class="search__wrapper"
|
|
:class="[{ active: isSearchActive }]"
|
|
>
|
|
<form class="search__form" @submit.prevent="submitSearch">
|
|
<input
|
|
type="text"
|
|
v-model="query"
|
|
:placeholder="t('fields.search')"
|
|
inputmode="search"
|
|
/>
|
|
<div class="search__tools">
|
|
<button
|
|
type="button"
|
|
@click="clearSearch"
|
|
v-if="query"
|
|
>
|
|
<icon name="gridicons:cross" size="16" />
|
|
</button>
|
|
<div class="search__tools-line" v-if="query"></div>
|
|
<button type="submit">
|
|
<icon name="tabler:search" size="16" />
|
|
</button>
|
|
</div>
|
|
</form>
|
|
<div class="search__results" :class="[{ active: (searchResults && isSearchActive) || loading }]">
|
|
<skeletons-ui-search v-if="loading"/>
|
|
<div
|
|
class="search__results-inner"
|
|
v-for="(blocks, category) in filteredSearchResults"
|
|
:key="category"
|
|
>
|
|
<div class="search__results-title">
|
|
<p>{{ getBlockTitle(category) }}:</p>
|
|
</div>
|
|
<div
|
|
class="search__item"
|
|
v-for="item in blocks"
|
|
:key="item.uuid"
|
|
@click.stop="goTo(category, item)"
|
|
>
|
|
<div class="search__item-left">
|
|
<icon name="ic:twotone-search" size="18" />
|
|
<p>{{ item.name }}</p>
|
|
</div>
|
|
<icon name="line-md:external-link" size="18" />
|
|
</div>
|
|
</div>
|
|
<div class="search__results-empty" v-if="!hasResults && query && !loading">
|
|
<p>{{ t('header.search.empty') }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<transition name="opacity" mode="out-in">
|
|
<div
|
|
class="search__bg"
|
|
@click="toggleSearch(false)"
|
|
v-if="isSearchActive"
|
|
/>
|
|
</transition>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { useSearchUI } from '@composables/search';
|
|
|
|
const {t} = useI18n();
|
|
const router = useRouter();
|
|
const appStore = useAppStore();
|
|
|
|
const {
|
|
query,
|
|
isSearchActive,
|
|
loading,
|
|
searchResults,
|
|
filteredSearchResults,
|
|
hasResults,
|
|
getBlockTitle,
|
|
clearSearch,
|
|
toggleSearch
|
|
} = useSearchUI();
|
|
|
|
function submitSearch() {
|
|
if (query.value) {
|
|
router.push({
|
|
path: '/search',
|
|
query: { q: query.value }
|
|
})
|
|
|
|
toggleSearch(false);
|
|
}
|
|
}
|
|
|
|
watch(
|
|
() => isSearchActive.value,
|
|
(state) => {
|
|
appStore.setOverflowHidden(state);
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
|
|
function goTo(category: string, item: any) {
|
|
let path = "/";
|
|
|
|
switch (category) {
|
|
case "products": {
|
|
path = `/product/${item.slug}`;
|
|
break;
|
|
}
|
|
case "categories": {
|
|
path = `/catalog/${item.slug}`;
|
|
break;
|
|
}
|
|
case "brands": {
|
|
path = `/brand/${item.slug}`;
|
|
break;
|
|
}
|
|
case "posts": {
|
|
path = "/";
|
|
break;
|
|
}
|
|
}
|
|
|
|
toggleSearch(false);
|
|
router.push(path);
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.search {
|
|
width: 100%;
|
|
background-color: $main;
|
|
|
|
&__inner {
|
|
padding-block: 10px;
|
|
width: 100%;
|
|
position: relative;
|
|
z-index: 1;
|
|
height: 100%;
|
|
}
|
|
|
|
&__bg {
|
|
background-color: rgba(0, 0, 0, 0.2);
|
|
height: 100vh;
|
|
left: 0;
|
|
position: fixed;
|
|
top: 0;
|
|
width: 100vw;
|
|
z-index: 1;
|
|
}
|
|
|
|
&__wrapper {
|
|
width: 100%;
|
|
background-color: $border;
|
|
border-radius: $less_border_radius;
|
|
position: relative;
|
|
z-index: 2;
|
|
|
|
&.active {
|
|
background-color: $main;
|
|
box-shadow: 0 0 0 1px #0000000a,0 4px 4px #0000000a,0 20px 40px #00000014;
|
|
|
|
& .search__wrapper {
|
|
border-radius: $less_border_radius $less_border_radius 0 0;
|
|
}
|
|
|
|
& .search__form input {
|
|
border-radius: $less_border_radius $less_border_radius 0 0;
|
|
}
|
|
}
|
|
|
|
@include hover {
|
|
background-color: $main;
|
|
box-shadow: 0 0 0 1px #0000000a,0 4px 4px #0000000a,0 20px 40px #00000014;
|
|
}
|
|
}
|
|
|
|
&__form {
|
|
width: 100%;
|
|
height: 40px;
|
|
position: relative;
|
|
|
|
& input {
|
|
background-color: transparent;
|
|
width: 100%;
|
|
height: 100%;
|
|
padding-inline: 20px 150px;
|
|
border: 1px solid $link_secondary;
|
|
border-radius: $less_border_radius;
|
|
|
|
color: $secondary;
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
}
|
|
}
|
|
|
|
&__tools {
|
|
position: absolute;
|
|
right: 20px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
|
|
& button {
|
|
cursor: pointer;
|
|
display: grid;
|
|
place-items: center;
|
|
border-radius: $less_border_radius;
|
|
padding: 5px 12px;
|
|
border: 1px solid $primary;
|
|
background-color: transparent;
|
|
|
|
font-size: 12px;
|
|
color: $primary;
|
|
|
|
@include hover {
|
|
background-color: $primary;
|
|
color: $main;
|
|
}
|
|
}
|
|
|
|
&-line {
|
|
background-color: $primary;
|
|
height: 15px;
|
|
width: 1px;
|
|
}
|
|
}
|
|
|
|
&__results {
|
|
position: absolute;
|
|
z-index: 1;
|
|
top: 100%;
|
|
width: 100%;
|
|
max-height: 0;
|
|
overflow: auto;
|
|
background-color: $main;
|
|
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.0392156863), 0 4px 4px rgba(0, 0, 0, 0.0392156863), 0 20px 40px rgba(0, 0, 0, 0.0784313725);
|
|
|
|
&.active {
|
|
max-height: 40vh;
|
|
}
|
|
|
|
&-title {
|
|
background-color: rgba($primary, 0.2);
|
|
padding: 7px 20px;
|
|
|
|
font-weight: 600;
|
|
}
|
|
|
|
&-empty {
|
|
padding: 10px 20px;
|
|
font-size: 14px;
|
|
}
|
|
}
|
|
|
|
&__item {
|
|
cursor: pointer;
|
|
padding: 7px 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 30px;
|
|
font-size: 14px;
|
|
|
|
@include hover {
|
|
background-color: $main_hover;
|
|
}
|
|
|
|
&-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 15px;
|
|
}
|
|
|
|
& p {
|
|
word-break: break-all;
|
|
display: -webkit-box;
|
|
-webkit-box-orient: vertical;
|
|
-webkit-line-clamp: 1;
|
|
overflow: hidden;
|
|
}
|
|
|
|
& span {
|
|
color: $text;
|
|
}
|
|
}
|
|
}
|
|
</style> |