Added a global `notify` method via Nuxt plugin to replace `useNotification`. Improved messaging structure by embedding progress bars and handled dynamic durations. Updated usage across composables and components for consistency. - Replaced `useNotification` with `$notify` in all applicable files. - Updated `app.config.ts` to support customizable notification positions. - Refactored affected composables for simplified notification calls. - Enhanced progress indicator display within notifications. Breaking Changes: `useNotification` is removed, requiring migration to the new `$notify` API.
371 lines
No EOL
8.4 KiB
Vue
371 lines
No EOL
8.4 KiB
Vue
<template>
|
|
<header
|
|
class="header"
|
|
:class="[{
|
|
'header__no-search': !uiConfig.showSearchBar,
|
|
'header__fixed': uiConfig.isHeaderFixed
|
|
}]"
|
|
>
|
|
<div class="container">
|
|
<div class="header__wrapper">
|
|
<div class="header__inner">
|
|
<nuxt-link-locale to="/" class="header__logo">
|
|
SCHON
|
|
</nuxt-link-locale>
|
|
</div>
|
|
<div class="header__inner">
|
|
<nav class="header__nav">
|
|
<nuxt-link-locale
|
|
to="/shop"
|
|
class="header__nav-item"
|
|
:class="[{ active: route.name.includes('shop') }]"
|
|
>
|
|
{{ t('header.nav.shop') }}
|
|
</nuxt-link-locale>
|
|
<nuxt-link-locale
|
|
to="/catalog"
|
|
class="header__nav-item"
|
|
:class="[{ active: route.name.includes('catalog') }]"
|
|
>
|
|
{{ t('header.nav.catalog') }}
|
|
</nuxt-link-locale>
|
|
<nuxt-link-locale
|
|
to="/brands"
|
|
class="header__nav-item"
|
|
:class="[{ active: route.name.includes('brands') }]"
|
|
>
|
|
{{ t('header.nav.brands') }}
|
|
</nuxt-link-locale>
|
|
<nuxt-link-locale
|
|
to="/blog"
|
|
class="header__nav-item"
|
|
:class="[{ active: route.name.includes('blog') }]"
|
|
>
|
|
{{ t('header.nav.blog') }}
|
|
</nuxt-link-locale>
|
|
<nuxt-link-locale
|
|
to="/contact"
|
|
class="header__nav-item"
|
|
:class="[{ active: route.name.includes('contact') }]"
|
|
>
|
|
{{ t('header.nav.contact') }}
|
|
</nuxt-link-locale>
|
|
</nav>
|
|
</div>
|
|
<div class="header__inner">
|
|
<div class="header__block">
|
|
<icon
|
|
@click="isSearchVisible = true"
|
|
class="header__block-search"
|
|
name="tabler:search"
|
|
size="20"
|
|
/>
|
|
<ui-language-switcher />
|
|
<el-badge :value="productsInWishlistQuantity" color="#111827">
|
|
<nuxt-link-locale to="/wishlist">
|
|
<icon class="header__block-wishlist" name="material-symbols:favorite-rounded" size="20" />
|
|
</nuxt-link-locale>
|
|
</el-badge>
|
|
<el-badge :value="productsInCartQuantity" color="#111827">
|
|
<nuxt-link-locale to="/cart">
|
|
<icon class="header__block-cart" name="bx:bxs-shopping-bag" size="20" />
|
|
</nuxt-link-locale>
|
|
</el-badge>
|
|
<nuxt-link-locale
|
|
to="/profile/settings"
|
|
class="header__block-item"
|
|
v-if="isAuthenticated"
|
|
>
|
|
<nuxt-img
|
|
class="header__block-avatar"
|
|
v-if="user?.avatar"
|
|
:src="user?.avatar"
|
|
alt="avatar"
|
|
format="webp"
|
|
densities="x1"
|
|
/>
|
|
<div class="header__block-profile" v-else>
|
|
<icon name="clarity:avatar-line" size="16" />
|
|
</div>
|
|
</nuxt-link-locale>
|
|
<nuxt-link-locale
|
|
to="/auth/sign-in"
|
|
class="header__block-auth"
|
|
v-else
|
|
>
|
|
<p>{{ t('buttons.login') }}</p>
|
|
</nuxt-link-locale>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="header__search" :class="[{ active: isSearchVisible && uiConfig.showSearchBar }]">
|
|
<ui-search
|
|
ref="searchRef"
|
|
/>
|
|
</div>
|
|
</header>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { useProjectConfig } from "@composables/config";
|
|
import {onClickOutside} from "@vueuse/core";
|
|
|
|
const { t } = useI18n();
|
|
const localePath = useLocalePath();
|
|
const route = useRoute();
|
|
const appStore = useAppStore();
|
|
const userStore = useUserStore();
|
|
const wishlistStore = useWishlistStore();
|
|
const cartStore = useCartStore();
|
|
const { $appHelpers } = useNuxtApp();
|
|
|
|
const { uiConfig } = useProjectConfig();
|
|
|
|
const isAuthenticated = computed(() => userStore.isAuthenticated);
|
|
const user = computed(() => userStore.user);
|
|
|
|
const cookieWishlist = useCookie($appHelpers.COOKIES_WISHLIST_KEY, {
|
|
default: () => [],
|
|
path: '/',
|
|
});
|
|
const cookieCart = useCookie($appHelpers.COOKIES_CART_KEY, {
|
|
default: () => [],
|
|
path: '/',
|
|
});
|
|
|
|
const productsInCartQuantity = computed(() => {
|
|
if (isAuthenticated.value) {
|
|
let count = 0;
|
|
cartStore.currentOrder?.orderProducts?.edges.forEach((el) => {
|
|
count = count + el.node.quantity;
|
|
});
|
|
return count;
|
|
} else {
|
|
return cookieCart.value.reduce((acc, item) => acc + item.quantity, 0);
|
|
}
|
|
});
|
|
const productsInWishlistQuantity = computed(() => {
|
|
if (isAuthenticated.value) {
|
|
return wishlistStore.wishlist ? wishlistStore.wishlist.products.edges.length : 0;
|
|
} else {
|
|
return cookieWishlist.value.length
|
|
}
|
|
});
|
|
|
|
const isSearchVisible = ref<boolean>(false);
|
|
const searchRef = ref(null);
|
|
onClickOutside(searchRef, () => isSearchVisible.value = false);
|
|
|
|
const redirectTo = (to) => {
|
|
if (uiConfig.value.isAuthModals) {
|
|
appStore.setActiveAuthState(to);
|
|
} else {
|
|
navigateTo(localePath(`/auth/ + ${to}`));
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.header {
|
|
position: relative;
|
|
z-index: 5;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100vw;
|
|
background-color: rgba(255, 255, 255, 0.9);
|
|
border-bottom: 1px solid #f3f4f6;
|
|
|
|
&__no-search {
|
|
padding-inline: 75px;
|
|
|
|
& .header__inner {
|
|
gap: 75px;
|
|
}
|
|
}
|
|
|
|
&__fixed {
|
|
position: fixed;
|
|
}
|
|
|
|
&__wrapper {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 25px;
|
|
padding-block: 25px;
|
|
}
|
|
|
|
&__inner {
|
|
width: 33%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
|
|
&:first-child {
|
|
justify-content: flex-start;
|
|
}
|
|
&:last-child {
|
|
justify-content: flex-end;
|
|
}
|
|
}
|
|
|
|
&__logo {
|
|
transition: 0.2s;
|
|
font-size: 24px;
|
|
font-weight: 600;
|
|
letter-spacing: 6.7px;
|
|
font-family: 'Playfair Display', sans-serif;
|
|
color: #1a1a1a;
|
|
|
|
@include hover {
|
|
text-shadow: 0 0 5px #1a1a1a;
|
|
}
|
|
}
|
|
|
|
&__nav {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 40px;
|
|
|
|
&-item {
|
|
position: relative;
|
|
transition: 0.2s;
|
|
color: #1f2937;
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
letter-spacing: -0.5px;
|
|
|
|
&::after {
|
|
content: "";
|
|
position: absolute;
|
|
bottom: -3px;
|
|
left: 0;
|
|
height: 2px;
|
|
width: 0;
|
|
transition: all .3s ease;
|
|
background-color: #1f2937;
|
|
}
|
|
|
|
&.active::after {
|
|
width: 100%;
|
|
}
|
|
|
|
@include hover {
|
|
&::after {
|
|
width: 100%;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
&__block {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
|
|
&-block {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 15px;
|
|
}
|
|
|
|
&-search {
|
|
cursor: pointer;
|
|
display: block;
|
|
transition: 0.2s;
|
|
color: #374151;
|
|
|
|
@include hover {
|
|
color: #29323f;
|
|
}
|
|
}
|
|
|
|
&-wishlist {
|
|
display: block;
|
|
cursor: pointer;
|
|
transition: 0.2s;
|
|
color: #374151;
|
|
|
|
@include hover {
|
|
color: #29323f;
|
|
}
|
|
}
|
|
|
|
&-cart {
|
|
display: block;
|
|
cursor: pointer;
|
|
transition: 0.2s;
|
|
color: #374151;
|
|
|
|
@include hover {
|
|
color: #29323f;
|
|
}
|
|
}
|
|
|
|
&-auth {
|
|
transition: 0.2s;
|
|
border-bottom: 1px solid #111827;
|
|
padding-bottom: 2px;
|
|
color: #111827;
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
letter-spacing: -0.5px;
|
|
|
|
@include hover {
|
|
color: #424c62;
|
|
}
|
|
}
|
|
|
|
&-avatar {
|
|
width: 28px;
|
|
border-radius: 50%;
|
|
border: 1px solid #374151;
|
|
transition: 0.2s;
|
|
|
|
& span {
|
|
display: block;
|
|
}
|
|
|
|
@include hover {
|
|
opacity: 0.7;
|
|
}
|
|
}
|
|
|
|
&-profile {
|
|
width: 28px;
|
|
border-radius: 50%;
|
|
padding: 5px;
|
|
border: 1px solid #374151;
|
|
transition: 0.2s;
|
|
|
|
& span {
|
|
display: block;
|
|
}
|
|
|
|
@include hover {
|
|
opacity: 0.7;
|
|
}
|
|
}
|
|
}
|
|
|
|
&__search {
|
|
position: absolute;
|
|
width: 100%;
|
|
top: 100%;
|
|
left: 0;
|
|
overflow: hidden;
|
|
display: grid;
|
|
grid-template-rows: 0fr;
|
|
transition: grid-template-rows 0.2s ease;
|
|
|
|
&.active {
|
|
grid-template-rows: 1fr;
|
|
}
|
|
|
|
& > * {
|
|
min-height: 0;
|
|
}
|
|
}
|
|
}
|
|
</style> |