feat(storefront): refactor i18n and cart/wishlist handling for improved user experience
Refactored i18n configuration, replacing `DEFAULT_LOCALE` with `DEFAULT_LOCALE_FALLBACK` and enhancing environment-based locale validation. Improved cookie persistence for cart and wishlist, ensuring fallback handling for unauthenticated users. Enhancements: - Added `createProjectKey` utility for consistent project key generation. - Reworked cart and wishlist composables (`useOrderOverwrite`, `useWishlistOverwrite`) to decouple product identifier and handle cookies robustly. - Centralized `DEFAULT_LOCALE` logic for better maintainability. - Refined `useOrderSync` and `useWishlistSync` for clean synchronization across auth states. - Updated SCSS in hero and header styles for alignment corrections. Breaking Changes: `DEFAULT_LOCALE` constant removed; replaced with runtime config and fallback logic. Consumers must adapt to `DEFAULT_LOCALE_FALLBACK` and `$appHelpers.DEFAULT_LOCALE`.
This commit is contained in:
parent
a14be696e7
commit
2ea18eb8a6
24 changed files with 391 additions and 194 deletions
|
|
@ -1,9 +1,8 @@
|
||||||
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '@appConstants';
|
import { SUPPORTED_LOCALES } from '@appConstants';
|
||||||
|
|
||||||
export default defineAppConfig({
|
export default defineAppConfig({
|
||||||
i18n: {
|
i18n: {
|
||||||
supportedLocales: SUPPORTED_LOCALES,
|
supportedLocales: SUPPORTED_LOCALES,
|
||||||
defaultLocale: DEFAULT_LOCALE,
|
|
||||||
},
|
},
|
||||||
ui: {
|
ui: {
|
||||||
showBreadcrumbs: true,
|
showBreadcrumbs: true,
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {DEFAULT_LOCALE} from '@appConstants';
|
|
||||||
import {useRefresh} from '@composables/auth';
|
import {useRefresh} from '@composables/auth';
|
||||||
import {useLanguages, useLocaleRedirect} from '@composables/languages';
|
import {useLanguages, useLocaleRedirect} from '@composables/languages';
|
||||||
import {useCompanyInfo} from '@composables/company';
|
import {useCompanyInfo} from '@composables/company';
|
||||||
|
|
@ -49,11 +48,11 @@ const { locale } = useI18n();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
const { $appHelpers } = useNuxtApp();
|
||||||
|
|
||||||
const { isDemoMode, uiConfig } = useProjectConfig();
|
const { isDemoMode, uiConfig } = useProjectConfig();
|
||||||
const toaster = { position: uiConfig.value.toastPosition };
|
const toaster = { position: uiConfig.value.toastPosition };
|
||||||
const switchLocalePath = useSwitchLocalePath();
|
const switchLocalePath = useSwitchLocalePath();
|
||||||
const { $appHelpers } = useNuxtApp();
|
|
||||||
|
|
||||||
const showBreadcrumbs = computed(() => {
|
const showBreadcrumbs = computed(() => {
|
||||||
const name = typeof route.name === 'string' ? route.name : '';
|
const name = typeof route.name === 'string' ? route.name : '';
|
||||||
|
|
@ -77,7 +76,7 @@ const activeState = computed(() => appStore.activeAuthState);
|
||||||
const cookieLocale = useCookie(
|
const cookieLocale = useCookie(
|
||||||
$appHelpers.COOKIES_LOCALE_KEY,
|
$appHelpers.COOKIES_LOCALE_KEY,
|
||||||
{
|
{
|
||||||
default: () => DEFAULT_LOCALE,
|
default: () => $appHelpers.DEFAULT_LOCALE,
|
||||||
path: '/'
|
path: '/'
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -116,7 +115,7 @@ watch(locale, () => {
|
||||||
let stopWatcher: VoidFunction = () => {};
|
let stopWatcher: VoidFunction = () => {};
|
||||||
|
|
||||||
if (!cookieLocale.value) {
|
if (!cookieLocale.value) {
|
||||||
cookieLocale.value = DEFAULT_LOCALE;
|
cookieLocale.value = $appHelpers.DEFAULT_LOCALE;
|
||||||
await router.push({path: switchLocalePath(cookieLocale.value)});
|
await router.push({path: switchLocalePath(cookieLocale.value)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,10 +126,10 @@ if (locale.value !== cookieLocale.value) {
|
||||||
query: route.query
|
query: route.query
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
cookieLocale.value = DEFAULT_LOCALE
|
cookieLocale.value = $appHelpers.DEFAULT_LOCALE;
|
||||||
|
|
||||||
await router.push({
|
await router.push({
|
||||||
path: switchLocalePath(DEFAULT_LOCALE),
|
path: switchLocalePath($appHelpers.DEFAULT_LOCALE),
|
||||||
query: route.query
|
query: route.query
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,11 @@ const cookieWishlist = useCookie($appHelpers.COOKIES_WISHLIST_KEY, {
|
||||||
default: () => [],
|
default: () => [],
|
||||||
path: '/',
|
path: '/',
|
||||||
});
|
});
|
||||||
|
const cookieCart = useCookie($appHelpers.COOKIES_CART_KEY, {
|
||||||
|
default: () => [],
|
||||||
|
path: '/',
|
||||||
|
});
|
||||||
|
console.log(cookieCart.value)
|
||||||
|
|
||||||
const productsInCartQuantity = computed(() => {
|
const productsInCartQuantity = computed(() => {
|
||||||
if (isAuthenticated.value) {
|
if (isAuthenticated.value) {
|
||||||
|
|
@ -166,7 +171,7 @@ const redirectTo = (to) => {
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.header {
|
.header {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 3;
|
z-index: 5;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@
|
||||||
class="card__wishlist"
|
class="card__wishlist"
|
||||||
@click="overwriteWishlist({
|
@click="overwriteWishlist({
|
||||||
type: (isProductInWishlist ? 'remove' : 'add'),
|
type: (isProductInWishlist ? 'remove' : 'add'),
|
||||||
product: product,
|
productUuid: product.uuid,
|
||||||
|
productName: product.name,
|
||||||
})"
|
})"
|
||||||
>
|
>
|
||||||
<icon style="color: #dc2626;" name="mdi:cards-heart" size="16" v-if="isProductInWishlist" />
|
<icon style="color: #dc2626;" name="mdi:cards-heart" size="16" v-if="isProductInWishlist" />
|
||||||
|
|
@ -128,22 +129,56 @@ const props = defineProps<{
|
||||||
const {t} = useI18n();
|
const {t} = useI18n();
|
||||||
const wishlistStore = useWishlistStore();
|
const wishlistStore = useWishlistStore();
|
||||||
const cartStore = useCartStore();
|
const cartStore = useCartStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const { $appHelpers } = useNuxtApp();
|
||||||
|
|
||||||
const { overwriteWishlist } = useWishlistOverwrite();
|
const { overwriteWishlist } = useWishlistOverwrite();
|
||||||
const { addLoading, removeLoading, overwriteOrder } = useOrderOverwrite();
|
const { addLoading, removeLoading, overwriteOrder } = useOrderOverwrite();
|
||||||
|
|
||||||
|
const cookieWishlist = useCookie($appHelpers.COOKIES_WISHLIST_KEY, {
|
||||||
|
default: () => [],
|
||||||
|
path: '/',
|
||||||
|
});
|
||||||
|
const cookieCart = useCookie($appHelpers.COOKIES_CART_KEY, {
|
||||||
|
default: () => [],
|
||||||
|
path: '/',
|
||||||
|
});
|
||||||
|
|
||||||
|
const isAuthenticated = computed(() => userStore.isAuthenticated);
|
||||||
|
|
||||||
const isProductInWishlist = computed(() => {
|
const isProductInWishlist = computed(() => {
|
||||||
const el = wishlistStore.wishlist?.products?.edges.find(
|
if (isAuthenticated.value) {
|
||||||
|
return !!wishlistStore.wishlist?.products?.edges.find(
|
||||||
(el) => el?.node?.uuid === props.product.uuid
|
(el) => el?.node?.uuid === props.product.uuid
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
return !!el;
|
return (cookieWishlist.value ?? []).includes(props.product.uuid);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
const isProductInCart = computed(() => {
|
const isProductInCart = computed(() => {
|
||||||
return cartStore.currentOrder?.orderProducts?.edges.find((prod) => prod.node.product.uuid === props.product?.uuid);
|
if (isAuthenticated.value) {
|
||||||
|
return !!cartStore.currentOrder?.orderProducts?.edges.find(
|
||||||
|
(prod) => prod.node.product.uuid === props.product?.uuid
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (cookieCart.value ?? []).some(
|
||||||
|
(item) => item.product === props.product?.uuid
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const productInCartQuantity = computed(() => {
|
const productInCartQuantity = computed(() => {
|
||||||
return cartStore.currentOrder?.orderProducts?.edges.filter((prod) => prod.node.product.uuid === props.product.uuid)[0].node.quantity;
|
if (isAuthenticated.value) {
|
||||||
|
const productEdge = cartStore.currentOrder?.orderProducts?.edges.find(
|
||||||
|
(prod) => prod.node.product.uuid === props.product.uuid
|
||||||
|
);
|
||||||
|
return productEdge?.node.quantity ?? 0;
|
||||||
|
} else {
|
||||||
|
const cartItem = (cookieCart.value ?? []).find(
|
||||||
|
(item) => item.product === props.product.uuid
|
||||||
|
);
|
||||||
|
return cartItem?.quantity ?? 0;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const rating = computed(() => {
|
const rating = computed(() => {
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ const {t} = useI18n();
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
color: $white;
|
color: $white;
|
||||||
|
text-align: center;
|
||||||
font-family: "Playfair Display", sans-serif;
|
font-family: "Playfair Display", sans-serif;
|
||||||
font-size: 60px;
|
font-size: 60px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { DEFAULT_LOCALE } from '@appConstants';
|
|
||||||
import { useLocaleRedirect } from '@composables/languages';
|
import { useLocaleRedirect } from '@composables/languages';
|
||||||
import { useNotification } from '@composables/notification';
|
import { useNotification } from '@composables/notification';
|
||||||
import { useUserBaseData } from '@composables/user';
|
import { useUserBaseData } from '@composables/user';
|
||||||
|
|
@ -22,7 +21,7 @@ export function useLogin() {
|
||||||
path: '/',
|
path: '/',
|
||||||
});
|
});
|
||||||
const cookieLocale = useCookie($appHelpers.COOKIES_LOCALE_KEY, {
|
const cookieLocale = useCookie($appHelpers.COOKIES_LOCALE_KEY, {
|
||||||
default: () => DEFAULT_LOCALE,
|
default: () => $appHelpers.DEFAULT_LOCALE,
|
||||||
path: '/',
|
path: '/',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -43,8 +42,6 @@ export function useLogin() {
|
||||||
userStore.setUser(authData.user);
|
userStore.setUser(authData.user);
|
||||||
cookieAccess.value = authData.accessToken;
|
cookieAccess.value = authData.accessToken;
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
navigateTo(localePath('/'));
|
navigateTo(localePath('/'));
|
||||||
|
|
||||||
useNotification({
|
useNotification({
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { DEFAULT_LOCALE } from '@appConstants';
|
|
||||||
import { useLogout } from '@composables/auth';
|
import { useLogout } from '@composables/auth';
|
||||||
import { useLocaleRedirect } from '@composables/languages';
|
import { useLocaleRedirect } from '@composables/languages';
|
||||||
import { useNotification } from '@composables/notification';
|
import { useNotification } from '@composables/notification';
|
||||||
|
|
@ -40,7 +39,7 @@ export function useRefresh() {
|
||||||
path: '/',
|
path: '/',
|
||||||
});
|
});
|
||||||
const cookieLocale = useCookie($appHelpers.COOKIES_LOCALE_KEY, {
|
const cookieLocale = useCookie($appHelpers.COOKIES_LOCALE_KEY, {
|
||||||
default: () => DEFAULT_LOCALE,
|
default: () => $appHelpers.DEFAULT_LOCALE,
|
||||||
path: '/',
|
path: '/',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '@appConstants';
|
import { SUPPORTED_LOCALES } from '@appConstants';
|
||||||
import { GET_LANGUAGES } from '@graphql/queries/standalone/languages.js';
|
import { GET_LANGUAGES } from '@graphql/queries/standalone/languages.js';
|
||||||
import type { ILanguage, ILanguagesResponse } from '@types';
|
import type { ILanguage, ILanguagesResponse } from '@types';
|
||||||
|
|
||||||
|
|
@ -7,7 +7,7 @@ export async function useLanguages() {
|
||||||
const { $appHelpers } = useNuxtApp();
|
const { $appHelpers } = useNuxtApp();
|
||||||
|
|
||||||
const cookieLocale = useCookie($appHelpers.COOKIES_LOCALE_KEY, {
|
const cookieLocale = useCookie($appHelpers.COOKIES_LOCALE_KEY, {
|
||||||
default: () => DEFAULT_LOCALE,
|
default: () => $appHelpers.DEFAULT_LOCALE,
|
||||||
path: '/',
|
path: '/',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { DEFAULT_LOCALE } from '@appConstants';
|
|
||||||
import { SWITCH_LANGUAGE } from '@graphql/mutations/languages.js';
|
import { SWITCH_LANGUAGE } from '@graphql/mutations/languages.js';
|
||||||
import type { IUserResponse, LocaleDefinition } from '@types';
|
import type { IUserResponse, LocaleDefinition } from '@types';
|
||||||
|
|
||||||
|
|
@ -11,7 +10,7 @@ export function useLanguageSwitch() {
|
||||||
const switchLocalePath = useSwitchLocalePath();
|
const switchLocalePath = useSwitchLocalePath();
|
||||||
|
|
||||||
const cookieLocale = useCookie($appHelpers.COOKIES_LOCALE_KEY, {
|
const cookieLocale = useCookie($appHelpers.COOKIES_LOCALE_KEY, {
|
||||||
default: () => DEFAULT_LOCALE,
|
default: () => $appHelpers.DEFAULT_LOCALE,
|
||||||
path: '/',
|
path: '/',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '@appConstants';
|
import { SUPPORTED_LOCALES } from '@appConstants';
|
||||||
import type { SupportedLocale } from '@types';
|
import type { SupportedLocale } from '@types';
|
||||||
|
|
||||||
export function useLocaleRedirect() {
|
export function useLocaleRedirect() {
|
||||||
|
|
@ -9,7 +9,7 @@ export function useLocaleRedirect() {
|
||||||
const switchLocalePath = useSwitchLocalePath();
|
const switchLocalePath = useSwitchLocalePath();
|
||||||
|
|
||||||
const cookieLocale = useCookie($appHelpers.COOKIES_LOCALE_KEY, {
|
const cookieLocale = useCookie($appHelpers.COOKIES_LOCALE_KEY, {
|
||||||
default: () => DEFAULT_LOCALE,
|
default: () => $appHelpers.DEFAULT_LOCALE,
|
||||||
path: '/',
|
path: '/',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -18,7 +18,7 @@ export function useLocaleRedirect() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkAndRedirect(userLocale: string) {
|
async function checkAndRedirect(userLocale: string) {
|
||||||
const targetLocale = isSupportedLocale(userLocale) ? userLocale : DEFAULT_LOCALE;
|
const targetLocale = isSupportedLocale(userLocale) ? userLocale : $appHelpers.DEFAULT_LOCALE;
|
||||||
|
|
||||||
if (targetLocale !== locale.value) {
|
if (targetLocale !== locale.value) {
|
||||||
cookieLocale.value = targetLocale;
|
cookieLocale.value = targetLocale;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import {
|
||||||
import type {
|
import type {
|
||||||
IAddToOrderResponse,
|
IAddToOrderResponse,
|
||||||
IBulkOrderResponse,
|
IBulkOrderResponse,
|
||||||
IProduct,
|
|
||||||
IRemoveAllFromOrderResponse,
|
IRemoveAllFromOrderResponse,
|
||||||
IRemoveFromOrderResponse,
|
IRemoveFromOrderResponse,
|
||||||
IRemoveKindFromOrderResponse,
|
IRemoveKindFromOrderResponse,
|
||||||
|
|
@ -17,7 +16,8 @@ import type {
|
||||||
|
|
||||||
interface IOverwriteOrderArguments {
|
interface IOverwriteOrderArguments {
|
||||||
type: string;
|
type: string;
|
||||||
product: IProduct;
|
productUuid: string;
|
||||||
|
productName: string;
|
||||||
bulkAction?: string;
|
bulkAction?: string;
|
||||||
isBulkSync?: boolean;
|
isBulkSync?: boolean;
|
||||||
products?: {
|
products?: {
|
||||||
|
|
@ -29,6 +29,7 @@ export function useOrderOverwrite() {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const cartStore = useCartStore();
|
const cartStore = useCartStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const { $appHelpers } = useNuxtApp();
|
||||||
|
|
||||||
const isAuthenticated = computed(() => userStore.isAuthenticated);
|
const isAuthenticated = computed(() => userStore.isAuthenticated);
|
||||||
const orderUuid = computed(() => cartStore.currentOrder?.uuid);
|
const orderUuid = computed(() => cartStore.currentOrder?.uuid);
|
||||||
|
|
@ -36,6 +37,7 @@ export function useOrderOverwrite() {
|
||||||
const cookieCart = useCookie($appHelpers.COOKIES_CART_KEY, {
|
const cookieCart = useCookie($appHelpers.COOKIES_CART_KEY, {
|
||||||
default: () => [],
|
default: () => [],
|
||||||
path: '/',
|
path: '/',
|
||||||
|
watch: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -70,7 +72,7 @@ export function useOrderOverwrite() {
|
||||||
case 'add': {
|
case 'add': {
|
||||||
const addResult = await addMutate({
|
const addResult = await addMutate({
|
||||||
orderUuid: orderUuid.value,
|
orderUuid: orderUuid.value,
|
||||||
productUuid: args.product.uuid,
|
productUuid: args.productUuid,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (addResult?.data?.addOrderProduct?.order) {
|
if (addResult?.data?.addOrderProduct?.order) {
|
||||||
|
|
@ -78,7 +80,7 @@ export function useOrderOverwrite() {
|
||||||
|
|
||||||
useNotification({
|
useNotification({
|
||||||
message: t('popup.success.addToCart', {
|
message: t('popup.success.addToCart', {
|
||||||
product: args.product.name,
|
product: args.productName,
|
||||||
}),
|
}),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
|
|
@ -90,7 +92,7 @@ export function useOrderOverwrite() {
|
||||||
case 'remove': {
|
case 'remove': {
|
||||||
const removeResult = await removeMutate({
|
const removeResult = await removeMutate({
|
||||||
orderUuid: orderUuid.value,
|
orderUuid: orderUuid.value,
|
||||||
productUuid: args.product.uuid,
|
productUuid: args.productUuid,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (removeResult?.data?.removeOrderProduct?.order) {
|
if (removeResult?.data?.removeOrderProduct?.order) {
|
||||||
|
|
@ -98,7 +100,7 @@ export function useOrderOverwrite() {
|
||||||
|
|
||||||
useNotification({
|
useNotification({
|
||||||
message: t('popup.success.removeFromCart', {
|
message: t('popup.success.removeFromCart', {
|
||||||
product: args.product.name,
|
product: args.productName,
|
||||||
}),
|
}),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
|
|
@ -110,7 +112,7 @@ export function useOrderOverwrite() {
|
||||||
case 'removeKind': {
|
case 'removeKind': {
|
||||||
const removeKindResult = await removeKindMutate({
|
const removeKindResult = await removeKindMutate({
|
||||||
orderUuid: orderUuid.value,
|
orderUuid: orderUuid.value,
|
||||||
productUuid: args.product.uuid,
|
productUuid: args.productUuid,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (removeKindResult?.data?.removeOrderProductsOfAKind?.order) {
|
if (removeKindResult?.data?.removeOrderProductsOfAKind?.order) {
|
||||||
|
|
@ -118,7 +120,7 @@ export function useOrderOverwrite() {
|
||||||
|
|
||||||
useNotification({
|
useNotification({
|
||||||
message: t('popup.success.removeFromCart', {
|
message: t('popup.success.removeFromCart', {
|
||||||
product: args.product.name,
|
product: args.productName,
|
||||||
}),
|
}),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
|
|
@ -168,27 +170,29 @@ export function useOrderOverwrite() {
|
||||||
} else {
|
} else {
|
||||||
switch (args.type) {
|
switch (args.type) {
|
||||||
case 'add': {
|
case 'add': {
|
||||||
const currentCart = cookieCart.value || [];
|
const current = Array.isArray(cookieCart.value) ? [...cookieCart.value] : [];
|
||||||
const existingItem = currentCart.find((item) => item.product.uuid === args.product.uuid);
|
|
||||||
|
|
||||||
if (existingItem) {
|
const index = current.findIndex(
|
||||||
existingItem.quantity += 1;
|
(item) => item.product === args.productUuid
|
||||||
cookieCart.value = [
|
);
|
||||||
...currentCart,
|
|
||||||
];
|
if (index !== -1) {
|
||||||
|
current[index] = {
|
||||||
|
...current[index],
|
||||||
|
quantity: current[index].quantity + 1,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
cookieCart.value = [
|
current.push({
|
||||||
...currentCart,
|
product: args.productUuid,
|
||||||
{
|
|
||||||
product: args.product,
|
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
},
|
});
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cookieCart.value = current;
|
||||||
|
|
||||||
useNotification({
|
useNotification({
|
||||||
message: t('popup.success.addToCart', {
|
message: t('popup.success.addToCart', {
|
||||||
product: args.product.name,
|
product: args.productName,
|
||||||
}),
|
}),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
|
|
@ -197,22 +201,29 @@ export function useOrderOverwrite() {
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'remove': {
|
case 'remove': {
|
||||||
const currentCart = cookieCart.value || [];
|
const current = Array.isArray(cookieCart.value)
|
||||||
const existingItem = currentCart.find((item) => item.product.uuid === args.product.uuid);
|
? [...cookieCart.value]
|
||||||
|
: [];
|
||||||
|
|
||||||
if (existingItem) {
|
const index = current.findIndex(
|
||||||
if (existingItem.quantity > 1) {
|
(item) => item.product === args.productUuid
|
||||||
existingItem.quantity -= 1;
|
);
|
||||||
cookieCart.value = [
|
|
||||||
...currentCart,
|
if (index !== -1) {
|
||||||
];
|
if (current[index].quantity > 1) {
|
||||||
|
current[index] = {
|
||||||
|
...current[index],
|
||||||
|
quantity: current[index].quantity - 1,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
cookieCart.value = currentCart.filter((item) => item.product.uuid !== args.product.uuid);
|
current.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cookieCart.value = current;
|
||||||
|
|
||||||
useNotification({
|
useNotification({
|
||||||
message: t('popup.success.removeFromCart', {
|
message: t('popup.success.removeFromCart', {
|
||||||
product: args.product.name,
|
product: args.productName,
|
||||||
}),
|
}),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
|
|
@ -222,11 +233,13 @@ export function useOrderOverwrite() {
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'removeKind': {
|
case 'removeKind': {
|
||||||
cookieCart.value = cookieCart.value.filter((item) => item.product.uuid !== args.product.uuid);
|
cookieCart.value = (cookieCart.value ?? []).filter(
|
||||||
|
(item) => item.product.uuid !== args.productUuid
|
||||||
|
);
|
||||||
|
|
||||||
useNotification({
|
useNotification({
|
||||||
message: t('popup.success.removeFromCart', {
|
message: t('popup.success.removeFromCart', {
|
||||||
product: args.product.name,
|
product: args.productName,
|
||||||
}),
|
}),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
|
|
@ -246,28 +259,18 @@ export function useOrderOverwrite() {
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'bulk': {
|
case 'bulk': {
|
||||||
if (args.bulkAction === 'remove' && args.products) {
|
const bulkResult = await bulkMutate({
|
||||||
const uuidsToRemove = args.products.map((p) => p.uuid);
|
orderUuid: orderUuid.value,
|
||||||
cookieCart.value = cookieCart.value.filter((item) => !uuidsToRemove.includes(item.product.uuid));
|
action: args.bulkAction,
|
||||||
|
products: args.products,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (bulkResult?.data?.bulkOrderAction?.order) {
|
||||||
|
cartStore.setCurrentOrders(bulkResult.data.bulkOrderAction.order);
|
||||||
useNotification({
|
useNotification({
|
||||||
message: t('popup.success.bulkRemoveOrder'),
|
message: t('popup.success.bulkRemoveOrder'),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
} else if (args.bulkAction === 'add' && args.products) {
|
|
||||||
const currentCart = cookieCart.value || [];
|
|
||||||
|
|
||||||
for (const productRef of args.products) {
|
|
||||||
const existingItem = currentCart.find((item) => item.product.uuid === productRef.uuid);
|
|
||||||
|
|
||||||
if (existingItem) {
|
|
||||||
existingItem.quantity += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cookieCart.value = [
|
|
||||||
...currentCart,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
@ -279,21 +282,29 @@ export function useOrderOverwrite() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(addError || removedError || removedKindError || removedAllError || bulkError, (err) => {
|
watch(
|
||||||
|
[addError, removedError, removedKindError, removedAllError, bulkError],
|
||||||
|
(errors) => {
|
||||||
|
const err = errors.find(Boolean);
|
||||||
if (!err) return;
|
if (!err) return;
|
||||||
|
|
||||||
console.error('useOrderOverwrite error:', err);
|
console.error('useOrderOverwrite error:', err);
|
||||||
|
|
||||||
let message = t('popup.errors.defaultError');
|
let message = t('popup.errors.defaultError');
|
||||||
|
|
||||||
if (isGraphQLError(err)) {
|
if (isGraphQLError(err)) {
|
||||||
message = err.graphQLErrors?.[0]?.message || message;
|
message = err.graphQLErrors?.[0]?.message || message;
|
||||||
} else {
|
} else {
|
||||||
message = err.message;
|
message = err.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
useNotification({
|
useNotification({
|
||||||
message,
|
message,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
title: t('popup.errors.main'),
|
title: t('popup.errors.main'),
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
addLoading,
|
addLoading,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { DEFAULT_LOCALE } from '@appConstants';
|
|
||||||
import { useLogout } from '@composables/auth';
|
import { useLogout } from '@composables/auth';
|
||||||
import { useLocaleRedirect } from '@composables/languages';
|
import { useLocaleRedirect } from '@composables/languages';
|
||||||
import { useNotification } from '@composables/notification';
|
import { useNotification } from '@composables/notification';
|
||||||
|
|
@ -16,7 +15,7 @@ export function useUserUpdating() {
|
||||||
const { logout } = useLogout();
|
const { logout } = useLogout();
|
||||||
|
|
||||||
const cookieLocale = useCookie($appHelpers.COOKIES_LOCALE_KEY, {
|
const cookieLocale = useCookie($appHelpers.COOKIES_LOCALE_KEY, {
|
||||||
default: () => DEFAULT_LOCALE,
|
default: () => $appHelpers.DEFAULT_LOCALE,
|
||||||
path: '/',
|
path: '/',
|
||||||
});
|
});
|
||||||
const userUuid = computed(() => userStore.user?.uuid);
|
const userUuid = computed(() => userStore.user?.uuid);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useNotification } from '@composables/notification';
|
import {useNotification} from '@composables/notification';
|
||||||
import {
|
import {
|
||||||
ADD_TO_WISHLIST,
|
ADD_TO_WISHLIST,
|
||||||
BULK_WISHLIST,
|
BULK_WISHLIST,
|
||||||
|
|
@ -8,14 +8,14 @@ import {
|
||||||
import type {
|
import type {
|
||||||
IAddToWishlistResponse,
|
IAddToWishlistResponse,
|
||||||
IBulkWishlistResponse,
|
IBulkWishlistResponse,
|
||||||
IProduct,
|
|
||||||
IRemoveAllFromWishlistResponse,
|
IRemoveAllFromWishlistResponse,
|
||||||
IRemoveFromWishlistResponse,
|
IRemoveFromWishlistResponse,
|
||||||
} from '@types';
|
} from '@types';
|
||||||
|
|
||||||
interface IOverwriteWishlistArguments {
|
interface IOverwriteWishlistArguments {
|
||||||
type: string;
|
type: string;
|
||||||
product: IProduct;
|
productUuid: string;
|
||||||
|
productName: string;
|
||||||
bulkAction?: string;
|
bulkAction?: string;
|
||||||
isBulkSync?: boolean;
|
isBulkSync?: boolean;
|
||||||
products?: {
|
products?: {
|
||||||
|
|
@ -35,6 +35,7 @@ export function useWishlistOverwrite() {
|
||||||
const cookieWishlist = useCookie($appHelpers.COOKIES_WISHLIST_KEY, {
|
const cookieWishlist = useCookie($appHelpers.COOKIES_WISHLIST_KEY, {
|
||||||
default: () => [],
|
default: () => [],
|
||||||
path: '/',
|
path: '/',
|
||||||
|
watch: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -64,7 +65,7 @@ export function useWishlistOverwrite() {
|
||||||
case 'add': {
|
case 'add': {
|
||||||
const addResult = await addMutate({
|
const addResult = await addMutate({
|
||||||
wishlistUuid: wishlistUuid.value,
|
wishlistUuid: wishlistUuid.value,
|
||||||
productUuid: args.product.uuid,
|
productUuid: args.productUuid,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (addResult?.data?.addWishlistProduct?.wishlist) {
|
if (addResult?.data?.addWishlistProduct?.wishlist) {
|
||||||
|
|
@ -72,7 +73,7 @@ export function useWishlistOverwrite() {
|
||||||
|
|
||||||
useNotification({
|
useNotification({
|
||||||
message: t('popup.success.addToWishlist', {
|
message: t('popup.success.addToWishlist', {
|
||||||
product: args.product.name,
|
product: args.productName,
|
||||||
}),
|
}),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
|
|
@ -84,7 +85,7 @@ export function useWishlistOverwrite() {
|
||||||
case 'remove': {
|
case 'remove': {
|
||||||
const removeResult = await removeMutate({
|
const removeResult = await removeMutate({
|
||||||
wishlistUuid: wishlistUuid.value,
|
wishlistUuid: wishlistUuid.value,
|
||||||
productUuid: args.product.uuid,
|
productUuid: args.productUuid,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (removeResult?.data?.removeWishlistProduct?.wishlist) {
|
if (removeResult?.data?.removeWishlistProduct?.wishlist) {
|
||||||
|
|
@ -92,7 +93,7 @@ export function useWishlistOverwrite() {
|
||||||
|
|
||||||
useNotification({
|
useNotification({
|
||||||
message: t('popup.success.removeFromWishlist', {
|
message: t('popup.success.removeFromWishlist', {
|
||||||
product: args.product.name,
|
product: args.productName,
|
||||||
}),
|
}),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
|
|
@ -104,7 +105,7 @@ export function useWishlistOverwrite() {
|
||||||
case 'removeAll': {
|
case 'removeAll': {
|
||||||
const removeAllResult = await removeAllMutate({
|
const removeAllResult = await removeAllMutate({
|
||||||
wishlistUuid: wishlistUuid.value,
|
wishlistUuid: wishlistUuid.value,
|
||||||
productUuid: args.product.uuid,
|
productUuid: args.productUuid,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (removeAllResult?.data?.removeAllWishlistProducts?.wishlist) {
|
if (removeAllResult?.data?.removeAllWishlistProducts?.wishlist) {
|
||||||
|
|
@ -148,38 +149,43 @@ export function useWishlistOverwrite() {
|
||||||
} else {
|
} else {
|
||||||
switch (args.type) {
|
switch (args.type) {
|
||||||
case 'add': {
|
case 'add': {
|
||||||
const isAlreadyInWishlist = cookieWishlist.value.some((item) => item.uuid === args.product.uuid);
|
const current = Array.isArray(cookieWishlist.value)
|
||||||
|
? [...cookieWishlist.value]
|
||||||
|
: [];
|
||||||
|
|
||||||
if (isAlreadyInWishlist) {
|
if (current.includes(args.productUuid)) {
|
||||||
useNotification({
|
useNotification({
|
||||||
message: t('popup.errors.alreadyInWishlist', {
|
message: t('popup.errors.alreadyInWishlist', {
|
||||||
product: args.product.name,
|
product: args.productName,
|
||||||
}),
|
}),
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
});
|
});
|
||||||
} else {
|
return;
|
||||||
cookieWishlist.value = [
|
}
|
||||||
...cookieWishlist.value,
|
|
||||||
args.product,
|
current.push(args.productUuid);
|
||||||
];
|
cookieWishlist.value = current;
|
||||||
|
|
||||||
useNotification({
|
useNotification({
|
||||||
message: t('popup.success.addToWishlist', {
|
message: t('popup.success.addToWishlist', {
|
||||||
product: args.product.name,
|
product: args.productName,
|
||||||
}),
|
}),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'remove': {
|
case 'remove': {
|
||||||
cookieWishlist.value = cookieWishlist.value.filter((item) => item.uuid !== args.product.uuid);
|
cookieWishlist.value = Array.isArray(cookieWishlist.value)
|
||||||
|
? cookieWishlist.value.filter(
|
||||||
|
(uuid) => uuid !== args.productUuid
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
useNotification({
|
useNotification({
|
||||||
message: t('popup.success.removeFromWishlist', {
|
message: t('popup.success.removeFromWishlist', {
|
||||||
product: args.product.name,
|
product: args.productName,
|
||||||
}),
|
}),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
|
|
@ -227,7 +233,10 @@ export function useWishlistOverwrite() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(addError || removedError || removeAllError || bulkError, (err) => {
|
watch(
|
||||||
|
[addError, removedError, removeAllError, bulkError],
|
||||||
|
(errors) => {
|
||||||
|
const err = errors.find(Boolean);
|
||||||
if (!err) return;
|
if (!err) return;
|
||||||
console.error('useWishlistOverwrite error:', err);
|
console.error('useWishlistOverwrite error:', err);
|
||||||
let message = t('popup.errors.defaultError');
|
let message = t('popup.errors.defaultError');
|
||||||
|
|
@ -241,7 +250,8 @@ export function useWishlistOverwrite() {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
title: t('popup.errors.main'),
|
title: t('popup.errors.main'),
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
addLoading,
|
addLoading,
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export function useWishlistSync() {
|
||||||
|
|
||||||
const apiProductUuids = wishlistStore.wishlist?.products?.edges.map((e) => e.node.uuid) || [];
|
const apiProductUuids = wishlistStore.wishlist?.products?.edges.map((e) => e.node.uuid) || [];
|
||||||
|
|
||||||
const productsToAdd = cookieProducts.filter((product) => !apiProductUuids.includes(product.uuid));
|
const productsToAdd = cookieProducts.filter((product) => !apiProductUuids.includes(product));
|
||||||
|
|
||||||
if (productsToAdd.length === 0) {
|
if (productsToAdd.length === 0) {
|
||||||
cookieWishlist.value = [];
|
cookieWishlist.value = [];
|
||||||
|
|
@ -41,7 +41,7 @@ export function useWishlistSync() {
|
||||||
bulkAction: 'add',
|
bulkAction: 'add',
|
||||||
isBulkSync: true,
|
isBulkSync: true,
|
||||||
products: productsToAdd.map((p) => ({
|
products: productsToAdd.map((p) => ({
|
||||||
uuid: p.uuid,
|
uuid: p,
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@ import type { LocaleDefinition } from '@types';
|
||||||
export const SUPPORTED_LOCALES: LocaleDefinition[] = [
|
export const SUPPORTED_LOCALES: LocaleDefinition[] = [
|
||||||
{
|
{
|
||||||
code: 'en-gb',
|
code: 'en-gb',
|
||||||
file: 'en-gb.json',
|
file: 'en-gb.json'
|
||||||
default: true,
|
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// code: 'ar-ar',
|
// code: 'ar-ar',
|
||||||
|
|
@ -73,8 +72,7 @@ export const SUPPORTED_LOCALES: LocaleDefinition[] = [
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
code: 'ru-ru',
|
code: 'ru-ru',
|
||||||
file: 'ru-ru.json',
|
file: 'ru-ru.json'
|
||||||
default: false,
|
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// code: 'zh-hans',
|
// code: 'zh-hans',
|
||||||
|
|
@ -83,4 +81,4 @@ export const SUPPORTED_LOCALES: LocaleDefinition[] = [
|
||||||
// },
|
// },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const DEFAULT_LOCALE = SUPPORTED_LOCALES.find((locale) => locale.default)?.code || 'en-gb';
|
export const DEFAULT_LOCALE_FALLBACK = 'en-gb';
|
||||||
|
|
@ -79,7 +79,8 @@
|
||||||
class="product__main-button"
|
class="product__main-button"
|
||||||
@click="overwriteWishlist({
|
@click="overwriteWishlist({
|
||||||
type: (isProductInWishlist ? 'remove' : 'add'),
|
type: (isProductInWishlist ? 'remove' : 'add'),
|
||||||
product: product
|
productUuid: product.uuid,
|
||||||
|
productName: product.name,
|
||||||
})"
|
})"
|
||||||
:type="'button'"
|
:type="'button'"
|
||||||
:style="'secondary'"
|
:style="'secondary'"
|
||||||
|
|
@ -165,6 +166,7 @@ const route = useRoute();
|
||||||
const {t, locale} = useI18n();
|
const {t, locale} = useI18n();
|
||||||
const wishlistStore = useWishlistStore();
|
const wishlistStore = useWishlistStore();
|
||||||
const cartStore = useCartStore();
|
const cartStore = useCartStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
const { $appHelpers } = useNuxtApp();
|
const { $appHelpers } = useNuxtApp();
|
||||||
|
|
||||||
const { setPageTitle } = usePageTitle();
|
const { setPageTitle } = usePageTitle();
|
||||||
|
|
@ -176,6 +178,13 @@ const { overwriteWishlist } = useWishlistOverwrite();
|
||||||
const { addLoading, removeLoading, overwriteOrder } = useOrderOverwrite();
|
const { addLoading, removeLoading, overwriteOrder } = useOrderOverwrite();
|
||||||
const { product, seoMeta } = await useProductBySlug(slug.value);
|
const { product, seoMeta } = await useProductBySlug(slug.value);
|
||||||
|
|
||||||
|
const cookieWishlist = useCookie($appHelpers.COOKIES_WISHLIST_KEY, {
|
||||||
|
default: () => [],
|
||||||
|
path: '/',
|
||||||
|
});
|
||||||
|
|
||||||
|
const isAuthenticated = computed(() => userStore.isAuthenticated);
|
||||||
|
|
||||||
const meta = useDefaultSeo(seoMeta.value || null);
|
const meta = useDefaultSeo(seoMeta.value || null);
|
||||||
|
|
||||||
if (meta) {
|
if (meta) {
|
||||||
|
|
@ -211,18 +220,39 @@ if (meta) {
|
||||||
const { products } = useProducts({ categoriesSlugs: product.value?.category.slug });
|
const { products } = useProducts({ categoriesSlugs: product.value?.category.slug });
|
||||||
|
|
||||||
const isProductInWishlist = computed(() => {
|
const isProductInWishlist = computed(() => {
|
||||||
const el = wishlistStore.wishlist?.products?.edges.find(
|
if (isAuthenticated.value) {
|
||||||
(el) => el?.node?.uuid === product.value?.uuid
|
return !!wishlistStore.wishlist?.products?.edges.find(
|
||||||
|
(el) => el?.node?.uuid === product.value.uuid
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
return !!el;
|
return (cookieWishlist.value ?? []).includes(product.value.uuid);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const isProductInCart = computed(() => {
|
const isProductInCart = computed(() => {
|
||||||
return cartStore.currentOrder?.orderProducts?.edges.find((prod) => prod.node.product.uuid === product.value?.uuid);
|
if (isAuthenticated.value) {
|
||||||
|
return !!cartStore.currentOrder?.orderProducts?.edges.find(
|
||||||
|
(prod) => prod.node.product.uuid === product.value?.uuid
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (cookieCart.value ?? []).some(
|
||||||
|
(item) => item.product.uuid === product.value?.uuid
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const productInCartQuantity = computed(() => {
|
const productInCartQuantity = computed(() => {
|
||||||
return cartStore.currentOrder?.orderProducts?.edges.filter((prod) => prod.node.product.uuid === product.value.uuid)[0].node.quantity;
|
if (isAuthenticated.value) {
|
||||||
|
const productEdge = cartStore.currentOrder?.orderProducts?.edges.find(
|
||||||
|
(prod) => prod.node.product.uuid === product.value.uuid
|
||||||
|
);
|
||||||
|
return productEdge?.node.quantity ?? 0;
|
||||||
|
} else {
|
||||||
|
const cartItem = (cookieCart.value ?? []).find(
|
||||||
|
(item) => item.product === product.value.uuid
|
||||||
|
);
|
||||||
|
return cartItem?.quantity ?? 0;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const images = computed<string[]>(() =>
|
const images = computed<string[]>(() =>
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {usePageTitle} from '@composables/utils';
|
import {usePageTitle} from '@composables/utils';
|
||||||
import { useDate } from '@composables/date';
|
import { useDate } from '@composables/date';
|
||||||
import {DEFAULT_LOCALE} from '@appConstants';
|
|
||||||
import {useAvatarUpload} from '@composables/user';
|
import {useAvatarUpload} from '@composables/user';
|
||||||
import {useNotification} from '@composables/notification';
|
import {useNotification} from '@composables/notification';
|
||||||
|
|
||||||
|
|
@ -62,7 +61,7 @@ const { uploadAvatar } = useAvatarUpload();
|
||||||
const cookieLocale = useCookie(
|
const cookieLocale = useCookie(
|
||||||
$appHelpers. COOKIES_LOCALE_KEY,
|
$appHelpers. COOKIES_LOCALE_KEY,
|
||||||
{
|
{
|
||||||
default: () => DEFAULT_LOCALE,
|
default: () => $appHelpers.DEFAULT_LOCALE,
|
||||||
path: '/'
|
path: '/'
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -77,9 +76,9 @@ const joinData = computed(() => {
|
||||||
|
|
||||||
const referralLink = computed(() => {
|
const referralLink = computed(() => {
|
||||||
if (finishedOrdersQuantity.value > 0) {
|
if (finishedOrdersQuantity.value > 0) {
|
||||||
return `https://${$appHelpers.APP_DOMAIN}/${DEFAULT_LOCALE}/?referrer=` + user.value?.uuid;
|
return `https://${$appHelpers.APP_DOMAIN}/${$appHelpers.DEFAULT_LOCALE}/?referrer=` + user.value?.uuid;
|
||||||
} else {
|
} else {
|
||||||
return `https://${$appHelpers.APP_DOMAIN}/${DEFAULT_LOCALE}/`;
|
return `https://${$appHelpers.APP_DOMAIN}/${$appHelpers.DEFAULT_LOCALE}/`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@
|
||||||
import {usePageTitle} from '@composables/utils';
|
import {usePageTitle} from '@composables/utils';
|
||||||
|
|
||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
const { $appHelpers } = useNuxtApp();
|
|
||||||
|
|
||||||
const { setPageTitle } = usePageTitle();
|
const { setPageTitle } = usePageTitle();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
import { COOKIE_KEY_TEMPLATES } from '~/constants';
|
import { COOKIE_KEY_TEMPLATES, DEFAULT_LOCALE_FALLBACK, SUPPORTED_LOCALES } from '~/constants';
|
||||||
|
import { createProjectKey } from "~/utils/transliterator";
|
||||||
|
|
||||||
export default defineNuxtPlugin(() => {
|
export default defineNuxtPlugin(() => {
|
||||||
const runtimeConfig = useRuntimeConfig();
|
const runtimeConfig = useRuntimeConfig();
|
||||||
const APP_NAME = runtimeConfig.public.schonProjectName as string;
|
const APP_NAME = runtimeConfig.public.schonProjectName as string;
|
||||||
const APP_NAME_KEY = APP_NAME.toLowerCase().replace(/\s+/g, '-');
|
const APP_NAME_KEY = createProjectKey(APP_NAME);
|
||||||
|
|
||||||
|
const envLocale = runtimeConfig.public.schonLanguageCode as string;
|
||||||
|
const isValid = SUPPORTED_LOCALES.some(locale => locale.code === envLocale);
|
||||||
|
const DEFAULT_LOCALE = isValid ? envLocale : DEFAULT_LOCALE_FALLBACK;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
provide: {
|
provide: {
|
||||||
|
|
@ -11,6 +16,7 @@ export default defineNuxtPlugin(() => {
|
||||||
APP_DOMAIN: runtimeConfig.public.schonBaseDomain,
|
APP_DOMAIN: runtimeConfig.public.schonBaseDomain,
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
APP_NAME_KEY,
|
APP_NAME_KEY,
|
||||||
|
DEFAULT_LOCALE,
|
||||||
COOKIES_LOCALE_KEY: COOKIE_KEY_TEMPLATES.LOCALE(APP_NAME_KEY),
|
COOKIES_LOCALE_KEY: COOKIE_KEY_TEMPLATES.LOCALE(APP_NAME_KEY),
|
||||||
COOKIES_REFRESH_TOKEN_KEY: COOKIE_KEY_TEMPLATES.REFRESH_TOKEN(APP_NAME_KEY),
|
COOKIES_REFRESH_TOKEN_KEY: COOKIE_KEY_TEMPLATES.REFRESH_TOKEN(APP_NAME_KEY),
|
||||||
COOKIES_ACCESS_TOKEN_KEY: COOKIE_KEY_TEMPLATES.ACCESS_TOKEN(APP_NAME_KEY),
|
COOKIES_ACCESS_TOKEN_KEY: COOKIE_KEY_TEMPLATES.ACCESS_TOKEN(APP_NAME_KEY),
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
import { DEFAULT_LOCALE } from '@appConstants';
|
|
||||||
import type { ILanguage } from '@types';
|
import type { ILanguage } from '@types';
|
||||||
|
|
||||||
export const useLanguageStore = defineStore('language', () => {
|
export const useLanguageStore = defineStore('language', () => {
|
||||||
const { $appHelpers } = useNuxtApp();
|
const { $appHelpers } = useNuxtApp();
|
||||||
|
|
||||||
const cookieLocale = useCookie($appHelpers.COOKIES_LOCALE_KEY, {
|
const cookieLocale = useCookie($appHelpers.COOKIES_LOCALE_KEY, {
|
||||||
default: () => DEFAULT_LOCALE,
|
default: () => $appHelpers.DEFAULT_LOCALE,
|
||||||
path: '/',
|
path: '/',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
99
storefront/app/utils/transliterator.ts
Normal file
99
storefront/app/utils/transliterator.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
|
||||||
|
export function transliterate(str: string | undefined): string {
|
||||||
|
if (!str) return '';
|
||||||
|
|
||||||
|
const map = {
|
||||||
|
// Russian/Ukrainian/Belarusian Cyrillic
|
||||||
|
'А': 'A', 'Б': 'B', 'В': 'V', 'Г': 'G', 'Д': 'D', 'Е': 'E', 'Ё': 'Yo', 'Ж': 'Zh',
|
||||||
|
'З': 'Z', 'И': 'I', 'Й': 'Y', 'К': 'K', 'Л': 'L', 'М': 'M', 'Н': 'N', 'О': 'O',
|
||||||
|
'П': 'P', 'Р': 'R', 'С': 'S', 'Т': 'T', 'У': 'U', 'Ф': 'F', 'Х': 'Kh', 'Ц': 'Ts',
|
||||||
|
'Ч': 'Ch', 'Ш': 'Sh', 'Щ': 'Shch', 'Ъ': '', 'Ы': 'Y', 'Ь': '', 'Э': 'E', 'Ю': 'Yu',
|
||||||
|
'Я': 'Ya',
|
||||||
|
'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd', 'е': 'e', 'ё': 'yo', 'ж': 'zh',
|
||||||
|
'з': 'z', 'и': 'i', 'й': 'y', 'к': 'k', 'л': 'l', 'м': 'm', 'н': 'n', 'о': 'o',
|
||||||
|
'п': 'p', 'р': 'r', 'с': 's', 'т': 't', 'у': 'u', 'ф': 'f', 'х': 'kh', 'ц': 'ts',
|
||||||
|
'ч': 'ch', 'ш': 'sh', 'щ': 'shch', 'ъ': '', 'ы': 'y', 'ь': '', 'э': 'e', 'ю': 'yu',
|
||||||
|
'я': 'ya',
|
||||||
|
|
||||||
|
// Ukrainian extras
|
||||||
|
'Ґ': 'G', 'ґ': 'g', 'Є': 'Ye', 'є': 'ye', 'І': 'I', 'і': 'i', 'Ї': 'Yi', 'ї': 'yi',
|
||||||
|
|
||||||
|
// Serbian/Macedonian Cyrillic extras
|
||||||
|
'Ђ': 'Dj', 'ђ': 'dj', 'Ј': 'J', 'ј': 'j', 'Љ': 'Lj', 'љ': 'lj', 'Њ': 'Nj', 'њ': 'nj',
|
||||||
|
'Ћ': 'C', 'ћ': 'c', 'Џ': 'Dz', 'џ': 'dz', 'Ѓ': 'Gj', 'ѓ': 'gj', 'Ѕ': 'Dz', 'ѕ': 'dz',
|
||||||
|
'Ќ': 'Kj', 'ќ': 'kj',
|
||||||
|
|
||||||
|
// Belarusian extras
|
||||||
|
'Ў': 'U', 'ў': 'u',
|
||||||
|
|
||||||
|
// Bulgarian extras (mostly covered above, but ъ is different)
|
||||||
|
// In Bulgarian context ъ = 'a', but we keep '' as default for Russian
|
||||||
|
|
||||||
|
// Greek
|
||||||
|
'Α': 'A', 'Β': 'V', 'Γ': 'G', 'Δ': 'D', 'Ε': 'E', 'Ζ': 'Z', 'Η': 'I', 'Θ': 'Th',
|
||||||
|
'Ι': 'I', 'Κ': 'K', 'Λ': 'L', 'Μ': 'M', 'Ν': 'N', 'Ξ': 'X', 'Ο': 'O', 'Π': 'P',
|
||||||
|
'Ρ': 'R', 'Σ': 'S', 'Τ': 'T', 'Υ': 'Y', 'Φ': 'F', 'Χ': 'Ch', 'Ψ': 'Ps', 'Ω': 'O',
|
||||||
|
'α': 'a', 'β': 'v', 'γ': 'g', 'δ': 'd', 'ε': 'e', 'ζ': 'z', 'η': 'i', 'θ': 'th',
|
||||||
|
'ι': 'i', 'κ': 'k', 'λ': 'l', 'μ': 'm', 'ν': 'n', 'ξ': 'x', 'ο': 'o', 'π': 'p',
|
||||||
|
'ρ': 'r', 'σ': 's', 'ς': 's', 'τ': 't', 'υ': 'y', 'φ': 'f', 'χ': 'ch', 'ψ': 'ps', 'ω': 'o',
|
||||||
|
|
||||||
|
// Arabic
|
||||||
|
'ا': 'a', 'ب': 'b', 'ت': 't', 'ث': 'th', 'ج': 'j', 'ح': 'h', 'خ': 'kh', 'د': 'd',
|
||||||
|
'ذ': 'dh', 'ر': 'r', 'ز': 'z', 'س': 's', 'ش': 'sh', 'ص': 's', 'ض': 'd', 'ط': 't',
|
||||||
|
'ظ': 'z', 'ع': 'a', 'غ': 'gh', 'ف': 'f', 'ق': 'q', 'ك': 'k', 'ل': 'l', 'م': 'm',
|
||||||
|
'ن': 'n', 'ه': 'h', 'و': 'w', 'ي': 'y', 'ة': 'h', 'ء': '\'', 'آ': 'aa',
|
||||||
|
|
||||||
|
// German/Nordic/Central European diacritics
|
||||||
|
'Ä': 'Ae', 'ä': 'ae', 'Ö': 'Oe', 'ö': 'oe', 'Ü': 'Ue', 'ü': 'ue', 'ß': 'ss',
|
||||||
|
'Å': 'A', 'å': 'a', 'Æ': 'Ae', 'æ': 'ae', 'Ø': 'O', 'ø': 'o',
|
||||||
|
'Ð': 'D', 'ð': 'd', 'Þ': 'Th', 'þ': 'th',
|
||||||
|
|
||||||
|
// Czech/Slovak/Polish/Hungarian/Romanian/Turkish
|
||||||
|
'Č': 'C', 'č': 'c', 'Ď': 'D', 'ď': 'd', 'Ě': 'E', 'ě': 'e', 'Ň': 'N', 'ň': 'n',
|
||||||
|
'Ř': 'R', 'ř': 'r', 'Š': 'S', 'š': 's', 'Ť': 'T', 'ť': 't', 'Ů': 'U', 'ů': 'u',
|
||||||
|
'Ž': 'Z', 'ž': 'z', 'Ľ': 'L', 'ľ': 'l', 'Ĺ': 'L', 'ĺ': 'l', 'Ŕ': 'R', 'ŕ': 'r',
|
||||||
|
'Ą': 'A', 'ą': 'a', 'Ć': 'C', 'ć': 'c', 'Ę': 'E', 'ę': 'e', 'Ł': 'L', 'ł': 'l',
|
||||||
|
'Ń': 'N', 'ń': 'n', 'Ś': 'S', 'ś': 's', 'Ź': 'Z', 'ź': 'z', 'Ż': 'Z', 'ż': 'z',
|
||||||
|
'Á': 'A', 'á': 'a', 'É': 'E', 'é': 'e', 'Í': 'I', 'í': 'i', 'Ó': 'O', 'ó': 'o',
|
||||||
|
'Ú': 'U', 'ú': 'u', 'Ý': 'Y', 'ý': 'y',
|
||||||
|
'Ő': 'O', 'ő': 'o', 'Ű': 'U', 'ű': 'u',
|
||||||
|
'Ă': 'A', 'ă': 'a', 'Â': 'A', 'â': 'a', 'Î': 'I', 'î': 'i', 'Ș': 'S', 'ș': 's', 'Ț': 'T', 'ț': 't',
|
||||||
|
'Ç': 'C', 'ç': 'c', 'Ğ': 'G', 'ğ': 'g', 'İ': 'I', 'ı': 'i', 'Ş': 'S', 'ş': 's',
|
||||||
|
|
||||||
|
// Vietnamese (basic - stripped of tone marks conceptually)
|
||||||
|
'Đ': 'D', 'đ': 'd',
|
||||||
|
|
||||||
|
// Spanish/Portuguese/French
|
||||||
|
'Ñ': 'N', 'ñ': 'n',
|
||||||
|
'À': 'A', 'à': 'a', 'È': 'E', 'è': 'e', 'Ì': 'I', 'ì': 'i', 'Ò': 'O', 'ò': 'o',
|
||||||
|
'Ù': 'U', 'ù': 'u', 'Ë': 'E', 'ë': 'e', 'Ï': 'I', 'ï': 'i',
|
||||||
|
'Ê': 'E', 'ê': 'e', 'Ô': 'O', 'ô': 'o', 'Û': 'U', 'û': 'u',
|
||||||
|
'Ã': 'A', 'ã': 'a', 'Õ': 'O', 'õ': 'o',
|
||||||
|
};
|
||||||
|
|
||||||
|
return str.split('').map(ch => {
|
||||||
|
if (ch in map) return map[ch];
|
||||||
|
|
||||||
|
const normalized = ch
|
||||||
|
.normalize('NFD')
|
||||||
|
.replace(/[\u0300-\u036f]/g, '');
|
||||||
|
|
||||||
|
if (normalized !== ch && /^[\x20-\x7E]+$/.test(normalized)) {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ch;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createProjectKey(str: string | undefined): string {
|
||||||
|
if (!str) return 'default-project';
|
||||||
|
|
||||||
|
return transliterate(str)
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/['"]/g, '')
|
||||||
|
.replace(/[^a-z0-9\s-]/g, '')
|
||||||
|
.trim()
|
||||||
|
.replace(/\s+/g, '-')
|
||||||
|
.replace(/-+/g, '-');
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,16 @@
|
||||||
import type { NuxtI18nOptions } from '@nuxtjs/i18n';
|
import type { NuxtI18nOptions } from '@nuxtjs/i18n';
|
||||||
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '../app/constants';
|
import { SUPPORTED_LOCALES } from '../app/constants';
|
||||||
|
import { createProjectKey } from "../app/utils/transliterator";
|
||||||
|
|
||||||
export const i18nConfig: NuxtI18nOptions = {
|
export function createI18nConfig(defaultLocaleCode?: string, projectName: string): NuxtI18nOptions {
|
||||||
defaultLocale: DEFAULT_LOCALE,
|
const defaultLocale = defaultLocaleCode && SUPPORTED_LOCALES.some(l => l.code === defaultLocaleCode)
|
||||||
|
? defaultLocaleCode
|
||||||
|
: 'en-gb';
|
||||||
|
|
||||||
|
const cookieKey = `${createProjectKey(projectName)}-locale`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
defaultLocale,
|
||||||
locales: SUPPORTED_LOCALES.map((locale) => ({
|
locales: SUPPORTED_LOCALES.map((locale) => ({
|
||||||
code: locale.code,
|
code: locale.code,
|
||||||
file: locale.file,
|
file: locale.file,
|
||||||
|
|
@ -12,7 +20,8 @@ export const i18nConfig: NuxtI18nOptions = {
|
||||||
detectBrowserLanguage: {
|
detectBrowserLanguage: {
|
||||||
alwaysRedirect: true,
|
alwaysRedirect: true,
|
||||||
redirectOn: 'root',
|
redirectOn: 'root',
|
||||||
fallbackLocale: DEFAULT_LOCALE,
|
fallbackLocale: defaultLocale,
|
||||||
cookieKey: 'schon-locale',
|
cookieKey,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
1
storefront/nuxt-app.d.ts
vendored
1
storefront/nuxt-app.d.ts
vendored
|
|
@ -7,6 +7,7 @@ declare module '#app' {
|
||||||
APP_DOMAIN: string;
|
APP_DOMAIN: string;
|
||||||
APP_NAME: string;
|
APP_NAME: string;
|
||||||
APP_NAME_KEY: string;
|
APP_NAME_KEY: string;
|
||||||
|
DEFAULT_LOCALE: string;
|
||||||
COOKIES_LOCALE_KEY: string;
|
COOKIES_LOCALE_KEY: string;
|
||||||
COOKIES_REFRESH_TOKEN_KEY: string;
|
COOKIES_REFRESH_TOKEN_KEY: string;
|
||||||
COOKIES_ACCESS_TOKEN_KEY: string;
|
COOKIES_ACCESS_TOKEN_KEY: string;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { resolve } from 'node:path';
|
import { resolve } from 'node:path';
|
||||||
import { fileURLToPath, URL } from 'node:url';
|
import { fileURLToPath, URL } from 'node:url';
|
||||||
import { i18nConfig } from './i18n/i18.config';
|
import { createProjectKey } from './app/utils/transliterator';
|
||||||
|
import { createI18nConfig } from './i18n/i18.config';
|
||||||
|
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
compatibilityDate: '2025-07-15',
|
compatibilityDate: '2025-07-15',
|
||||||
|
|
@ -22,7 +23,7 @@ export default defineNuxtConfig({
|
||||||
'@nuxt/hints',
|
'@nuxt/hints',
|
||||||
'@nuxt/image',
|
'@nuxt/image',
|
||||||
],
|
],
|
||||||
i18n: i18nConfig,
|
i18n: createI18nConfig(process.env.SCHON_LANGUAGE_CODE),
|
||||||
apollo: {
|
apollo: {
|
||||||
autoImports: true,
|
autoImports: true,
|
||||||
clients: {
|
clients: {
|
||||||
|
|
@ -32,7 +33,7 @@ export default defineNuxtConfig({
|
||||||
authType: 'Bearer',
|
authType: 'Bearer',
|
||||||
authHeader: 'X-SCHON-AUTH',
|
authHeader: 'X-SCHON-AUTH',
|
||||||
tokenStorage: 'cookie',
|
tokenStorage: 'cookie',
|
||||||
tokenName: `${process.env.SCHON_PROJECT_NAME?.toLowerCase().replace(/\s+/g, '-')}-access`,
|
tokenName: `${createProjectKey(process.env.SCHON_PROJECT_NAME)}-access`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -40,6 +41,7 @@ export default defineNuxtConfig({
|
||||||
public: {
|
public: {
|
||||||
schonProjectName: process.env.SCHON_PROJECT_NAME,
|
schonProjectName: process.env.SCHON_PROJECT_NAME,
|
||||||
schonBaseDomain: process.env.SCHON_BASE_DOMAIN,
|
schonBaseDomain: process.env.SCHON_BASE_DOMAIN,
|
||||||
|
schonLanguageCode: process.env.SCHON_LANGUAGE_CODE,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
app: {
|
app: {
|
||||||
|
|
@ -75,6 +77,7 @@ export default defineNuxtConfig({
|
||||||
'@appConstants': fileURLToPath(new URL('./app/constants', import.meta.url)),
|
'@appConstants': fileURLToPath(new URL('./app/constants', import.meta.url)),
|
||||||
'@composables': fileURLToPath(new URL('./app/composables', import.meta.url)),
|
'@composables': fileURLToPath(new URL('./app/composables', import.meta.url)),
|
||||||
'@types': fileURLToPath(new URL('./types', import.meta.url)),
|
'@types': fileURLToPath(new URL('./types', import.meta.url)),
|
||||||
|
'@utils': fileURLToPath(new URL('./app/utils', import.meta.url)),
|
||||||
},
|
},
|
||||||
vite: {
|
vite: {
|
||||||
envDir: '../',
|
envDir: '../',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue