feat(notification): integrate global notification plugin using ElNotification

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.
This commit is contained in:
Alexandr SaVBaD Waltz 2026-03-01 15:30:47 +03:00
parent 2ea18eb8a6
commit 8d7685ef67
31 changed files with 257 additions and 199 deletions

View file

@ -6,14 +6,13 @@ declare module 'nuxt/schema' {
file: string;
default: boolean;
}>;
defaultLocale: string;
};
ui: {
showBreadcrumbs: boolean;
showSearchBar: boolean;
isHeaderFixed: boolean;
isAuthModals: boolean;
toastPosition: string;
notificationPosition: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
};
}
}

View file

@ -9,6 +9,6 @@ export default defineAppConfig({
showSearchBar: true,
isHeaderFixed: true,
isAuthModals: false,
toastPosition: 'top-right',
notificationPosition: 'top-right',
},
});

View file

@ -51,7 +51,6 @@ const appStore = useAppStore();
const { $appHelpers } = useNuxtApp();
const { isDemoMode, uiConfig } = useProjectConfig();
const toaster = { position: uiConfig.value.toastPosition };
const switchLocalePath = useSwitchLocalePath();
const showBreadcrumbs = computed(() => {

View file

@ -99,10 +99,9 @@
</div>
</div>
</div>
<div class="header__search" :class="[{ active: isSearchVisible }]">
<div class="header__search" :class="[{ active: isSearchVisible && uiConfig.showSearchBar }]">
<ui-search
ref="searchRef"
v-if="uiConfig.showSearchBar"
/>
</div>
</header>
@ -134,7 +133,6 @@ const cookieCart = useCookie($appHelpers.COOKIES_CART_KEY, {
default: () => [],
path: '/',
});
console.log(cookieCart.value)
const productsInCartQuantity = computed(() => {
if (isAuthenticated.value) {

View file

@ -1,5 +1,4 @@
import { useLocaleRedirect } from '@composables/languages';
import { useNotification } from '@composables/notification';
import { useUserBaseData } from '@composables/user';
import { LOGIN } from '@graphql/mutations/auth';
import type { ILoginResponse } from '@types';
@ -8,9 +7,10 @@ export function useLogin() {
const { t } = useI18n();
const userStore = useUserStore();
const localePath = useLocalePath();
const { $appHelpers } = useNuxtApp();
const { $appHelpers, $notify } = useNuxtApp();
const { checkAndRedirect } = useLocaleRedirect();
const { loadUserBaseData } = useUserBaseData();
const cookieRefresh = useCookie($appHelpers.COOKIES_REFRESH_TOKEN_KEY, {
default: () => '',
@ -25,9 +25,11 @@ export function useLogin() {
path: '/',
});
const { mutate, loading, error } = useMutation<ILoginResponse>(LOGIN);
const { mutate, loading } = useMutation<ILoginResponse>(LOGIN);
async function login(email: string, password: string, isStayLogin: boolean) {
try {
const result = await mutate({
email,
password,
@ -44,7 +46,7 @@ export function useLogin() {
navigateTo(localePath('/'));
useNotification({
$notify({
message: t('popup.success.login'),
type: 'success',
});
@ -53,11 +55,8 @@ export function useLogin() {
await checkAndRedirect(authData.user.language);
}
await useUserBaseData(authData.user.email);
}
watch(error, (err) => {
if (!err) return;
await loadUserBaseData(authData.user.email);
} catch (err: unknown) {
console.error('useLogin error:', err);
let message = t('popup.errors.defaultError');
if (isGraphQLError(err)) {
@ -65,12 +64,13 @@ export function useLogin() {
} else {
message = err.message;
}
useNotification({
$notify({
message,
type: 'error',
title: t('popup.errors.main'),
});
});
}
}
return {
loading,

View file

@ -1,10 +1,10 @@
import { useNotification } from '@composables/notification';
import { NEW_PASSWORD } from '@graphql/mutations/auth.js';
import type { INewPasswordResponse } from '@types';
export function useNewPassword() {
const { t } = useI18n();
const router = useRouter();
const { $notify } = useNuxtApp();
const localePath = useLocalePath();
const token = useRouteQuery('token', '');
@ -21,7 +21,7 @@ export function useNewPassword() {
});
if (result?.data?.confirmResetPassword.success) {
useNotification({
$notify({
message: t('popup.success.newPassword'),
type: 'success',
});
@ -43,7 +43,7 @@ export function useNewPassword() {
} else {
message = err.message;
}
useNotification({
$notify({
message,
type: 'error',
title: t('popup.errors.main'),

View file

@ -1,9 +1,9 @@
import { useNotification } from '@composables/notification';
import { RESET_PASSWORD } from '@graphql/mutations/auth.js';
import type { IPasswordResetResponse } from '@types';
export function usePasswordReset() {
const { t } = useI18n();
const { $notify } = useNuxtApp();
const appStore = useAppStore();
const { mutate, loading, error } = useMutation<IPasswordResetResponse>(RESET_PASSWORD);
@ -14,7 +14,7 @@ export function usePasswordReset() {
});
if (result?.data?.resetPassword.success) {
useNotification({
$notify({
message: t('popup.success.reset'),
type: 'success',
});
@ -32,7 +32,7 @@ export function usePasswordReset() {
} else {
message = err.message;
}
useNotification({
$notify({
message,
type: 'error',
title: t('popup.errors.main'),

View file

@ -1,6 +1,5 @@
import { useLogout } from '@composables/auth';
import { useLocaleRedirect } from '@composables/languages';
import { useNotification } from '@composables/notification';
import { useUserBaseData } from '@composables/user';
import { REFRESH } from '@graphql/mutations/auth';
@ -9,9 +8,10 @@ export function useRefresh() {
const router = useRouter();
const localePath = useLocalePath();
const userStore = useUserStore();
const { $appHelpers } = useNuxtApp();
const { $appHelpers, $notify } = useNuxtApp();
const { checkAndRedirect } = useLocaleRedirect();
const { loadUserBaseData } = useUserBaseData();
const { logout } = useLogout();
const { mutate, loading, error } = useMutation(REFRESH);
@ -65,7 +65,7 @@ export function useRefresh() {
await checkAndRedirect(data.user.language);
}
await useUserBaseData(data.user.email);
await loadUserBaseData(data.user.email);
} catch (err) {
if (isTokenInvalidError(err)) {
await logout();
@ -83,7 +83,7 @@ export function useRefresh() {
message = err;
}
useNotification({
$notify({
message,
type: 'error',
title: t('popup.errors.main'),
@ -107,7 +107,7 @@ export function useRefresh() {
} else {
message = err.message;
}
useNotification({
$notify({
message,
type: 'error',
title: t('popup.errors.main'),

View file

@ -1,4 +1,3 @@
import { useNotification } from '@composables/notification';
import { useMailClient } from '@composables/utils';
import { REGISTER } from '@graphql/mutations/auth.js';
import type { IRegisterResponse } from '@types';
@ -16,6 +15,7 @@ interface IRegisterArguments {
export function useRegister() {
const { t } = useI18n();
const { $notify } = useNuxtApp();
const appStore = useAppStore();
const { mailClientUrl, detectMailClient, openMailClient } = useMailClient();
@ -37,7 +37,7 @@ export function useRegister() {
if (result?.data?.createUser?.success) {
detectMailClient(payload.email);
useNotification({
$notify({
message: h('div', [
h('p', t('popup.success.register')),
mailClientUrl.value
@ -69,7 +69,7 @@ export function useRegister() {
} else {
message = err.message;
}
useNotification({
$notify({
message,
type: 'error',
title: t('popup.errors.main'),

View file

@ -1,10 +1,10 @@
import { useNotification } from '@composables/notification';
import type { AppConfig } from 'nuxt/schema';
export type DemoConfigKey = keyof AppConfig['ui'];
export const useProjectConfig = () => {
const appConfig = useAppConfig();
const { $notify } = useNuxtApp();
const { t } = useI18n();
const demoOverridesCookie = useCookie<Partial<AppConfig['ui']>>('nuxt-demo-overrides', {
@ -114,7 +114,7 @@ ${entries}
await navigator.clipboard.writeText(text);
useNotification({
$notify({
message: t('popup.success.configCopy'),
type: 'success',
});

View file

@ -1,4 +1,3 @@
import { useNotification } from '@composables/notification';
import { CONTACT_US } from '@graphql/mutations/contact';
import type { IContactUsResponse } from '@types';
@ -12,6 +11,7 @@ interface IContactUsArguments {
export function useContactUs() {
const { t } = useI18n();
const { $notify } = useNuxtApp();
const { mutate, loading, error } = useMutation<IContactUsResponse>(CONTACT_US);
@ -33,7 +33,7 @@ export function useContactUs() {
const result = await mutate(variables);
if (result?.data?.contactUs.received) {
useNotification({
$notify({
message: t('popup.success.contactUs'),
type: 'success',
});
@ -49,7 +49,7 @@ export function useContactUs() {
} else {
message = err.message;
}
useNotification({
$notify({
message,
type: 'error',
title: t('popup.errors.main'),

View file

@ -1,4 +1,3 @@
import { useNotification } from '@composables/notification';
import { MANAGE_FEEDBACK } from '@graphql/mutations/feedbacks';
import type { IFeedbackActionResponse } from '@types';
@ -11,6 +10,7 @@ interface IFeedbackActionArguments {
export function useFeedbackAction() {
const { t } = useI18n();
const { $notify } = useNuxtApp();
const userStore = useUserStore();
const isAuthenticated = computed(() => userStore.isAuthenticated);
@ -35,13 +35,13 @@ export function useFeedbackAction() {
const result = await mutate(variables);
if (result?.data?.feedback) {
useNotification({
$notify({
message: t('popup.success.addFeedback'),
type: 'success',
});
}
} else {
useNotification({
$notify({
message: t('popup.errors.loginFirst'),
type: 'error',
});
@ -57,7 +57,7 @@ export function useFeedbackAction() {
} else {
message = err.message;
}
useNotification({
$notify({
message,
type: 'error',
title: t('popup.errors.main'),

View file

@ -1,36 +1,50 @@
import type { VNodeChild } from 'vue';
import type { VNodeChild } from 'vue'
import type { NotificationOptions } from 'element-plus'
interface INotificationArguments {
message: string | VNodeChild;
type: string;
title?: string;
export interface INotificationArguments {
message: string | VNodeChild
type: 'success' | 'error' | 'warning' | 'info'
title?: string
}
export function useNotification(args: INotificationArguments) {
const duration = 5000;
export default defineNuxtPlugin(() => {
const duration = 5000
const config = useAppConfig()
function notify(args: INotificationArguments) {
const progressBar = h('div', {
class: 'el-notification__progress',
style: {
animationDuration: `${duration}ms`,
},
});
})
const bodyContent: VNodeChild = typeof args.message === 'string' ? h('p', args.message) : args.message;
const bodyContent =
typeof args.message === 'string'
? h('p', args.message)
: args.message
const messageVNode = h('div', [
bodyContent,
progressBar,
]);
])
const notification = ElNotification({
title: args.title,
duration: 0,
message: messageVNode,
type: args.type,
} as NotificationOptions);
position: config.ui.notificationPosition,
} as NotificationOptions)
setTimeout(() => {
notification.close();
}, duration);
}
notification.close()
}, duration)
}
return {
provide: {
notify,
},
}
})

View file

@ -1,9 +1,9 @@
import { useNotification } from '@composables/notification';
import { BUY_CART } from '@graphql/mutations/cart';
import type { IBuyOrderResponse } from '@types';
export function useOrderBuy() {
const { t } = useI18n();
const { $notify } = useNuxtApp();
const cartStore = useCartStore();
const orderUuid = computed(() => cartStore.currentOrder?.uuid);
@ -32,7 +32,7 @@ export function useOrderBuy() {
} else {
message = err.message;
}
useNotification({
$notify({
message,
type: 'error',
title: t('popup.errors.main'),

View file

@ -1,4 +1,3 @@
import { useNotification } from '@composables/notification';
import {
ADD_TO_CART,
BULK_CART,
@ -29,7 +28,7 @@ export function useOrderOverwrite() {
const { t } = useI18n();
const cartStore = useCartStore();
const userStore = useUserStore();
const { $appHelpers } = useNuxtApp();
const { $appHelpers, $notify } = useNuxtApp();
const isAuthenticated = computed(() => userStore.isAuthenticated);
const orderUuid = computed(() => cartStore.currentOrder?.uuid);
@ -78,7 +77,7 @@ export function useOrderOverwrite() {
if (addResult?.data?.addOrderProduct?.order) {
cartStore.setCurrentOrders(addResult.data.addOrderProduct.order);
useNotification({
$notify({
message: t('popup.success.addToCart', {
product: args.productName,
}),
@ -98,7 +97,7 @@ export function useOrderOverwrite() {
if (removeResult?.data?.removeOrderProduct?.order) {
cartStore.setCurrentOrders(removeResult.data.removeOrderProduct.order);
useNotification({
$notify({
message: t('popup.success.removeFromCart', {
product: args.productName,
}),
@ -118,7 +117,7 @@ export function useOrderOverwrite() {
if (removeKindResult?.data?.removeOrderProductsOfAKind?.order) {
cartStore.setCurrentOrders(removeKindResult.data.removeOrderProductsOfAKind.order);
useNotification({
$notify({
message: t('popup.success.removeFromCart', {
product: args.productName,
}),
@ -137,7 +136,7 @@ export function useOrderOverwrite() {
if (removeAllResult?.data?.removeAllOrderProducts?.order) {
cartStore.setCurrentOrders(removeAllResult.data.removeAllOrderProducts.order);
useNotification({
$notify({
message: t('popup.success.removeAllFromCart'),
type: 'success',
});
@ -155,7 +154,7 @@ export function useOrderOverwrite() {
if (bulkResult?.data?.bulkOrderAction?.order) {
cartStore.setCurrentOrders(bulkResult.data.bulkOrderAction.order);
useNotification({
$notify({
message: t('popup.success.bulkRemoveOrder'),
type: 'success',
});
@ -190,7 +189,7 @@ export function useOrderOverwrite() {
cookieCart.value = current;
useNotification({
$notify({
message: t('popup.success.addToCart', {
product: args.productName,
}),
@ -221,7 +220,7 @@ export function useOrderOverwrite() {
cookieCart.value = current;
useNotification({
$notify({
message: t('popup.success.removeFromCart', {
product: args.productName,
}),
@ -237,7 +236,7 @@ export function useOrderOverwrite() {
(item) => item.product.uuid !== args.productUuid
);
useNotification({
$notify({
message: t('popup.success.removeFromCart', {
product: args.productName,
}),
@ -250,7 +249,7 @@ export function useOrderOverwrite() {
case 'removeAll': {
cookieCart.value = [];
useNotification({
$notify({
message: t('popup.success.removeAllFromCart'),
type: 'success',
});
@ -267,7 +266,7 @@ export function useOrderOverwrite() {
if (bulkResult?.data?.bulkOrderAction?.order) {
cartStore.setCurrentOrders(bulkResult.data.bulkOrderAction.order);
useNotification({
$notify({
message: t('popup.success.bulkRemoveOrder'),
type: 'success',
});
@ -287,18 +286,14 @@ export function useOrderOverwrite() {
(errors) => {
const err = errors.find(Boolean);
if (!err) return;
console.error('useOrderOverwrite error:', err);
let message = t('popup.errors.defaultError');
if (isGraphQLError(err)) {
message = err.graphQLErrors?.[0]?.message || message;
} else {
message = err.message;
}
useNotification({
$notify({
message,
type: 'error',
title: t('popup.errors.main'),

View file

@ -1,4 +1,3 @@
import { useNotification } from '@composables/notification';
import { BUY_PRODUCT } from '@graphql/mutations/products';
import type { IBuyProductResponse } from '@types';
@ -23,7 +22,7 @@ export function useProductBuy() {
console.log(result?.data);
}
} else {
useNotification({
$notify({
message: t('popup.errors.loginFirst'),
type: 'error',
});
@ -39,7 +38,7 @@ export function useProductBuy() {
} else {
message = err.message;
}
useNotification({
$notify({
message,
type: 'error',
title: t('popup.errors.main'),

View file

@ -2,13 +2,15 @@ export function useValidators() {
const { t } = useI18n();
const required = (text: string) => {
if (text) return true;
if (text?.trim()) return true;
return t('errors.required');
};
const isEmail = (email: string) => {
if (!email) return required(email);
if (/.+@.+\..+/.test(email)) return true;
const value = email?.trim();
if (!value) return required(value);
if (/.+@.+\..+/.test(value)) return true;
return t('errors.mail');
};

View file

@ -1,9 +1,9 @@
import { useNotification } from '@composables/notification';
import { SEARCH } from '@graphql/mutations/search';
import type { ISearchResponse, ISearchResults } from '@types';
export function useSearch() {
const { t } = useI18n();
const { $notify } = useNuxtApp();
const searchResults = ref<ISearchResults | null>(null);
@ -39,7 +39,7 @@ export function useSearch() {
} else {
message = err.message;
}
useNotification({
$notify({
message,
type: 'error',
title: t('popup.errors.main'),

View file

@ -1,9 +1,9 @@
import { useNotification } from '@composables/notification';
import { UPLOAD_AVATAR } from '@graphql/mutations/user';
import type { IAvatarUploadResponse } from '@types';
export function useAvatarUpload() {
const { t } = useI18n();
const { $notify } = useNuxtApp();
const userStore = useUserStore();
const { mutate, onDone, error } = useMutation<IAvatarUploadResponse>(UPLOAD_AVATAR);
@ -27,7 +27,7 @@ export function useAvatarUpload() {
const user = data?.uploadAvatar.user;
if (user) {
userStore.setUser(user);
useNotification({
$notify({
message: t('popup.success.avatarUpload'),
type: 'success',
});
@ -43,7 +43,7 @@ export function useAvatarUpload() {
} else {
message = err.message;
}
useNotification({
$notify({
message,
type: 'error',
title: t('popup.errors.main'),

View file

@ -1,8 +1,8 @@
import { useNotification } from '@composables/notification';
import { DEPOSIT } from '@graphql/mutations/deposit';
export function useDeposit() {
const { t } = useI18n();
const { $notify } = useNuxtApp();
const { mutate, loading, error } = useMutation(DEPOSIT);
@ -29,7 +29,7 @@ export function useDeposit() {
} else {
message = err.message;
}
useNotification({
$notify({
message,
type: 'error',
title: t('popup.errors.main'),

View file

@ -1,9 +1,9 @@
import { useNotification } from '@composables/notification';
import { ACTIVATE_USER } from '@graphql/mutations/user.js';
import type { IUserActivationResponse } from '@types';
export function useUserActivation() {
const { t } = useI18n();
const { $notify } = useNuxtApp();
const { mutate, loading, error } = useMutation<IUserActivationResponse>(ACTIVATE_USER);
@ -14,7 +14,7 @@ export function useUserActivation() {
});
if (result?.data?.activateUser) {
useNotification({
$notify({
message: t('popup.activationSuccess'),
type: 'success',
});
@ -30,7 +30,7 @@ export function useUserActivation() {
} else {
message = err.message;
}
useNotification({
$notify({
message,
type: 'error',
title: t('popup.errors.main'),

View file

@ -4,7 +4,7 @@ import { useWishlistSync } from '@composables/wishlist';
import { getUserBaseData } from '@graphql/queries/combined/userBaseData';
import type { IUserBaseDataResponse } from '@types';
export async function useUserBaseData(userEmail: string) {
export function useUserBaseData() {
const wishlistStore = useWishlistStore();
const cartStore = useCartStore();
const promocodeStore = usePromocodeStore();
@ -12,14 +12,19 @@ export async function useUserBaseData(userEmail: string) {
const { syncWishlist } = useWishlistSync();
const { syncOrder } = useOrderSync();
async function loadUserBaseData(userEmail: string) {
const { document, variables } = getUserBaseData({
userEmail,
status: orderStatuses.PENDING,
});
const { mutate, error } = useMutation<IUserBaseDataResponse>(document);
const result = await mutate(variables);
if (error.value) {
console.error('useUserBaseData error:', error.value);
}
const data = result?.data;
if (data?.wishlists.edges) {
@ -35,12 +40,9 @@ export async function useUserBaseData(userEmail: string) {
if (data?.promocodes.edges) {
promocodeStore.setPromocodes(data.promocodes.edges);
}
watch(error, (err) => {
if (err) {
console.error('useUserBaseData error:', err);
}
});
return {};
return {
loadUserBaseData
};
}

View file

@ -1,13 +1,12 @@
import { useLogout } from '@composables/auth';
import { useLocaleRedirect } from '@composables/languages';
import { useNotification } from '@composables/notification';
import { UPDATE_USER } from '@graphql/mutations/user';
import type { IUserUpdatingResponse } from '@types';
export function useUserUpdating() {
const userStore = useUserStore();
const { t } = useI18n();
const { $appHelpers } = useNuxtApp();
const { $appHelpers, $notify } = useNuxtApp();
const { mutate, loading, error } = useMutation<IUserUpdatingResponse>(UPDATE_USER);
@ -53,7 +52,7 @@ export function useUserUpdating() {
// }
if (Object.keys(params).length === 0) {
useNotification({
$notify({
message: t('popup.errors.noDataToUpdate'),
type: 'error',
});
@ -66,14 +65,14 @@ export function useUserUpdating() {
if (userEmail.value !== email) {
await logout();
useNotification({
$notify({
message: t('popup.success.emailUpdate'),
type: 'info',
});
} else {
userStore.setUser(data.user);
useNotification({
$notify({
message: t('popup.success.userUpdate'),
type: 'success',
});
@ -94,7 +93,7 @@ export function useUserUpdating() {
} else {
message = err.message;
}
useNotification({
$notify({
message,
type: 'error',
title: t('popup.errors.main'),

View file

@ -1,4 +1,3 @@
import {useNotification} from '@composables/notification';
import {
ADD_TO_WISHLIST,
BULK_WISHLIST,
@ -27,7 +26,7 @@ export function useWishlistOverwrite() {
const { t } = useI18n();
const wishlistStore = useWishlistStore();
const userStore = useUserStore();
const { $appHelpers } = useNuxtApp();
const { $appHelpers, $notify } = useNuxtApp();
const isAuthenticated = computed(() => userStore.isAuthenticated);
const wishlistUuid = computed(() => wishlistStore.wishlist?.uuid);
@ -71,7 +70,7 @@ export function useWishlistOverwrite() {
if (addResult?.data?.addWishlistProduct?.wishlist) {
wishlistStore.setWishlist(addResult.data.addWishlistProduct.wishlist);
useNotification({
$notify({
message: t('popup.success.addToWishlist', {
product: args.productName,
}),
@ -91,7 +90,7 @@ export function useWishlistOverwrite() {
if (removeResult?.data?.removeWishlistProduct?.wishlist) {
wishlistStore.setWishlist(removeResult.data.removeWishlistProduct.wishlist);
useNotification({
$notify({
message: t('popup.success.removeFromWishlist', {
product: args.productName,
}),
@ -111,7 +110,7 @@ export function useWishlistOverwrite() {
if (removeAllResult?.data?.removeAllWishlistProducts?.wishlist) {
wishlistStore.setWishlist(removeAllResult.data.removeAllWishlistProducts.wishlist);
useNotification({
$notify({
message: t('popup.success.removeAllFromWishlist'),
type: 'success',
});
@ -133,7 +132,7 @@ export function useWishlistOverwrite() {
if (args.isBulkSync) {
cookieWishlist.value = [];
} else {
useNotification({
$notify({
message: t('popup.success.bulkRemoveWishlist'),
type: 'success',
});
@ -154,7 +153,7 @@ export function useWishlistOverwrite() {
: [];
if (current.includes(args.productUuid)) {
useNotification({
$notify({
message: t('popup.errors.alreadyInWishlist', {
product: args.productName,
}),
@ -166,7 +165,7 @@ export function useWishlistOverwrite() {
current.push(args.productUuid);
cookieWishlist.value = current;
useNotification({
$notify({
message: t('popup.success.addToWishlist', {
product: args.productName,
}),
@ -183,7 +182,7 @@ export function useWishlistOverwrite() {
)
: [];
useNotification({
$notify({
message: t('popup.success.removeFromWishlist', {
product: args.productName,
}),
@ -196,7 +195,7 @@ export function useWishlistOverwrite() {
case 'removeAll': {
cookieWishlist.value = [];
useNotification({
$notify({
message: t('popup.success.removeAllFromWishlist'),
type: 'success',
});
@ -217,7 +216,7 @@ export function useWishlistOverwrite() {
if (args.isBulkSync) {
cookieWishlist.value = [];
} else {
useNotification({
$notify({
message: t('popup.success.bulkRemoveWishlist'),
type: 'success',
});
@ -245,7 +244,7 @@ export function useWishlistOverwrite() {
} else {
message = err.message;
}
useNotification({
$notify({
message,
type: 'error',
title: t('popup.errors.main'),

View file

@ -51,14 +51,9 @@ export function useWishlistSync() {
cookieWishlist.value = [];
}
} catch (err) {
console.error('Failed to sync wishlist:', err);
}
}
watch(syncError, (err) => {
if (!err) return;
console.error('useWishlistSync error:', err);
});
}
}
return {
syncWishlist,

View file

@ -14,9 +14,9 @@ import { useRouteQuery } from '@vueuse/router';
import { useBrands } from '@composables/brands';
import { usePosts } from '@composables/posts';
import { useProducts, useProductTags } from '@composables/products';
import { useNotification } from '@composables/notification';
const {t} = useI18n();
const { $notify } = useNuxtApp();
const appStore = useAppStore();
const route = useRoute();
@ -47,7 +47,7 @@ onMounted( async () => {
}
if (route.path.includes('payment')) {
useNotification({
$notify({
message: t('popup.payment'),
type: 'info'
});

View file

@ -40,10 +40,10 @@
<script setup lang="ts">
import {usePageTitle} from '@composables/utils';
import {useNotification} from '@composables/notification';
import {useDate} from '@composables/date';
const {t, locale} = useI18n();
const { $notify } = useNuxtApp();
const promocodeStore = usePromocodeStore();
const promocodes = computed(() => promocodeStore.promocodes);
@ -51,7 +51,7 @@ const promocodes = computed(() => promocodeStore.promocodes);
const copyCode = (code: string) => {
navigator.clipboard.writeText(code)
.then(() => {
useNotification({
$notify({
message: t('popup.success.promocodeCopy'),
type: 'success'
});

View file

@ -50,11 +50,10 @@
import {usePageTitle} from '@composables/utils';
import { useDate } from '@composables/date';
import {useAvatarUpload} from '@composables/user';
import {useNotification} from '@composables/notification';
const {t} = useI18n();
const userStore = useUserStore();
const { $appHelpers } = useNuxtApp();
const { $appHelpers, $notify } = useNuxtApp();
const { uploadAvatar } = useAvatarUpload();
@ -86,7 +85,7 @@ const copyReferral = () => {
if (finishedOrdersQuantity.value > 0) {
navigator.clipboard.writeText(referralLink.value)
.then(() => {
useNotification({
$notify({
message: t('popup.success.referralCopy'),
type: 'success'
});

View file

@ -0,0 +1,52 @@
import type { NotificationOptions } from 'element-plus';
import type { VNodeChild } from 'vue';
export interface INotificationArguments {
message: string | VNodeChild;
type: 'success' | 'error' | 'warning' | 'info';
title?: string;
}
export default defineNuxtPlugin(() => {
const config = useAppConfig();
function notify(args: INotificationArguments) {
if (process.server) return;
const duration = 5000;
const progressBar = h('div', {
class: 'el-notification__progress',
style: {
animationDuration: `${duration}ms`,
},
});
const bodyContent =
typeof args.message === 'string'
? h('p', args.message)
: args.message;
const messageVNode = h('div', [
bodyContent,
progressBar,
]);
const notification = ElNotification({
title: args.title,
duration: 0,
message: messageVNode,
type: args.type,
position: config.ui.notificationPosition,
} as NotificationOptions);
setTimeout(() => {
notification.close();
}, duration);
}
return {
provide: {
notify,
},
}
});

View file

@ -1,5 +1,6 @@
import type { ApolloClient, NormalizedCacheObject } from '@apollo/client/core';
import type { ApolloError } from '@apollo/client/errors';
import type { INotificationArguments } from '~/plugins/03.notification'
declare module '#app' {
interface NuxtApp {
@ -17,6 +18,7 @@ declare module '#app' {
$apollo: {
defaultClient: ApolloClient<NormalizedCacheObject>;
};
$notify: (args: INotificationArguments) => void
}
interface NuxtConfig {

View file

@ -15,9 +15,13 @@
"path": "./.nuxt/tsconfig.node.json"
}
],
"compilerOptions": {
"types": ["@types/node"]
},
"include": [
"./**/*.d.ts",
"./**/*.ts",
"./**/*.vue"
"./**/*.vue",
"types/**/*.d.ts"
]
}