From c60ac13e880bada1669d05a1853be9950c2e8ddc Mon Sep 17 00:00:00 2001 From: Alexandr SaVBaD Waltz Date: Tue, 8 Jul 2025 23:41:31 +0300 Subject: [PATCH] Features: 1) Introduce `handleDeposit` function with validation logic and deposit transaction flow; 2) Add `useDeposit` composable and `balance.vue` page for user account balance management; 3) Enhance wishlist and cart functionality with authentication checks and notification improvements; Fixes: 1) Replace `ElNotification` with `useNotification` across all components and composables; 2) Add missing semicolons, consistent formatting, and type annotations in multiple files; 3) Resolve non-reactive elements in wishlist and cart state management; Extra: 1) Update i18n translations with new strings for promocodes, balance, authentication, and profile settings; 2) Refactor SCSS styles including variable additions and component-specific tweaks; 3) Remove redundant queries, unused imports, and `storePage.ts` file for cleanup. --- storefront/app.vue | 14 +- .../assets/styles/global/variables.scss | 1 + storefront/components/base/header/index.vue | 8 +- storefront/components/base/header/search.vue | 1 + storefront/components/cards/product.vue | 172 +++++++++---- storefront/components/forms/contact.vue | 2 + storefront/components/forms/deposit.vue | 56 +++-- storefront/components/forms/login.vue | 5 +- storefront/components/forms/register.vue | 56 +++-- .../components/forms/reset-password.vue | 1 + storefront/components/forms/update.vue | 172 ++++++------- storefront/components/profile/navigation.vue | 133 +++++++---- storefront/components/ui/input.vue | 8 +- .../components/ui/language-switcher.vue | 31 ++- storefront/components/ui/link.vue | 10 +- storefront/composables/auth/useLogin.ts | 27 ++- storefront/composables/auth/useNewPassword.ts | 18 +- .../composables/auth/usePasswordReset.ts | 18 +- storefront/composables/auth/useRefresh.ts | 13 +- storefront/composables/auth/useRegister.ts | 52 ++-- storefront/composables/config/useAppConfig.ts | 2 + .../composables/contact/useContactUs.ts | 18 +- storefront/composables/date/index.ts | 1 + storefront/composables/date/useDate.ts | 14 ++ .../notification/useNotification.ts | 48 ++-- .../composables/orders/useOrderOverwrite.ts | 157 ++++++------ storefront/composables/promocodes/index.ts | 1 + .../composables/promocodes/usePromocodes.ts | 24 ++ storefront/composables/search/useSearch.ts | 10 +- storefront/composables/user/index.ts | 5 +- .../composables/user/useAvatarUpload.ts | 52 ++++ storefront/composables/user/useDeposit.ts | 42 ++++ .../composables/user/useUserActivation.ts | 18 +- .../composables/user/useUserUpdating.ts | 116 +++++++++ .../composables/wishlist/useWishlist.ts | 4 +- .../wishlist/useWishlistOverwrite.ts | 129 +++++----- storefront/config/constants.ts | 4 +- .../graphql/fragments/promocodes.fragment.ts | 12 + storefront/graphql/fragments/user.fragment.ts | 5 + storefront/graphql/mutations/auth.ts | 6 +- storefront/graphql/mutations/user.ts | 15 ++ .../graphql/queries/combined/storePage.ts | 24 -- .../graphql/queries/standalone/promocodes.ts | 14 ++ storefront/i18n/locales/en-gb.json | 32 ++- storefront/nuxt.config.ts | 4 +- storefront/package-lock.json | 51 ++-- storefront/package.json | 1 + storefront/pages/brand/[uuid].vue | 2 +- storefront/pages/product/[slug].vue | 56 ++++- storefront/pages/profile/balance.vue | 33 +++ storefront/pages/profile/cart.vue | 7 +- storefront/pages/profile/promocodes.vue | 88 +++++++ storefront/pages/profile/settings.vue | 226 +++++++++++++++++- storefront/pages/profile/wishlist.vue | 17 +- storefront/plugins/apollo.ts | 21 +- storefront/stores/promocodes.ts | 13 + storefront/stores/user.ts | 9 +- storefront/types/api/promocodes.ts | 9 + storefront/types/api/user.ts | 12 + storefront/types/app/promocodes.ts | 10 + storefront/types/app/user.ts | 9 +- storefront/types/index.ts | 4 +- 62 files changed, 1560 insertions(+), 563 deletions(-) create mode 100644 storefront/composables/date/index.ts create mode 100644 storefront/composables/date/useDate.ts create mode 100644 storefront/composables/promocodes/index.ts create mode 100644 storefront/composables/promocodes/usePromocodes.ts create mode 100644 storefront/composables/user/useAvatarUpload.ts create mode 100644 storefront/composables/user/useDeposit.ts create mode 100644 storefront/composables/user/useUserUpdating.ts create mode 100644 storefront/graphql/fragments/promocodes.fragment.ts delete mode 100644 storefront/graphql/queries/combined/storePage.ts create mode 100644 storefront/graphql/queries/standalone/promocodes.ts create mode 100644 storefront/pages/profile/balance.vue create mode 100644 storefront/pages/profile/promocodes.vue create mode 100644 storefront/stores/promocodes.ts create mode 100644 storefront/types/api/promocodes.ts create mode 100644 storefront/types/app/promocodes.ts diff --git a/storefront/app.vue b/storefront/app.vue index 978334c4..a88ab6bf 100644 --- a/storefront/app.vue +++ b/storefront/app.vue @@ -68,12 +68,20 @@ await Promise.all([ watch( () => appStore.activeState, (state) => { - appStore.setOverflowHidden(state !== '') + appStore.setOverflowHidden(state !== ''); }, { immediate: true } -) +); -let stopWatcher: VoidFunction = () => {} +watch(locale, () => { + useHead({ + htmlAttrs: { + lang: locale.value + } + }); +}); + +let stopWatcher: VoidFunction = () => {}; onMounted( async () => { refreshInterval = setInterval(async () => { diff --git a/storefront/assets/styles/global/variables.scss b/storefront/assets/styles/global/variables.scss index 81535374..2da17eb4 100644 --- a/storefront/assets/styles/global/variables.scss +++ b/storefront/assets/styles/global/variables.scss @@ -8,5 +8,6 @@ $accentDark: #5743b5; $accentLight: #a69cdc; $accentDisabled: #826fa2; $accentSmooth: #656bd1; +$contrast: #FFC107; $error: #f13838; $default_border_radius: 4px; \ No newline at end of file diff --git a/storefront/components/base/header/index.vue b/storefront/components/base/header/index.vue index b785e060..edb9a223 100644 --- a/storefront/components/base/header/index.vue +++ b/storefront/components/base/header/index.vue @@ -39,7 +39,7 @@ @click="appStore.setActiveState('login')" v-else > - +

{{ t('header.actions.login') }}

@@ -58,8 +58,6 @@ \ No newline at end of file diff --git a/storefront/components/forms/login.vue b/storefront/components/forms/login.vue index 8f324440..bbbf85ea 100644 --- a/storefront/components/forms/login.vue +++ b/storefront/components/forms/login.vue @@ -6,6 +6,7 @@ :placeholder="t('fields.email')" :rules="[isEmail]" v-model="email" + :inputMode="'email'" /> (''); const password = ref(''); @@ -58,7 +59,7 @@ const isFormValid = computed(() => { return ( isEmail(email.value) === true && required(password.value) === true - ) + ); }); const { login, loading } = useLogin(); diff --git a/storefront/components/forms/register.vue b/storefront/components/forms/register.vue index b99d0fa8..c59f72aa 100644 --- a/storefront/components/forms/register.vue +++ b/storefront/components/forms/register.vue @@ -21,12 +21,14 @@ :placeholder="t('fields.phoneNumber')" :rules="[required]" v-model="phoneNumber" + :inputMode="'tel'" /> - @@ -118,7 +124,7 @@ async function handleRegister() { &__box { display: flex; - align-items: center; + align-items: flex-start; gap: 20px; } diff --git a/storefront/components/forms/reset-password.vue b/storefront/components/forms/reset-password.vue index 41522d4b..52eed779 100644 --- a/storefront/components/forms/reset-password.vue +++ b/storefront/components/forms/reset-password.vue @@ -6,6 +6,7 @@ :placeholder="t('fields.email')" :rules="[isEmail]" v-model="email" + :inputMode="'email'" /> - -
- - - - - - - - - - - - + +
+ + +
+
+ + +
+
+ + +
{{ t('buttons.save') }} - \ No newline at end of file diff --git a/storefront/components/profile/navigation.vue b/storefront/components/profile/navigation.vue index d80a9682..308f7a03 100644 --- a/storefront/components/profile/navigation.vue +++ b/storefront/components/profile/navigation.vue @@ -1,62 +1,91 @@ \ No newline at end of file diff --git a/storefront/pages/profile/cart.vue b/storefront/pages/profile/cart.vue index 6be66978..e75ef0ae 100644 --- a/storefront/pages/profile/cart.vue +++ b/storefront/pages/profile/cart.vue @@ -13,13 +13,14 @@ :key="product.node.uuid" :product="product.node.product" :isList="true" + :isToolsVisible="true" /> - + + \ No newline at end of file diff --git a/storefront/pages/profile/settings.vue b/storefront/pages/profile/settings.vue index 3e3ad289..3cbbb12b 100644 --- a/storefront/pages/profile/settings.vue +++ b/storefront/pages/profile/settings.vue @@ -1,15 +1,239 @@ \ No newline at end of file diff --git a/storefront/pages/profile/wishlist.vue b/storefront/pages/profile/wishlist.vue index 72aadd00..47a118f3 100644 --- a/storefront/pages/profile/wishlist.vue +++ b/storefront/pages/profile/wishlist.vue @@ -11,12 +11,17 @@

{{ t('profile.wishlist.total', {quantity: productsInWishlist.length, amount: totalPrice}) }}

-
- -
+
+ +
+
@@ -118,6 +123,7 @@ setPageTitle(t('breadcrumbs.wishlist')); align-items: center; justify-content: space-between; box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.2); + border-radius: $default_border_radius; &-left { display: flex; @@ -155,6 +161,7 @@ setPageTitle(t('breadcrumbs.wishlist')); background-color: $white; padding: 20px; box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.2); + border-radius: $default_border_radius; flex: 1; overflow-y: auto; diff --git a/storefront/plugins/apollo.ts b/storefront/plugins/apollo.ts index 2edb2ea5..61052f7b 100644 --- a/storefront/plugins/apollo.ts +++ b/storefront/plugins/apollo.ts @@ -1,9 +1,18 @@ -import { provideApolloClient } from '@vue/apollo-composable' -import type { ApolloClient } from '@apollo/client/core' +import { useAppConfig } from '~/composables/config'; export default defineNuxtPlugin(() => { - const apollo = useApollo() - const defaultClient = apollo.clients!.default as ApolloClient + const { COOKIES_LOCALE_KEY } = useAppConfig(); + const localeCookie = useCookie(COOKIES_LOCALE_KEY); + const originalFetch = globalThis.fetch; - provideApolloClient(defaultClient) -}) \ No newline at end of file + globalThis.fetch = (input, init = {}) => { + const lang = localeCookie.value || 'en-gb'; + const headers = new Headers(init.headers as any); + headers.set('Accept-Language', lang); + + return originalFetch(input, { + ...init, + headers + }); + }; +}); \ No newline at end of file diff --git a/storefront/stores/promocodes.ts b/storefront/stores/promocodes.ts new file mode 100644 index 00000000..87a36981 --- /dev/null +++ b/storefront/stores/promocodes.ts @@ -0,0 +1,13 @@ +import type {IPromocode} from "~/types"; + +export const usePromocodeStore = defineStore('promocode', () => { + const promocodes = ref<{ node: IPromocode }[] | null>(null); + const setPromocodes = (promo: { node: IPromocode }[]) => { + promocodes.value = promo + }; + + return { + promocodes, + setPromocodes + } +}) \ No newline at end of file diff --git a/storefront/stores/user.ts b/storefront/stores/user.ts index f3061032..9dc31b14 100644 --- a/storefront/stores/user.ts +++ b/storefront/stores/user.ts @@ -12,7 +12,11 @@ export const useUserStore = defineStore('user', () => { ); const user = ref(null); + const isAuthenticated = computed(() => Boolean(cookieAccess.value && user.value)); + const finishedOrdersQuantity = computed(() => { + return user.value?.orders.filter((order) => order.status === 'FINISHED').length || 0; + }); const setUser = (data: IUser | null) => { user.value = data; @@ -21,6 +25,7 @@ export const useUserStore = defineStore('user', () => { return { user, setUser, - isAuthenticated + isAuthenticated, + finishedOrdersQuantity }; -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/storefront/types/api/promocodes.ts b/storefront/types/api/promocodes.ts new file mode 100644 index 00000000..0512d83f --- /dev/null +++ b/storefront/types/api/promocodes.ts @@ -0,0 +1,9 @@ +import type {IPromocode} from "~/types"; + +export interface IPromocodesResponse { + promocodes: { + edges: { + node: IPromocode + }[] + } +} \ No newline at end of file diff --git a/storefront/types/api/user.ts b/storefront/types/api/user.ts index 05bf2346..586b77fc 100644 --- a/storefront/types/api/user.ts +++ b/storefront/types/api/user.ts @@ -10,4 +10,16 @@ export interface IUserActivationResponse { activateUser: { success: boolean } +} + +export interface IUserUpdatingResponse { + updateUser: { + user: IUser + } +} + +export interface IAvatarUploadResponse { + uploadAvatar: { + user: IUser + } } \ No newline at end of file diff --git a/storefront/types/app/promocodes.ts b/storefront/types/app/promocodes.ts new file mode 100644 index 00000000..4a20e053 --- /dev/null +++ b/storefront/types/app/promocodes.ts @@ -0,0 +1,10 @@ +export interface IPromocode { + code: string, + discount: string, + discountType: string, + endTime: string, + id: string, + startTime: string, + usedOn: string, + uuid: string +} \ No newline at end of file diff --git a/storefront/types/app/user.ts b/storefront/types/app/user.ts index be3bf3b9..0fbf3742 100644 --- a/storefront/types/app/user.ts +++ b/storefront/types/app/user.ts @@ -1,7 +1,7 @@ export interface IUser { avatar: string, uuid: string, - attributes: string | null + attributes: string | null, language: string, email: string, firstName: string, @@ -10,5 +10,10 @@ export interface IUser { dateJoined: string, balance: { amount: number, - } + }, + orders: { + uuid: string, + humanReadableId: string, + status: string + }[] } \ No newline at end of file diff --git a/storefront/types/index.ts b/storefront/types/index.ts index 7044480a..824285fd 100644 --- a/storefront/types/index.ts +++ b/storefront/types/index.ts @@ -10,6 +10,7 @@ export * from './app/wishlist' export * from './app/orders' export * from './app/category' export * from './app/store' +export * from './app/promocodes' @@ -26,4 +27,5 @@ export * from './api/user' export * from './api/categories' export * from './api/brands' export * from './api/contact' -export * from './api/store' \ No newline at end of file +export * from './api/store' +export * from './api/promocodes' \ No newline at end of file