Fixes: Added more Vue integrations for Astro;
|
|
@ -1,7 +1,8 @@
|
||||||
import node from '@astrojs/node';
|
|
||||||
import { defineConfig } from 'astro/config';
|
import { defineConfig } from 'astro/config';
|
||||||
|
import node from '@astrojs/node';
|
||||||
import vue from '@astrojs/vue';
|
import vue from '@astrojs/vue';
|
||||||
import { fileURLToPath, URL } from 'node:url'
|
import {fileURLToPath, URL} from "node:url";
|
||||||
|
import {DEFAULT_LOCALE, SUPPORTED_LOCALES} from "/src/config/index.js";
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
|
@ -9,14 +10,21 @@ export default defineConfig({
|
||||||
vue({
|
vue({
|
||||||
appEntrypoint: '/src/plugins/index.js',
|
appEntrypoint: '/src/plugins/index.js',
|
||||||
devtools: { launchEditor: "webstorm" }
|
devtools: { launchEditor: "webstorm" }
|
||||||
})
|
}),
|
||||||
],
|
],
|
||||||
|
i18n: {
|
||||||
|
defaultLocale: DEFAULT_LOCALE,
|
||||||
|
locales: SUPPORTED_LOCALES.map(locale => locale.code),
|
||||||
|
routing: {
|
||||||
|
prefixDefaultLocale: true
|
||||||
|
}
|
||||||
|
},
|
||||||
output: 'server',
|
output: 'server',
|
||||||
adapter: node({
|
adapter: node({
|
||||||
mode: 'standalone'
|
mode: 'standalone'
|
||||||
}),
|
}),
|
||||||
vite: {
|
vite: {
|
||||||
envDir: '../',
|
// envDir: './',
|
||||||
envPrefix: 'EVIBES_',
|
envPrefix: 'EVIBES_',
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|
|
||||||
12
storefront/jsconfig.json
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"],
|
||||||
|
"@styles/*": ["src/assets/styles/*"],
|
||||||
|
"@icons/*": ["src/assets/icons/*"],
|
||||||
|
"@images/*": ["src/assets/images/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src", "astro.config.mjs"]
|
||||||
|
}
|
||||||
2162
storefront/package-lock.json
generated
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "evibes-storefront",
|
"name": "astro",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
@ -9,29 +9,26 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.13.8",
|
|
||||||
"@astrojs/node": "^9.2.2",
|
"@astrojs/node": "^9.2.2",
|
||||||
"@astrojs/vue": "^5.1.0",
|
"@astrojs/vue": "^5.1.0",
|
||||||
"@vue/apollo-composable": "^4.2.2",
|
"@nanostores/vue": "^1.0.0",
|
||||||
"@vueuse/core": "^13.2.0",
|
"@vueuse/core": "^13.2.0",
|
||||||
|
"@vueuse/integrations": "^13.3.0",
|
||||||
"astro": "^5.8.1",
|
"astro": "^5.8.1",
|
||||||
"element-plus": "^2.9.11",
|
"element-plus": "^2.9.11",
|
||||||
"graphql": "^16.11.0",
|
"graphql": "^16.11.0",
|
||||||
|
"graphql-request": "^7.2.0",
|
||||||
"graphql-tag": "^2.12.6",
|
"graphql-tag": "^2.12.6",
|
||||||
"pinia": "^3.0.1",
|
"nanostores": "^1.0.1",
|
||||||
"primeicons": "^7.0.0",
|
"primeicons": "^7.0.0",
|
||||||
"swiper": "^11.2.8",
|
"swiper": "^11.2.8",
|
||||||
|
"universal-cookie": "^7.2.2",
|
||||||
"vue": "^3.5.16",
|
"vue": "^3.5.16",
|
||||||
"vue-i18n": "^11.1.4",
|
"vue-i18n": "^11.1.4",
|
||||||
"vue3-marquee-slider": "^1.0.5"
|
"vue3-marquee-slider": "^1.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"astro-eslint-parser": "^1.2.2",
|
|
||||||
"eslint": "^9.28.0",
|
|
||||||
"eslint-plugin-astro": "^1.3.1",
|
|
||||||
"eslint-plugin-vue": "^10.1.0",
|
|
||||||
"sass": "^1.83.0",
|
"sass": "^1.83.0",
|
||||||
"sass-loader": "^16.0.4",
|
"sass-loader": "^16.0.4"
|
||||||
"vue-eslint-parser": "^10.1.3"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,9 @@
|
||||||
<?xml version="1.0" standalone="no"?>
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
||||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
<style>
|
||||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
path { fill: #000; }
|
||||||
width="100.000000pt" height="100.000000pt" viewBox="0 0 100.000000 100.000000"
|
@media (prefers-color-scheme: dark) {
|
||||||
preserveAspectRatio="xMidYMid meet">
|
path { fill: #FFF; }
|
||||||
|
}
|
||||||
<g transform="translate(0.000000,100.000000) scale(0.100000,-0.100000)"
|
</style>
|
||||||
fill="#7965D1" stroke="none">
|
|
||||||
<path d="M678 935 c-73 -50 -88 -121 -38 -175 29 -31 50 -35 57 -13 2 6 -5 14
|
|
||||||
-16 18 -30 9 -26 48 9 88 63 72 130 72 149 -1 18 -67 -6 -117 -89 -182 -97
|
|
||||||
-76 -142 -97 -235 -109 -121 -16 -324 -29 -380 -24 -48 5 -49 4 -33 -13 26
|
|
||||||
-26 108 -34 248 -23 308 23 362 40 480 147 l65 59 0 64 c0 79 -17 114 -72 152
|
|
||||||
-61 41 -100 44 -145 12z"/>
|
|
||||||
<path d="M327 912 c-10 -10 -17 -27 -17 -38 0 -24 35 -64 55 -64 18 0 19 12 3
|
|
||||||
28 -16 16 19 54 46 50 17 -2 22 -11 24 -45 4 -55 -38 -105 -105 -124 -50 -14
|
|
||||||
-179 -17 -225 -6 -34 9 -36 -3 -6 -23 55 -35 251 -29 327 10 95 48 92 168 -6
|
|
||||||
219 -33 17 -78 13 -96 -7z"/>
|
|
||||||
<path d="M475 435 c-60 -8 -171 -19 -245 -25 -74 -7 -137 -14 -139 -16 -2 -2
|
|
||||||
9 -9 25 -16 35 -15 179 -13 309 3 50 7 146 12 215 13 186 2 223 -22 185 -119
|
|
||||||
-20 -53 -49 -78 -115 -100 -37 -12 -54 -14 -69 -5 -41 21 -16 91 36 105 27 6
|
|
||||||
27 7 9 21 -31 22 -69 17 -99 -14 -15 -15 -27 -34 -27 -42 0 -23 52 -90 81
|
|
||||||
-106 43 -22 73 -17 144 22 73 40 93 64 102 118 21 131 -138 193 -412 161z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 749 B |
BIN
storefront/public/images/evibes-big-simple.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
|
@ -1,43 +0,0 @@
|
||||||
import pkg from '@apollo/client/core';
|
|
||||||
import { setContext } from '@apollo/client/link/context'
|
|
||||||
import {DEFAULT_LOCALE, LOCALE_STORAGE_ACCESS_TOKEN_KEY, LOCALE_STORAGE_LOCALE_KEY} from "@/config/index.js";
|
|
||||||
import {computed} from "vue";
|
|
||||||
|
|
||||||
const { ApolloClient, ApolloLink, createHttpLink, InMemoryCache } = pkg;
|
|
||||||
|
|
||||||
const httpLink = createHttpLink({
|
|
||||||
uri: 'https://api.' + import.meta.env.EVIBES_BASE_DOMAIN + '/graphql/',
|
|
||||||
});
|
|
||||||
|
|
||||||
const userLocale = computed(() => {
|
|
||||||
return localStorage.getItem(LOCALE_STORAGE_LOCALE_KEY)
|
|
||||||
});
|
|
||||||
|
|
||||||
export const createApolloClient = () => {
|
|
||||||
const accessToken = computed(() => {
|
|
||||||
return localStorage.getItem(LOCALE_STORAGE_ACCESS_TOKEN_KEY)
|
|
||||||
})
|
|
||||||
|
|
||||||
const authLink = setContext((_, { headers }) => {
|
|
||||||
const baseHeaders = {
|
|
||||||
...headers,
|
|
||||||
"Accept-language": userLocale.value ? userLocale.value : DEFAULT_LOCALE,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (accessToken.value) {
|
|
||||||
baseHeaders["X-EVIBES-AUTH"] = `Bearer ${accessToken.value}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { headers: baseHeaders };
|
|
||||||
})
|
|
||||||
|
|
||||||
return new ApolloClient({
|
|
||||||
link: authLink.concat(httpLink),
|
|
||||||
cache: new InMemoryCache(),
|
|
||||||
defaultOptions: {
|
|
||||||
watchQuery: {
|
|
||||||
fetchPolicy: 'cache-and-network',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
<template>
|
|
||||||
<div style="display: none;"></div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { useRefresh } from "@/composables/auth";
|
|
||||||
import { useCompanyInfo } from "@/composables/company";
|
|
||||||
import { useLanguages } from "@/composables/languages/index.js";
|
|
||||||
import { onMounted } from "vue";
|
|
||||||
|
|
||||||
const { refresh } = useRefresh();
|
|
||||||
const { getCompanyInfo } = useCompanyInfo();
|
|
||||||
const { getLanguages } = useLanguages();
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await refresh();
|
|
||||||
await getCompanyInfo();
|
|
||||||
await getLanguages();
|
|
||||||
|
|
||||||
setInterval(async () => {
|
|
||||||
await refresh();
|
|
||||||
}, 600000);
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="auth">
|
|
||||||
<div class="auth__content" ref="modalRef">
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {useAppStore} from "@/stores/app.js";
|
|
||||||
import {ref} from "vue";
|
|
||||||
import {onClickOutside} from "@vueuse/core";
|
|
||||||
|
|
||||||
const appStore = useAppStore()
|
|
||||||
|
|
||||||
const closeModal = () => {
|
|
||||||
appStore.setActiveState(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
const modalRef = ref(null)
|
|
||||||
onClickOutside(modalRef, () => closeModal())
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.auth {
|
|
||||||
position: fixed;
|
|
||||||
z-index: 3;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
left: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
backdrop-filter: blur(3px);
|
|
||||||
background-color: rgba(0, 0, 0, 0.4);
|
|
||||||
|
|
||||||
&__content {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 2;
|
|
||||||
top: 125px;
|
|
||||||
background-color: $white;
|
|
||||||
width: 600px;
|
|
||||||
padding: 30px;
|
|
||||||
border-radius: $default_border_radius;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1000px) {
|
|
||||||
.auth {
|
|
||||||
&__content {
|
|
||||||
width: 85%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 500px) {
|
|
||||||
.auth {
|
|
||||||
&__content {
|
|
||||||
padding: 20px 30px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
<template>
|
|
||||||
<footer class="footer">
|
|
||||||
<div class="container">
|
|
||||||
<div class="footer_wrapper">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.footer {
|
|
||||||
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -5,7 +5,8 @@
|
||||||
class="search__wrapper"
|
class="search__wrapper"
|
||||||
:class="[{ active: isSearchActive }]"
|
:class="[{ active: isSearchActive }]"
|
||||||
>
|
>
|
||||||
<form class="search__form" @submit.prevent="submitSearch">
|
<!-- <form class="search__form" @submit.prevent="submitSearch">-->
|
||||||
|
<form class="search__form" @submit.prevent="">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
v-model="query"
|
v-model="query"
|
||||||
|
|
@ -27,7 +28,7 @@
|
||||||
</form>
|
</form>
|
||||||
<div class="search__results" :class="[{ active: (searchResults && isSearchActive) || loading }]">
|
<div class="search__results" :class="[{ active: (searchResults && isSearchActive) || loading }]">
|
||||||
<header-search-skeleton v-if="loading" />
|
<header-search-skeleton v-if="loading" />
|
||||||
<div
|
<div
|
||||||
class="search__results-inner"
|
class="search__results-inner"
|
||||||
v-for="(blocks, item) in filteredSearchResults"
|
v-for="(blocks, item) in filteredSearchResults"
|
||||||
:key="item"
|
:key="item"
|
||||||
|
|
@ -49,6 +50,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="search__results-empty" v-if="!hasResults && query && !loading">
|
<div class="search__results-empty" v-if="!hasResults && query && !loading">
|
||||||
<p>{{ t('header.search.empty') }}</p>
|
<p>{{ t('header.search.empty') }}</p>
|
||||||
|
<!-- <p>header.search.empty</p>-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -61,11 +63,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {useI18n} from "vue-i18n";
|
|
||||||
import HeaderSearchSkeleton from "@/components/skeletons/header/header-search-skeleton.vue";
|
import HeaderSearchSkeleton from "@/components/skeletons/header/header-search-skeleton.vue";
|
||||||
import { useSearchUI } from "@/composables/search";
|
import {useSearchUI} from "@/composables/search/index.js";
|
||||||
|
import {useI18n} from "vue-i18n";
|
||||||
|
|
||||||
const {t} = useI18n();
|
const {t, locale} = useI18n()
|
||||||
|
|
||||||
|
console.log(locale.value)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
query,
|
query,
|
||||||
|
|
@ -79,13 +83,16 @@ const {
|
||||||
toggleSearch
|
toggleSearch
|
||||||
} = useSearchUI();
|
} = useSearchUI();
|
||||||
|
|
||||||
function submitSearch() {
|
// function submitSearch() {
|
||||||
if (query.value) {
|
// if (query.value) {
|
||||||
window.location.href = `/search?q=${encodeURIComponent(query.value)}`;
|
// router.push({
|
||||||
|
// name: 'search',
|
||||||
toggleSearch(false);
|
// query: { q: query.value }
|
||||||
}
|
// });
|
||||||
}
|
//
|
||||||
|
// toggleSearch(false);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
||||||
|
|
@ -1,74 +1,87 @@
|
||||||
<template>
|
<template>
|
||||||
<header class="header">
|
<header class="header">
|
||||||
<router-link :to="translations.i18nRoute({ name: 'home' })">
|
<a href="/">
|
||||||
<img class="header__logo" src="@images/evibes-big-simple.png" alt="logo">
|
<img class="header__logo" src="/images/evibes-big-simple.png" alt="logo">
|
||||||
</router-link>
|
</a>
|
||||||
|
<p>{{ t('field.search') }}</p>
|
||||||
<base-header-catalogue />
|
<base-header-catalogue />
|
||||||
<base-header-search />
|
<base-header-search />
|
||||||
<div class="header__actions">
|
<div class="header__actions">
|
||||||
<router-link :to="translations.i18nRoute({ name: 'wishlist' })" class="header__actions-item">
|
<!-- <router-link :to="translations.i18nRoute({ name: 'wishlist' })" class="header__actions-item">-->
|
||||||
<div>
|
<!-- <div>-->
|
||||||
<ui-counter>0</ui-counter>
|
<!-- <ui-counter>0</ui-counter>-->
|
||||||
<!-- <counter-skeleton />-->
|
<!--<!– <counter-skeleton />–>-->
|
||||||
<i class="pi pi-heart"></i>
|
<!-- <i class="pi pi-heart"></i>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
<p>{{ t('header.actions.wishlist') }}</p>
|
<!-- <p>{{ t('header.actions.wishlist') }}</p>-->
|
||||||
</router-link>
|
<!-- </router-link>-->
|
||||||
<router-link :to="translations.i18nRoute({ name: 'cart' })" class="header__actions-item">
|
<!-- <router-link :to="translations.i18nRoute({ name: 'cart' })" class="header__actions-item">-->
|
||||||
<div>
|
<!-- <div>-->
|
||||||
<ui-counter>0</ui-counter>
|
<!-- <ui-counter>0</ui-counter>-->
|
||||||
<!-- <counter-skeleton />-->
|
<!--<!– <counter-skeleton />–>-->
|
||||||
<i class="pi pi-shopping-cart"></i>
|
<!-- <i class="pi pi-shopping-cart"></i>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
<p>{{ t('header.actions.cart') }}</p>
|
<!-- <p>{{ t('header.actions.cart') }}</p>-->
|
||||||
</router-link>
|
<!-- </router-link>-->
|
||||||
<router-link
|
<!-- <router-link-->
|
||||||
:to="translations.i18nRoute({ name: 'home' })"
|
<!-- :to="translations.i18nRoute({ name: 'home' })"-->
|
||||||
class="header__actions-item"
|
<!-- class="header__actions-item"-->
|
||||||
v-if="isAuthenticated"
|
<!-- v-if="isAuthenticated"-->
|
||||||
>
|
<!-- >-->
|
||||||
<i class="pi pi-user"></i>
|
<!-- <i class="pi pi-user"></i>-->
|
||||||
<p>{{ t('header.actions.user') }}</p>
|
<!-- <p>{{ t('header.actions.user') }}</p>-->
|
||||||
</router-link>
|
<!-- </router-link>-->
|
||||||
<div
|
<!-- <div-->
|
||||||
class="header__actions-item"
|
<!-- class="header__actions-item"-->
|
||||||
@click="appStore.setActiveState('login')"
|
<!-- @click="appStore.setActiveState('login')"-->
|
||||||
v-else
|
<!-- v-else-->
|
||||||
>
|
<!-- >-->
|
||||||
<i class="pi pi-user"></i>
|
<!-- <i class="pi pi-user"></i>-->
|
||||||
<p>{{ t('header.actions.user') }}</p>
|
<!-- <p>{{ t('header.actions.user') }}</p>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
</div>
|
</div>
|
||||||
<ui-language-switcher />
|
<ui-language-switcher />
|
||||||
</header>
|
</header>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {computed, onMounted} from "vue";
|
// import {computed, onMounted} from "vue";
|
||||||
import {useCategories} from "@/composables/categories/index.js";
|
// import {useCategories} from "@/composables/categories/index.js";
|
||||||
import translations from "@/core/helpers/translations.js";
|
// import {useI18n} from "vue-i18n";
|
||||||
import {useI18n} from "vue-i18n";
|
|
||||||
import BaseHeaderSearch from "@/components/base/header/base-header-search.vue";
|
import BaseHeaderSearch from "@/components/base/header/base-header-search.vue";
|
||||||
import UiLanguageSwitcher from "@/components/ui/ui-language-switcher.vue";
|
import UiLanguageSwitcher from "@/components/ui/ui-language-switcher.vue";
|
||||||
import {useUserStore} from "@/stores/user.js";
|
// import {useUserStore} from "@/stores/user.js";
|
||||||
import {useAppStore} from "@/stores/app.js";
|
// import {useAppStore} from "@/stores/app.js";
|
||||||
import UiCounter from "@/components/ui/ui-counter.vue";
|
// import UiCounter from "@/components/ui/ui-counter.vue";
|
||||||
import CounterSkeleton from "@/components/skeletons/ui/counter-skeleton.vue";
|
// import CounterSkeleton from "@/components/skeletons/ui/counter-skeleton.vue";
|
||||||
import BaseHeaderCatalogue from "@/components/base/header/base-header-catalogue.vue";
|
import BaseHeaderCatalogue from "@/components/base/header/base-header-catalogue.vue";
|
||||||
|
import { createI18n, useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
//TODO: add categories to header
|
//TODO: add categories to header
|
||||||
|
|
||||||
const {t} = useI18n()
|
const props = defineProps({
|
||||||
const userStore = useUserStore()
|
locale: String,
|
||||||
const appStore = useAppStore()
|
messages: Object
|
||||||
|
|
||||||
const isAuthenticated = computed(() => userStore.user)
|
|
||||||
|
|
||||||
const { categories, loading, getCategories } = useCategories();
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await getCategories()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// const i18n = createI18n({
|
||||||
|
// locale: props.locale,
|
||||||
|
// messages: { [props.locale]: props.messages },
|
||||||
|
// });
|
||||||
|
|
||||||
|
const { t } = useI18n({ useScope: 'global', locale: props.locale, messages: { [props.locale]: props.messages } });
|
||||||
|
|
||||||
|
// const {t} = useI18n()
|
||||||
|
// const userStore = useUserStore()
|
||||||
|
// const appStore = useAppStore()
|
||||||
|
|
||||||
|
// const isAuthenticated = computed(() => userStore.user)
|
||||||
|
|
||||||
|
// const { categories, loading, getCategories } = useCategories();
|
||||||
|
|
||||||
|
// onMounted(async () => {
|
||||||
|
// await getCategories()
|
||||||
|
// })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
||||||
|
|
@ -1,127 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="card">
|
|
||||||
<router-link
|
|
||||||
:to="translations.i18nRoute({ name: 'product', params: { productSlug: product.slug } })"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
class="card__image"
|
|
||||||
:src="product.images.edges[0].node.image"
|
|
||||||
:alt="product.name"
|
|
||||||
>
|
|
||||||
</router-link>
|
|
||||||
<div class="card__content">
|
|
||||||
<p class="card__price">{{ product.price }}</p>
|
|
||||||
<p class="card__name">{{ product.name }}</p>
|
|
||||||
<el-rate
|
|
||||||
v-model="rating"
|
|
||||||
size="large"
|
|
||||||
allow-half
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
<div class="card__quantity">{{ t('cards.product.stock') }} {{ product.quantity }}</div>
|
|
||||||
<div class="card__bottom">
|
|
||||||
<ui-button class="card__bottom-button">
|
|
||||||
{{ t('buttons.addToCart') }}
|
|
||||||
</ui-button>
|
|
||||||
<div class="card__bottom-wishlist">
|
|
||||||
<i class="pi pi-heart"></i>
|
|
||||||
<!-- <i class="pi pi-heart-fill"></i>-->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {useI18n} from "vue-i18n";
|
|
||||||
import {computed} from "vue";
|
|
||||||
import UiButton from "@/components/ui/ui-button.vue";
|
|
||||||
import translations from "@/core/helpers/translations.js";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
product: Object
|
|
||||||
})
|
|
||||||
|
|
||||||
const {t} = useI18n()
|
|
||||||
|
|
||||||
const rating = computed(() => {
|
|
||||||
return props.product.feedbacks.edges[0] ? props.product.feedbacks.edges[0]?.node?.rating : 5
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.card {
|
|
||||||
border-radius: $default_border_radius;
|
|
||||||
border: 1px solid $black;
|
|
||||||
width: 340px;
|
|
||||||
background-color: $white;
|
|
||||||
transition: 0.2s;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
@include hover {
|
|
||||||
box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__image {
|
|
||||||
width: 100%;
|
|
||||||
height: 300px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: $default_border_radius;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__content {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__price {
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__name {
|
|
||||||
word-break: break-all;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
-webkit-line-clamp: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__quantity {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__bottom {
|
|
||||||
margin-top: 20px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 5px;
|
|
||||||
max-width: 100%;
|
|
||||||
|
|
||||||
&-button {
|
|
||||||
width: 84%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-wishlist {
|
|
||||||
cursor: pointer;
|
|
||||||
width: 34px;
|
|
||||||
height: 34px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
background-color: $accent;
|
|
||||||
border-radius: $default_border_radius;
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
transition: 0.2s;
|
|
||||||
|
|
||||||
font-size: 22px;
|
|
||||||
color: $white;
|
|
||||||
|
|
||||||
@include hover {
|
|
||||||
background-color: $accentLight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
<template>
|
|
||||||
<form @submit.prevent="handleContactUs()" class="form">
|
|
||||||
<ui-input
|
|
||||||
:type="'text'"
|
|
||||||
:placeholder="t('fields.name')"
|
|
||||||
:rules="[required]"
|
|
||||||
v-model="name"
|
|
||||||
/>
|
|
||||||
<ui-input
|
|
||||||
:type="'email'"
|
|
||||||
:placeholder="t('fields.email')"
|
|
||||||
:rules="[required]"
|
|
||||||
v-model="email"
|
|
||||||
/>
|
|
||||||
<ui-input
|
|
||||||
:type="'text'"
|
|
||||||
:placeholder="t('fields.phoneNumber')"
|
|
||||||
:rules="[required]"
|
|
||||||
v-model="phoneNumber"
|
|
||||||
/>
|
|
||||||
<ui-input
|
|
||||||
:type="'text'"
|
|
||||||
:placeholder="t('fields.subject')"
|
|
||||||
:rules="[required]"
|
|
||||||
v-model="subject"
|
|
||||||
/>
|
|
||||||
<ui-textarea
|
|
||||||
:placeholder="t('fields.message')"
|
|
||||||
:rules="[required]"
|
|
||||||
v-model="message"
|
|
||||||
/>
|
|
||||||
<ui-button
|
|
||||||
class="form__button"
|
|
||||||
:isDisabled="!isFormValid"
|
|
||||||
:isLoading="loading"
|
|
||||||
>
|
|
||||||
{{ t('buttons.send') }}
|
|
||||||
</ui-button>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {useI18n} from "vue-i18n";
|
|
||||||
import {required} from "@/core/rules/textFieldRules.js";
|
|
||||||
import {computed, ref} from "vue";
|
|
||||||
import UiInput from "@/components/ui/ui-input.vue";
|
|
||||||
import UiButton from "@/components/ui/ui-button.vue";
|
|
||||||
import UiTextarea from "@/components/ui/ui-textarea.vue";
|
|
||||||
import {useContactUs} from "@/composables/contact/index.js";
|
|
||||||
|
|
||||||
const {t} = useI18n()
|
|
||||||
|
|
||||||
const name = ref('')
|
|
||||||
const email = ref('')
|
|
||||||
const phoneNumber = ref('')
|
|
||||||
const subject = ref('')
|
|
||||||
const message = ref('')
|
|
||||||
|
|
||||||
const isFormValid = computed(() => {
|
|
||||||
return (
|
|
||||||
required(name.value) === true &&
|
|
||||||
required(email.value) === true &&
|
|
||||||
required(phoneNumber.value) === true &&
|
|
||||||
required(subject.value) === true &&
|
|
||||||
required(message.value) === true
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const { contactUs, loading } = useContactUs();
|
|
||||||
|
|
||||||
async function handleContactUs() {
|
|
||||||
await contactUs(
|
|
||||||
name.value,
|
|
||||||
email.value,
|
|
||||||
phoneNumber.value,
|
|
||||||
subject.value,
|
|
||||||
message.value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
<template>
|
|
||||||
<form @submit.prevent="handleDeposit()" class="form">
|
|
||||||
<div class="form__box">
|
|
||||||
<ui-input
|
|
||||||
:type="'text'"
|
|
||||||
:placeholder="''"
|
|
||||||
v-model="amount"
|
|
||||||
:numberOnly="true"
|
|
||||||
/>
|
|
||||||
<ui-input
|
|
||||||
:type="'text'"
|
|
||||||
:placeholder="''"
|
|
||||||
v-model="amount"
|
|
||||||
:numberOnly="true"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<ui-button
|
|
||||||
class="form__button"
|
|
||||||
:isDisabled="!isFormValid"
|
|
||||||
:isLoading="loading"
|
|
||||||
>
|
|
||||||
{{ t('buttons.topUp') }}
|
|
||||||
</ui-button>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import UiInput from "@/components/ui/ui-input.vue";
|
|
||||||
import {computed, ref} from "vue";
|
|
||||||
import UiButton from "@/components/ui/ui-button.vue";
|
|
||||||
import {useI18n} from "vue-i18n";
|
|
||||||
import {useDeposit} from "@/composables/user/useDeposit.js";
|
|
||||||
import {useCompanyStore} from "@/stores/company.js";
|
|
||||||
|
|
||||||
const {t} = useI18n()
|
|
||||||
const companyStore = useCompanyStore()
|
|
||||||
|
|
||||||
const paymentMin = computed(() => companyStore.companyInfo?.paymentGatewayMinimum)
|
|
||||||
const paymentMax = computed(() => companyStore.companyInfo?.paymentGatewayMaximum)
|
|
||||||
|
|
||||||
const amount = ref('')
|
|
||||||
|
|
||||||
const isFormValid = computed(() => {
|
|
||||||
return (
|
|
||||||
amount.value >= paymentMin.value &&
|
|
||||||
amount.value <= paymentMax.value
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const { deposit, loading } = useDeposit();
|
|
||||||
|
|
||||||
async function handleDeposit() {
|
|
||||||
await deposit(amount.value);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.form {
|
|
||||||
&__box {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
<template>
|
|
||||||
<form @submit.prevent="handleLogin()" class="form">
|
|
||||||
<h2 class="form__title">{{ t('forms.login.title') }}</h2>
|
|
||||||
<ui-input
|
|
||||||
:type="'email'"
|
|
||||||
:placeholder="t('fields.email')"
|
|
||||||
:rules="[isEmail]"
|
|
||||||
v-model="email"
|
|
||||||
/>
|
|
||||||
<ui-input
|
|
||||||
:type="'password'"
|
|
||||||
:placeholder="t('fields.password')"
|
|
||||||
:rules="[required]"
|
|
||||||
v-model="password"
|
|
||||||
/>
|
|
||||||
<ui-checkbox
|
|
||||||
v-model="isStayLogin"
|
|
||||||
>
|
|
||||||
{{ t('checkboxes.remember') }}
|
|
||||||
</ui-checkbox>
|
|
||||||
<ui-link
|
|
||||||
@click="appStore.setActiveState('reset-password')"
|
|
||||||
>
|
|
||||||
{{ t('forms.login.forgot') }}
|
|
||||||
</ui-link>
|
|
||||||
<ui-button
|
|
||||||
class="form__button"
|
|
||||||
:isDisabled="!isFormValid"
|
|
||||||
:isLoading="loading"
|
|
||||||
>
|
|
||||||
{{ t('buttons.login') }}
|
|
||||||
</ui-button>
|
|
||||||
<p class="form__register">
|
|
||||||
{{ t('forms.login.register') }}
|
|
||||||
<ui-link
|
|
||||||
@click="appStore.setActiveState('register')"
|
|
||||||
>
|
|
||||||
{{ t('forms.register.title') }}
|
|
||||||
</ui-link>
|
|
||||||
</p>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {useI18n} from "vue-i18n";
|
|
||||||
import {isEmail, required} from "@/core/rules/textFieldRules.js";
|
|
||||||
import {computed, ref} from "vue";
|
|
||||||
import UiInput from "@/components/ui/ui-input.vue";
|
|
||||||
import UiButton from "@/components/ui/ui-button.vue";
|
|
||||||
import UiCheckbox from "@/components/ui/ui-checkbox.vue";
|
|
||||||
import {useLogin} from "@/composables/auth";
|
|
||||||
import UiLink from "@/components/ui/ui-link.vue";
|
|
||||||
import {useAppStore} from "@/stores/app.js";
|
|
||||||
|
|
||||||
const {t} = useI18n()
|
|
||||||
const appStore = useAppStore()
|
|
||||||
|
|
||||||
const email = ref('')
|
|
||||||
const password = ref('')
|
|
||||||
const isStayLogin = ref(false)
|
|
||||||
|
|
||||||
const isFormValid = computed(() => {
|
|
||||||
return (
|
|
||||||
isEmail(email.value) === true &&
|
|
||||||
required(password.value) === true
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const { login, loading } = useLogin();
|
|
||||||
|
|
||||||
async function handleLogin() {
|
|
||||||
await login(email.value, password.value, isStayLogin.value);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
font-size: 36px;
|
|
||||||
color: $accent;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__register {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
<template>
|
|
||||||
<form @submit.prevent="handleReset()" class="form">
|
|
||||||
<h2 class="form__title">{{ t('forms.newPassword.title') }}</h2>
|
|
||||||
<ui-input
|
|
||||||
:type="'password'"
|
|
||||||
:placeholder="t('fields.newPassword')"
|
|
||||||
:rules="[isPasswordValid]"
|
|
||||||
v-model="password"
|
|
||||||
/>
|
|
||||||
<ui-input
|
|
||||||
:type="'password'"
|
|
||||||
:placeholder="t('fields.confirmNewPassword')"
|
|
||||||
:rules="[compareStrings]"
|
|
||||||
v-model="confirmPassword"
|
|
||||||
/>
|
|
||||||
<ui-button
|
|
||||||
class="form__button"
|
|
||||||
:isDisabled="!isFormValid"
|
|
||||||
:isLoading="loading"
|
|
||||||
>
|
|
||||||
{{ t('buttons.save') }}
|
|
||||||
</ui-button>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {useI18n} from "vue-i18n";
|
|
||||||
import {isPasswordValid,} from "@/core/rules/textFieldRules.js";
|
|
||||||
import {computed, ref} from "vue";
|
|
||||||
import UiInput from "@/components/ui/ui-input.vue";
|
|
||||||
import UiButton from "@/components/ui/ui-button.vue";
|
|
||||||
import {useNewPassword} from "@/composables/auth";
|
|
||||||
|
|
||||||
const {t} = useI18n()
|
|
||||||
|
|
||||||
const password = ref('')
|
|
||||||
const confirmPassword = ref('')
|
|
||||||
|
|
||||||
const compareStrings = (v) => {
|
|
||||||
if (v === password.value) return true
|
|
||||||
return t('errors.compare')
|
|
||||||
}
|
|
||||||
|
|
||||||
const isFormValid = computed(() => {
|
|
||||||
return (
|
|
||||||
isPasswordValid(password.value) === true &&
|
|
||||||
compareStrings(confirmPassword.value) === true
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const { newPassword, loading } = useNewPassword();
|
|
||||||
|
|
||||||
async function handleReset() {
|
|
||||||
await newPassword(
|
|
||||||
password.value,
|
|
||||||
confirmPassword.value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
font-size: 36px;
|
|
||||||
color: $accent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,137 +0,0 @@
|
||||||
<template>
|
|
||||||
<form @submit.prevent="handleRegister()" class="form">
|
|
||||||
<h2 class="form__title">{{ t('forms.register.title') }}</h2>
|
|
||||||
<div class="form__box">
|
|
||||||
<ui-input
|
|
||||||
:type="'text'"
|
|
||||||
:placeholder="t('fields.firstName')"
|
|
||||||
:rules="[required]"
|
|
||||||
v-model="firstName"
|
|
||||||
/>
|
|
||||||
<ui-input
|
|
||||||
:type="'text'"
|
|
||||||
:placeholder="t('fields.lastName')"
|
|
||||||
:rules="[required]"
|
|
||||||
v-model="lastName"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="form__box">
|
|
||||||
<ui-input
|
|
||||||
:type="'text'"
|
|
||||||
:placeholder="t('fields.phoneNumber')"
|
|
||||||
:rules="[required]"
|
|
||||||
v-model="phoneNumber"
|
|
||||||
/>
|
|
||||||
<ui-input
|
|
||||||
:type="'email'"
|
|
||||||
:placeholder="t('fields.email')"
|
|
||||||
:rules="[isEmail]"
|
|
||||||
v-model="email"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<ui-input
|
|
||||||
:type="'password'"
|
|
||||||
:placeholder="t('fields.password')"
|
|
||||||
:rules="[isPasswordValid]"
|
|
||||||
v-model="password"
|
|
||||||
/>
|
|
||||||
<ui-input
|
|
||||||
:type="'password'"
|
|
||||||
:placeholder="t('fields.confirmPassword')"
|
|
||||||
:rules="[compareStrings]"
|
|
||||||
v-model="confirmPassword"
|
|
||||||
/>
|
|
||||||
<ui-button
|
|
||||||
class="form__button"
|
|
||||||
:isDisabled="!isFormValid"
|
|
||||||
:isLoading="loading"
|
|
||||||
>
|
|
||||||
{{ t('buttons.register') }}
|
|
||||||
</ui-button>
|
|
||||||
<p class="form__login">
|
|
||||||
{{ t('forms.register.login') }}
|
|
||||||
<ui-link
|
|
||||||
@click="appStore.setActiveState('login')"
|
|
||||||
>
|
|
||||||
{{ t('forms.login.title') }}
|
|
||||||
</ui-link>
|
|
||||||
</p>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {useI18n} from "vue-i18n";
|
|
||||||
import {isEmail, isPasswordValid, required} from "@/core/rules/textFieldRules.js";
|
|
||||||
import {computed, ref} from "vue";
|
|
||||||
import UiInput from "@/components/ui/ui-input.vue";
|
|
||||||
import UiButton from "@/components/ui/ui-button.vue";
|
|
||||||
import {useRegister} from "@/composables/auth/index.js";
|
|
||||||
import UiLink from "@/components/ui/ui-link.vue";
|
|
||||||
import {useAppStore} from "@/stores/app.js";
|
|
||||||
|
|
||||||
const {t} = useI18n()
|
|
||||||
const appStore = useAppStore()
|
|
||||||
|
|
||||||
const firstName = ref('')
|
|
||||||
const lastName = ref('')
|
|
||||||
const phoneNumber = ref('')
|
|
||||||
const email = ref('')
|
|
||||||
const password = ref('')
|
|
||||||
const confirmPassword = ref('')
|
|
||||||
|
|
||||||
const compareStrings = (v) => {
|
|
||||||
if (v === password.value) return true
|
|
||||||
return t('errors.compare')
|
|
||||||
}
|
|
||||||
|
|
||||||
const isFormValid = computed(() => {
|
|
||||||
return (
|
|
||||||
required(firstName.value) === true &&
|
|
||||||
required(lastName.value) === true &&
|
|
||||||
required(phoneNumber.value) === true &&
|
|
||||||
isEmail(email.value) === true &&
|
|
||||||
isPasswordValid(password.value) === true &&
|
|
||||||
compareStrings(confirmPassword.value) === true
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const { register, loading } = useRegister();
|
|
||||||
|
|
||||||
async function handleRegister() {
|
|
||||||
await register(
|
|
||||||
firstName.value,
|
|
||||||
lastName.value,
|
|
||||||
phoneNumber.value,
|
|
||||||
email.value,
|
|
||||||
password.value,
|
|
||||||
confirmPassword.value
|
|
||||||
);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
font-size: 36px;
|
|
||||||
color: $accent;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__box {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__login {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
<template>
|
|
||||||
<form @submit.prevent="handleReset()" class="form">
|
|
||||||
<h2 class="form__title">{{ t('forms.reset.title') }}</h2>
|
|
||||||
<ui-input
|
|
||||||
:type="'email'"
|
|
||||||
:placeholder="t('fields.email')"
|
|
||||||
:rules="[isEmail]"
|
|
||||||
v-model="email"
|
|
||||||
/>
|
|
||||||
<ui-button
|
|
||||||
class="form__button"
|
|
||||||
:isDisabled="!isFormValid"
|
|
||||||
:isLoading="loading"
|
|
||||||
>
|
|
||||||
{{ t('buttons.sendLink') }}
|
|
||||||
</ui-button>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {useI18n} from "vue-i18n";
|
|
||||||
import {isEmail} from "@/core/rules/textFieldRules.js";
|
|
||||||
import {computed, ref} from "vue";
|
|
||||||
import UiInput from "@/components/ui/ui-input.vue";
|
|
||||||
import UiButton from "@/components/ui/ui-button.vue";
|
|
||||||
import {usePasswordReset} from "@/composables/auth";
|
|
||||||
|
|
||||||
const {t} = useI18n()
|
|
||||||
|
|
||||||
const email = ref('')
|
|
||||||
|
|
||||||
const isFormValid = computed(() => {
|
|
||||||
return (
|
|
||||||
isEmail(email.value) === true
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const { resetPassword, loading } = usePasswordReset();
|
|
||||||
|
|
||||||
async function handleReset() {
|
|
||||||
await resetPassword(email.value);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
font-size: 36px;
|
|
||||||
color: $accent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,104 +0,0 @@
|
||||||
<template>
|
|
||||||
<form class="form" @submit.prevent="handleUpdate()">
|
|
||||||
<ui-input
|
|
||||||
:type="'text'"
|
|
||||||
:placeholder="t('fields.firstName')"
|
|
||||||
:rules="[required]"
|
|
||||||
v-model="firstName"
|
|
||||||
/>
|
|
||||||
<ui-input
|
|
||||||
:type="'text'"
|
|
||||||
:placeholder="t('fields.lastName')"
|
|
||||||
:rules="[required]"
|
|
||||||
v-model="lastName"
|
|
||||||
/>
|
|
||||||
<ui-input
|
|
||||||
:type="'email'"
|
|
||||||
:placeholder="t('fields.email')"
|
|
||||||
:rules="[isEmail]"
|
|
||||||
v-model="email"
|
|
||||||
/>
|
|
||||||
<ui-input
|
|
||||||
:type="'text'"
|
|
||||||
:placeholder="t('fields.phoneNumber')"
|
|
||||||
:rules="[required]"
|
|
||||||
v-model="phoneNumber"
|
|
||||||
/>
|
|
||||||
<ui-input
|
|
||||||
:type="'password'"
|
|
||||||
:placeholder="t('fields.newPassword')"
|
|
||||||
:rules="[isPasswordValid]"
|
|
||||||
v-model="password"
|
|
||||||
/>
|
|
||||||
<ui-input
|
|
||||||
:type="'password'"
|
|
||||||
:placeholder="t('fields.confirmNewPassword')"
|
|
||||||
:rules="[compareStrings]"
|
|
||||||
v-model="confirmPassword"
|
|
||||||
/>
|
|
||||||
<ui-button
|
|
||||||
class="form__button"
|
|
||||||
:isLoading="loading"
|
|
||||||
>
|
|
||||||
{{ t('buttons.save') }}
|
|
||||||
</ui-button>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {useI18n} from "vue-i18n";
|
|
||||||
import {isEmail, isPasswordValid, required} from "@/core/rules/textFieldRules.js";
|
|
||||||
import {computed, ref, watchEffect} from "vue";
|
|
||||||
import UiInput from "@/components/ui/ui-input.vue";
|
|
||||||
import UiButton from "@/components/ui/ui-button.vue";
|
|
||||||
import {useUserStore} from "@/stores/user.js";
|
|
||||||
import {useUserUpdating} from "@/composables/user";
|
|
||||||
|
|
||||||
const {t} = useI18n()
|
|
||||||
const userStore = useUserStore()
|
|
||||||
|
|
||||||
const userFirstName = computed(() => userStore.user?.firstName)
|
|
||||||
const userLastName = computed(() => userStore.user?.lastName)
|
|
||||||
const userEmail = computed(() => userStore.user?.email)
|
|
||||||
const userPhoneNumber = computed(() => userStore.user?.phoneNumber)
|
|
||||||
|
|
||||||
const firstName = ref('')
|
|
||||||
const lastName = ref('')
|
|
||||||
const email = ref('')
|
|
||||||
const phoneNumber = ref('')
|
|
||||||
const password = ref('')
|
|
||||||
const confirmPassword = ref('')
|
|
||||||
|
|
||||||
const compareStrings = (v) => {
|
|
||||||
if (v === password.value) return true
|
|
||||||
return t('errors.compare')
|
|
||||||
}
|
|
||||||
|
|
||||||
const { updateUser, loading } = useUserUpdating();
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
firstName.value = userFirstName.value || ''
|
|
||||||
lastName.value = userLastName.value || ''
|
|
||||||
email.value = userEmail.value || ''
|
|
||||||
phoneNumber.value = userPhoneNumber.value || ''
|
|
||||||
})
|
|
||||||
|
|
||||||
async function handleUpdate() {
|
|
||||||
await updateUser(
|
|
||||||
firstName.value,
|
|
||||||
lastName.value,
|
|
||||||
email.value,
|
|
||||||
phoneNumber.value,
|
|
||||||
password.value,
|
|
||||||
confirmPassword.value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="brands">
|
|
||||||
<div class="container">
|
|
||||||
<div class="brands__wrapper">
|
|
||||||
<vue-marquee-slider
|
|
||||||
id="marquee-slider"
|
|
||||||
:speed="40000"
|
|
||||||
:paused="isMarqueePaused"
|
|
||||||
@mouseenter="isMarqueePaused = true"
|
|
||||||
@mouseleave="isMarqueePaused = false"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="brands__item"
|
|
||||||
v-for="brand in brands"
|
|
||||||
:key="brand.node.uuid"
|
|
||||||
>
|
|
||||||
<p>{{ brand.node.name }}</p>
|
|
||||||
</div>
|
|
||||||
</vue-marquee-slider>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { VueMarqueeSlider } from 'vue3-marquee-slider';
|
|
||||||
import '../../../node_modules/vue3-marquee-slider/dist/style.css'
|
|
||||||
import {onMounted, ref} from "vue";
|
|
||||||
import {useBrands} from "@/composables/brands/index.js";
|
|
||||||
|
|
||||||
const isMarqueePaused = ref(false)
|
|
||||||
|
|
||||||
const { brands, loading, getBrands } = useBrands();
|
|
||||||
|
|
||||||
onMounted( async () => {
|
|
||||||
await getBrands()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.brands {
|
|
||||||
&__item {
|
|
||||||
margin-right: 20px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,106 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="tag">
|
|
||||||
<h2 class="tag__title">{{ tag.name }}</h2>
|
|
||||||
<div class="tag__block">
|
|
||||||
<div class="tag__block-inner">
|
|
||||||
<swiper
|
|
||||||
class="swiper"
|
|
||||||
:effect="'cards'"
|
|
||||||
:grabCursor="true"
|
|
||||||
:modules="[EffectCards, Mousewheel]"
|
|
||||||
:cardsEffect="{
|
|
||||||
slideShadows: false
|
|
||||||
}"
|
|
||||||
:mousewheel="true"
|
|
||||||
>
|
|
||||||
<swiper-slide
|
|
||||||
class="swiper__slide"
|
|
||||||
v-for="product in tag.productSet.edges"
|
|
||||||
:key="product.node.uuid"
|
|
||||||
>
|
|
||||||
<product-card
|
|
||||||
:product="product.node"
|
|
||||||
/>
|
|
||||||
</swiper-slide>
|
|
||||||
</swiper>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { Swiper, SwiperSlide } from 'swiper/vue';
|
|
||||||
import { EffectCards, Mousewheel } from 'swiper/modules';
|
|
||||||
import 'swiper/css';
|
|
||||||
import 'swiper/css/scrollbar';
|
|
||||||
import ProductCard from "@/components/cards/product-card.vue";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
tag: Object
|
|
||||||
})
|
|
||||||
|
|
||||||
const swiperOptions = {
|
|
||||||
speed: 500,
|
|
||||||
spaceBetween: 30,
|
|
||||||
slidesPerView: 3,
|
|
||||||
scrollbar: {
|
|
||||||
hide: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.tag {
|
|
||||||
width: 500px;
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
text-align: center;
|
|
||||||
color: $accent;
|
|
||||||
font-size: 56px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__block {
|
|
||||||
border-radius: $default_border_radius;
|
|
||||||
background-color: $accentLight;
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
&-inner {
|
|
||||||
border-radius: $default_border_radius;
|
|
||||||
border: 5px solid $white;
|
|
||||||
padding-inline:20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper {
|
|
||||||
width: 100%;
|
|
||||||
padding-block: 30px;
|
|
||||||
|
|
||||||
&__slide {
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
|
|
||||||
& .card {
|
|
||||||
&:after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
z-index: 2;
|
|
||||||
inset: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.2);
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
transition: 0.2s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.swiper-slide-active) {
|
|
||||||
& .card:after {
|
|
||||||
background-color: transparent;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,104 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="collection">
|
|
||||||
<div class="container">
|
|
||||||
<div class="collection__wrapper">
|
|
||||||
<h2 class="collection__title">{{ t('home.collection.title') }}</h2>
|
|
||||||
<div class="collection__inner">
|
|
||||||
<home-collection-inner
|
|
||||||
v-for="tag in tags"
|
|
||||||
:key="tag.uuid"
|
|
||||||
:tag="tag.node"
|
|
||||||
/>
|
|
||||||
<home-collection-inner
|
|
||||||
v-if="newProducts.length > 0"
|
|
||||||
:tag="newProductsTag"
|
|
||||||
/>
|
|
||||||
<home-collection-inner
|
|
||||||
v-if="priceProducts.length > 0"
|
|
||||||
:tag="priceProductsTag"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {useI18n} from "vue-i18n";
|
|
||||||
import HomeCollectionInner from "@/components/home/home-collection-inner.vue";
|
|
||||||
import {useProducts, useProductTags} from "@/composables/products";
|
|
||||||
import {computed, onMounted} from "vue";
|
|
||||||
|
|
||||||
const {t} = useI18n()
|
|
||||||
|
|
||||||
const { tags, loading: tagsLoading, getProductTags } = useProductTags();
|
|
||||||
const {
|
|
||||||
products: newProducts,
|
|
||||||
loading: newLoading,
|
|
||||||
getProducts: getNewProducts
|
|
||||||
} = useProducts();
|
|
||||||
|
|
||||||
const {
|
|
||||||
products: priceProducts,
|
|
||||||
loading: priceLoading,
|
|
||||||
getProducts: getPriceProducts
|
|
||||||
} = useProducts();
|
|
||||||
|
|
||||||
const newProductsTag = computed(() => {
|
|
||||||
return {
|
|
||||||
name: t('home.collection.newTag'),
|
|
||||||
uuid: 'new-products',
|
|
||||||
productSet: {
|
|
||||||
edges: newProducts.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const priceProductsTag = computed(() => {
|
|
||||||
return {
|
|
||||||
name: t('home.collection.cheapTag'),
|
|
||||||
uuid: 'price-products',
|
|
||||||
productSet: {
|
|
||||||
edges: priceProducts.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted( async () => {
|
|
||||||
await getProductTags()
|
|
||||||
await Promise.all([
|
|
||||||
getProductTags(),
|
|
||||||
getNewProducts({
|
|
||||||
orderBy: '-modified'
|
|
||||||
}),
|
|
||||||
getPriceProducts({
|
|
||||||
orderBy: '-price'
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.collection {
|
|
||||||
&__wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
font-size: 72px;
|
|
||||||
font-weight: 900;
|
|
||||||
color: #dd6878;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__inner {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 100px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="hero">
|
<div class="hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="hero__wrapper">
|
<div class="hero__wrapper">
|
||||||
<img src="@images/evibes-big.png" alt="logo">
|
<img src="/images/evibes-big.png" alt="logo">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.hero {
|
.hero {
|
||||||
background-image: url(@images/homeBg.png);
|
background-image: url(/images/homeBg.png);
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
-webkit-background-size: cover;
|
-webkit-background-size: cover;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
<template>
|
|
||||||
<button
|
|
||||||
class="button"
|
|
||||||
:disabled="isDisabled"
|
|
||||||
:class="[{active: isLoading}]"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
<ui-loader class="button__loader" v-if="isLoading" />
|
|
||||||
<slot v-else />
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import UiLoader from "@/components/ui/ui-loader.vue";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
isDisabled: Boolean,
|
|
||||||
isLoading: Boolean
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.button {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
cursor: pointer;
|
|
||||||
flex-shrink: 0;
|
|
||||||
transition: 0.2s;
|
|
||||||
border: 1px solid $accent;
|
|
||||||
background-color: $accent;
|
|
||||||
border-radius: $default_border_radius;
|
|
||||||
padding-block: 7px;
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
color: $white;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 700;
|
|
||||||
|
|
||||||
@include hover {
|
|
||||||
background-color: $accentLight;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background-color: $accentLight;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
cursor: not-allowed;
|
|
||||||
background-color: $accentDisabled;
|
|
||||||
color: $white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled:hover, &.active {
|
|
||||||
background-color: $accentDisabled;
|
|
||||||
color: $white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__loader {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="checkbox">
|
|
||||||
<input
|
|
||||||
:id="id ? `checkbox + id` : 'checkbox'"
|
|
||||||
class="checkbox__input"
|
|
||||||
type="checkbox"
|
|
||||||
:value="modelValue"
|
|
||||||
@input="onInput"
|
|
||||||
:checked="modelValue"
|
|
||||||
>
|
|
||||||
<span class="checkbox__block" @click="toggleCheckbox"></span>
|
|
||||||
<label :for="id ? `checkbox + id` : 'checkbox'" class="checkbox__label">
|
|
||||||
<slot />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
const $emit = defineEmits()
|
|
||||||
const props = defineProps({
|
|
||||||
id: [Number, String],
|
|
||||||
modelValue: Boolean
|
|
||||||
})
|
|
||||||
|
|
||||||
const onInput = (event) => {
|
|
||||||
$emit('update:modelValue', event.target.checked);
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleCheckbox = () => {
|
|
||||||
$emit('update:modelValue', !props.modelValue);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.checkbox {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
|
|
||||||
&__input {
|
|
||||||
display: none;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__block {
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-block;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border: 2px solid $black;
|
|
||||||
border-radius: $default_border_radius;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
background-color: $accent;
|
|
||||||
border-radius: 2px;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__label {
|
|
||||||
cursor: pointer;
|
|
||||||
color: #2B2B2B;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox__input:checked + .checkbox__block::after {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="counter">
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.counter {
|
|
||||||
position: absolute !important;
|
|
||||||
top: -10px;
|
|
||||||
right: -15px;
|
|
||||||
background-color: $accent;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 20px;
|
|
||||||
aspect-ratio: 1;
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: $white;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,148 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="block">
|
|
||||||
<div class="block__inner">
|
|
||||||
<input
|
|
||||||
:placeholder="placeholder"
|
|
||||||
:type="isPasswordVisible"
|
|
||||||
:value="modelValue"
|
|
||||||
@input="onInput"
|
|
||||||
@keydown="numberOnly ? onlyNumbersKeydown($event) : null"
|
|
||||||
class="block__input"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
@click.prevent="setPasswordVisible"
|
|
||||||
class="block__eyes"
|
|
||||||
v-if="type === 'password' && modelValue"
|
|
||||||
>
|
|
||||||
<i v-if="isPasswordVisible === 'password'" class="pi pi-eye-slash"></i>
|
|
||||||
<i v-else class="pi pi-eye"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<p v-if="!validate" class="block__error">{{ errorMessage }}</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {ref} from "vue";
|
|
||||||
|
|
||||||
const $emit = defineEmits()
|
|
||||||
const props = defineProps({
|
|
||||||
type: String,
|
|
||||||
placeholder: String,
|
|
||||||
isError: Boolean,
|
|
||||||
error: String,
|
|
||||||
modelValue: [String, Number],
|
|
||||||
rules: Array,
|
|
||||||
numberOnly: Boolean
|
|
||||||
})
|
|
||||||
|
|
||||||
const isPasswordVisible = ref(props.type)
|
|
||||||
const setPasswordVisible = () => {
|
|
||||||
if (isPasswordVisible.value === 'password') {
|
|
||||||
isPasswordVisible.value = 'text'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isPasswordVisible.value = 'password'
|
|
||||||
}
|
|
||||||
|
|
||||||
const onlyNumbersKeydown = (event) => {
|
|
||||||
if (!/^\d$/.test(event.key) &&
|
|
||||||
!['ArrowLeft', 'ArrowRight', 'Backspace', 'Delete', 'Tab', 'Home', 'End'].includes(event.key)) {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const validate = ref(true)
|
|
||||||
const errorMessage = ref('')
|
|
||||||
const onInput = (e) => {
|
|
||||||
let value = e.target.value;
|
|
||||||
|
|
||||||
if (props.numberOnly) {
|
|
||||||
const newValue = value.replace(/\D/g, '');
|
|
||||||
if (newValue !== value) {
|
|
||||||
e.target.value = newValue;
|
|
||||||
value = newValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = true
|
|
||||||
|
|
||||||
props.rules?.forEach((rule) => {
|
|
||||||
result = rule((e.target).value)
|
|
||||||
|
|
||||||
if (result !== true) {
|
|
||||||
errorMessage.value = String(result)
|
|
||||||
result = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
validate.value = result
|
|
||||||
|
|
||||||
return $emit('update:modelValue', (e.target).value)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.block {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 10px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&__inner {
|
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 6px 12px;
|
|
||||||
border: 1px solid #e0e0e0;
|
|
||||||
//border: 1px solid #b2b2b2;
|
|
||||||
border-radius: $default_border_radius;
|
|
||||||
background-color: $white;
|
|
||||||
|
|
||||||
color: #1f1f1f;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 20px;
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: #2B2B2B;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__eyes {
|
|
||||||
cursor: pointer;
|
|
||||||
position: absolute;
|
|
||||||
right: 20px;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
background-color: transparent;
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
font-size: 18px;
|
|
||||||
color: #838383;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__error {
|
|
||||||
color: $error;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
animation: fadeInUp 0.3s ease;
|
|
||||||
|
|
||||||
@keyframes fadeInUp {
|
|
||||||
0% {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -29,16 +29,18 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {computed, ref} from "vue";
|
import {computed, onMounted, ref} from "vue";
|
||||||
import {onClickOutside} from "@vueuse/core";
|
import {onClickOutside} from "@vueuse/core";
|
||||||
import {useLanguageStore} from "@/stores/languages.js";
|
import {useLanguages, useLanguageSwitch} from "@/composables/languages/index.js";
|
||||||
import {useLanguageSwitch} from "@/composables/languages/index.js";
|
|
||||||
import LanguageSwitcherSkeleton from "@/components/skeletons/ui/language-switcher-skeleton.vue";
|
import LanguageSwitcherSkeleton from "@/components/skeletons/ui/language-switcher-skeleton.vue";
|
||||||
|
import {COOKIES_LOCALE_KEY, SUPPORTED_LOCALES} from "@/config/index.js";
|
||||||
|
import {useCookies} from "@vueuse/integrations/useCookies";
|
||||||
|
import {useI18n} from "vue-i18n";
|
||||||
|
|
||||||
const languageStore = useLanguageStore()
|
const cookie = useCookies(['/', null, null])
|
||||||
|
const { t } = useI18n();
|
||||||
const locales = computed(() => languageStore.languages)
|
const locales = ref([])
|
||||||
const currentLocale = computed(() => languageStore.currentLocale)
|
const currentLocale = computed(() => locales.value.find((locale) => locale.code === cookie.get(COOKIES_LOCALE_KEY)))
|
||||||
|
|
||||||
const isSwitcherVisible = ref(false)
|
const isSwitcherVisible = ref(false)
|
||||||
const setSwitcherVisible = (state) => {
|
const setSwitcherVisible = (state) => {
|
||||||
|
|
@ -48,7 +50,43 @@ const setSwitcherVisible = (state) => {
|
||||||
const switcherRef = ref(null)
|
const switcherRef = ref(null)
|
||||||
onClickOutside(switcherRef, () => isSwitcherVisible.value = false)
|
onClickOutside(switcherRef, () => isSwitcherVisible.value = false)
|
||||||
|
|
||||||
|
const { languages, getLanguages } = useLanguages()
|
||||||
const { switchLanguage } = useLanguageSwitch()
|
const { switchLanguage } = useLanguageSwitch()
|
||||||
|
|
||||||
|
// const switchLanguage = async (localeCode) => {
|
||||||
|
// setSwitcherVisible(false);
|
||||||
|
//
|
||||||
|
// if (!SUPPORTED_LOCALES.some(locale => locale.code === localeCode)) {
|
||||||
|
// console.error(`Locale ${localeCode} is not supported`);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// try {
|
||||||
|
// cookie.set(COOKIES_LOCALE_KEY, localeCode);
|
||||||
|
//
|
||||||
|
// locale.value = localeCode;
|
||||||
|
//
|
||||||
|
// await switchUserLanguage(localeCode);
|
||||||
|
//
|
||||||
|
// const currentPath = window.location.pathname;
|
||||||
|
// const pathParts = currentPath.split('/');
|
||||||
|
//
|
||||||
|
// if (pathParts.length > 1 && SUPPORTED_LOCALES.some(locale => locale.code === pathParts[1])) {
|
||||||
|
// pathParts[1] = localeCode;
|
||||||
|
// } else {
|
||||||
|
// pathParts.splice(1, 0, localeCode);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// window.location.href = pathParts.join('/');
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error('Error switching language:', error);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
onMounted( async () => {
|
||||||
|
await getLanguages()
|
||||||
|
locales.value = languages.value
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
<template>
|
|
||||||
<div @click="redirect" class="link">
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {useRouter} from "vue-router";
|
|
||||||
import {DEFAULT_LOCALE, LOCALE_STORAGE_LOCALE_KEY} from "@/config/index.js";
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
routeName: String
|
|
||||||
})
|
|
||||||
|
|
||||||
const redirect = () => {
|
|
||||||
if (props.routeName) {
|
|
||||||
router.push({
|
|
||||||
name: props.routeName,
|
|
||||||
params: {
|
|
||||||
locale: localStorage.getItem(LOCALE_STORAGE_LOCALE_KEY) || DEFAULT_LOCALE
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.link {
|
|
||||||
width: fit-content;
|
|
||||||
transition: 0.2s;
|
|
||||||
cursor: pointer;
|
|
||||||
color: $accent;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
@include hover {
|
|
||||||
color: #5539ce;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="loader">
|
|
||||||
<li class="loader__dots" id="dot-1"></li>
|
|
||||||
<li class="loader__dots" id="dot-2"></li>
|
|
||||||
<li class="loader__dots" id="dot-3"></li>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.loader {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.6em;
|
|
||||||
list-style: none;
|
|
||||||
|
|
||||||
&__dots {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: $white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#dot-1 {
|
|
||||||
animation: loader-1 0.6s infinite ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes loader-1 {
|
|
||||||
50% {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-0.3em);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#dot-2 {
|
|
||||||
animation: loader-2 0.6s 0.3s infinite ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes loader-2 {
|
|
||||||
50% {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-0.3em);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#dot-3 {
|
|
||||||
animation: loader-3 0.6s 0.6s infinite ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes loader-3 {
|
|
||||||
50% {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-0.3em);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="block">
|
|
||||||
<textarea
|
|
||||||
:placeholder="placeholder"
|
|
||||||
:value="modelValue"
|
|
||||||
@input="onInput"
|
|
||||||
class="block__textarea"
|
|
||||||
/>
|
|
||||||
<p v-if="!validate" class="block__error">{{ errorMessage }}</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {ref} from "vue";
|
|
||||||
|
|
||||||
const $emit = defineEmits()
|
|
||||||
const props = defineProps({
|
|
||||||
placeholder: String,
|
|
||||||
isError: Boolean,
|
|
||||||
error: String,
|
|
||||||
modelValue: [String, Number],
|
|
||||||
rules: Array
|
|
||||||
})
|
|
||||||
|
|
||||||
const validate = ref(true)
|
|
||||||
const errorMessage = ref('')
|
|
||||||
const onInput = (e) => {
|
|
||||||
let result = true
|
|
||||||
|
|
||||||
props.rules?.forEach((rule) => {
|
|
||||||
result = rule((e.target).value)
|
|
||||||
|
|
||||||
if (result !== true) {
|
|
||||||
errorMessage.value = String(result)
|
|
||||||
result = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
validate.value = result
|
|
||||||
|
|
||||||
return $emit('update:modelValue', (e.target).value)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.block {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 10px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&__textarea {
|
|
||||||
width: 100%;
|
|
||||||
height: 150px;
|
|
||||||
resize: none;
|
|
||||||
padding: 6px 12px;
|
|
||||||
border: 1px solid $black;
|
|
||||||
background-color: $white;
|
|
||||||
|
|
||||||
color: #1f1f1f;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 20px;
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: #2B2B2B;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__error {
|
|
||||||
color: $error;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
animation: fadeInUp 0.3s ease;
|
|
||||||
|
|
||||||
@keyframes fadeInUp {
|
|
||||||
0% {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
export * from './useLogin.js';
|
|
||||||
export * from './useLogout.js';
|
|
||||||
export * from './useNewPassword.js';
|
|
||||||
export * from './usePasswordReset.js';
|
|
||||||
export * from './useRefresh.js';
|
|
||||||
export * from './useRegister.js';
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
import {useMutation} from "@vue/apollo-composable";
|
|
||||||
import {LOGIN} from "@/graphql/mutations/auth.js";
|
|
||||||
import {ref} from "vue";
|
|
||||||
import {ElNotification} from "element-plus";
|
|
||||||
import {useI18n} from "vue-i18n";
|
|
||||||
import {useUserStore} from "@/stores/user.js";
|
|
||||||
import translations from "@/core/helpers/translations.js";
|
|
||||||
import {
|
|
||||||
DEFAULT_LOCALE, LOCALE_STORAGE_ACCESS_TOKEN_KEY,
|
|
||||||
LOCALE_STORAGE_LOCALE_KEY,
|
|
||||||
LOCALE_STORAGE_REFRESH_TOKEN_KEY,
|
|
||||||
} from "@/config/index.js";
|
|
||||||
import {usePendingOrder} from "@/composables/orders";
|
|
||||||
import {useWishlist} from "@/composables/wishlist";
|
|
||||||
|
|
||||||
export function useLogin() {
|
|
||||||
const userStore = useUserStore()
|
|
||||||
const {t} = useI18n();
|
|
||||||
|
|
||||||
const { mutate: loginMutation } = useMutation(LOGIN);
|
|
||||||
|
|
||||||
const { getPendingOrder } = usePendingOrder();
|
|
||||||
const { getWishlist } = useWishlist();
|
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
async function login(
|
|
||||||
email,
|
|
||||||
password,
|
|
||||||
isStayLogin
|
|
||||||
) {
|
|
||||||
loading.value = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await loginMutation({
|
|
||||||
email,
|
|
||||||
password
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isStayLogin) {
|
|
||||||
localStorage.setItem(LOCALE_STORAGE_REFRESH_TOKEN_KEY, response.data.obtainJwtToken.refreshToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.data?.obtainJwtToken) {
|
|
||||||
userStore.setUser({
|
|
||||||
user: response.data.obtainJwtToken.user
|
|
||||||
});
|
|
||||||
localStorage.setItem(LOCALE_STORAGE_ACCESS_TOKEN_KEY, response.data.obtainJwtToken.accessToken)
|
|
||||||
|
|
||||||
ElNotification({
|
|
||||||
message: t('popup.success.login'),
|
|
||||||
type: 'success'
|
|
||||||
})
|
|
||||||
|
|
||||||
let locale = localStorage.getItem(LOCALE_STORAGE_LOCALE_KEY) || DEFAULT_LOCALE;
|
|
||||||
|
|
||||||
if (response.data.obtainJwtToken.user.language !== translations.currentLocale) {
|
|
||||||
translations.switchLanguage(response.data.obtainJwtToken.user.language);
|
|
||||||
locale = response.data.obtainJwtToken.user.language;
|
|
||||||
}
|
|
||||||
|
|
||||||
await getPendingOrder(response.data.obtainJwtToken.user.email);
|
|
||||||
await getWishlist();
|
|
||||||
|
|
||||||
window.location.href = `/${locale}/`;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("useLogin error:", error);
|
|
||||||
|
|
||||||
const errorMessage = error.graphQLErrors?.[0]?.message ||
|
|
||||||
error.message ||
|
|
||||||
t('popup.errors.defaultError');
|
|
||||||
|
|
||||||
ElNotification({
|
|
||||||
title: t('popup.errors.main'),
|
|
||||||
message: errorMessage,
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
login,
|
|
||||||
loading
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
import {useUserStore} from "@/stores/user.js";
|
|
||||||
import {
|
|
||||||
DEFAULT_LOCALE, LOCALE_STORAGE_ACCESS_TOKEN_KEY,
|
|
||||||
LOCALE_STORAGE_LOCALE_KEY,
|
|
||||||
LOCALE_STORAGE_REFRESH_TOKEN_KEY
|
|
||||||
} from "@/config/index.js";
|
|
||||||
|
|
||||||
export function useLogout() {
|
|
||||||
const userStore = useUserStore()
|
|
||||||
|
|
||||||
async function logout() {
|
|
||||||
userStore.setUser({
|
|
||||||
user: null,
|
|
||||||
accessToken: null
|
|
||||||
})
|
|
||||||
|
|
||||||
localStorage.removeItem(LOCALE_STORAGE_REFRESH_TOKEN_KEY)
|
|
||||||
localStorage.removeItem(LOCALE_STORAGE_ACCESS_TOKEN_KEY)
|
|
||||||
|
|
||||||
const locale = localStorage.getItem(LOCALE_STORAGE_LOCALE_KEY) || DEFAULT_LOCALE;
|
|
||||||
|
|
||||||
window.location.href = `/${locale}/`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
logout
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
import {useMutation} from "@vue/apollo-composable";
|
|
||||||
import {NEW_PASSWORD} from "@/graphql/mutations/auth.js";
|
|
||||||
import {computed, ref} from "vue";
|
|
||||||
import {ElNotification} from "element-plus";
|
|
||||||
import {useI18n} from "vue-i18n";
|
|
||||||
import {DEFAULT_LOCALE, LOCALE_STORAGE_LOCALE_KEY} from "@/config/index.js";
|
|
||||||
|
|
||||||
export function useNewPassword() {
|
|
||||||
const {t} = useI18n();
|
|
||||||
|
|
||||||
const { mutate: newPasswordMutation } = useMutation(NEW_PASSWORD);
|
|
||||||
|
|
||||||
const token = computed(() =>
|
|
||||||
route.query.token ? (route.query.token) : undefined,
|
|
||||||
);
|
|
||||||
const uid = computed(() =>
|
|
||||||
route.query.uid ? (route.query.uid) : undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
async function newPassword(
|
|
||||||
password,
|
|
||||||
confirmPassword
|
|
||||||
) {
|
|
||||||
loading.value = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await newPasswordMutation({
|
|
||||||
password,
|
|
||||||
confirmPassword,
|
|
||||||
token: token.value,
|
|
||||||
uid: uid.value
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.data?.confirmResetPassword.success) {
|
|
||||||
ElNotification({
|
|
||||||
message: t('popup.success.newPassword'),
|
|
||||||
type: 'success'
|
|
||||||
})
|
|
||||||
|
|
||||||
await router.push({
|
|
||||||
name: 'home',
|
|
||||||
params: {
|
|
||||||
locale: localStorage.getItem(LOCALE_STORAGE_LOCALE_KEY) || DEFAULT_LOCALE
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("useNewPassword error:", error);
|
|
||||||
|
|
||||||
const errorMessage = error.graphQLErrors?.[0]?.message ||
|
|
||||||
error.message ||
|
|
||||||
t('popup.errors.defaultError');
|
|
||||||
|
|
||||||
ElNotification({
|
|
||||||
title: t('popup.errors.main'),
|
|
||||||
message: errorMessage,
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
newPassword,
|
|
||||||
loading
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
import {useMutation} from "@vue/apollo-composable";
|
|
||||||
import {RESET_PASSWORD} from "@/graphql/mutations/auth.js";
|
|
||||||
import {ref} from "vue";
|
|
||||||
import {ElNotification} from "element-plus";
|
|
||||||
import {useI18n} from "vue-i18n";
|
|
||||||
|
|
||||||
export function usePasswordReset() {
|
|
||||||
const {t} = useI18n();
|
|
||||||
|
|
||||||
const { mutate: resetPasswordMutation } = useMutation(RESET_PASSWORD);
|
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
async function resetPassword(
|
|
||||||
email
|
|
||||||
) {
|
|
||||||
loading.value = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await resetPasswordMutation({
|
|
||||||
email
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.data?.resetPassword.success) {
|
|
||||||
ElNotification({
|
|
||||||
message: t('popup.success.reset'),
|
|
||||||
type: 'success'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("usePasswordReset error:", error);
|
|
||||||
|
|
||||||
const errorMessage = error.graphQLErrors?.[0]?.message ||
|
|
||||||
error.message ||
|
|
||||||
t('popup.errors.defaultError');
|
|
||||||
|
|
||||||
ElNotification({
|
|
||||||
title: t('popup.errors.main'),
|
|
||||||
message: errorMessage,
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
resetPassword,
|
|
||||||
loading
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
import {useMutation} from "@vue/apollo-composable";
|
|
||||||
import {REFRESH} from "@/graphql/mutations/auth.js";
|
|
||||||
import {computed, ref} from "vue";
|
|
||||||
import {ElNotification} from "element-plus";
|
|
||||||
import {useI18n} from "vue-i18n";
|
|
||||||
import {useUserStore} from "@/stores/user.js";
|
|
||||||
import {LOCALE_STORAGE_REFRESH_TOKEN_KEY} from "@/config/index.js";
|
|
||||||
import translations from "@/core/helpers/translations.js";
|
|
||||||
import {usePendingOrder} from "@/composables/orders";
|
|
||||||
import {useWishlist} from "@/composables/wishlist";
|
|
||||||
|
|
||||||
export function useRefresh() {
|
|
||||||
const userStore = useUserStore()
|
|
||||||
const {t} = useI18n();
|
|
||||||
|
|
||||||
const { mutate: refreshMutation } = useMutation(REFRESH);
|
|
||||||
|
|
||||||
const { getPendingOrder } = usePendingOrder();
|
|
||||||
const { getWishlist } = useWishlist();
|
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
async function refresh() {
|
|
||||||
loading.value = true;
|
|
||||||
|
|
||||||
const refreshToken = computed(() => localStorage.getItem(LOCALE_STORAGE_REFRESH_TOKEN_KEY))
|
|
||||||
|
|
||||||
if (!refreshToken.value) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await refreshMutation({
|
|
||||||
refreshToken: refreshToken.value
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.data?.refreshJwtToken) {
|
|
||||||
userStore.setUser({
|
|
||||||
user: response.data.refreshJwtToken.user,
|
|
||||||
accessToken: response.data.refreshJwtToken.accessToken
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.data.refreshJwtToken.user.language !== translations.currentLocale) {
|
|
||||||
translations.switchLanguage(response.data.refreshJwtToken.user.language)
|
|
||||||
}
|
|
||||||
|
|
||||||
localStorage.setItem(LOCALE_STORAGE_REFRESH_TOKEN_KEY, response.data.refreshJwtToken.refreshToken)
|
|
||||||
|
|
||||||
await getPendingOrder(response.data.refreshJwtToken.user.email);
|
|
||||||
await getWishlist();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("useRefresh error:", error);
|
|
||||||
|
|
||||||
const errorMessage = error.graphQLErrors?.[0]?.message ||
|
|
||||||
error.message ||
|
|
||||||
t('popup.errors.defaultError');
|
|
||||||
|
|
||||||
ElNotification({
|
|
||||||
title: t('popup.errors.main'),
|
|
||||||
message: errorMessage,
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
refresh,
|
|
||||||
loading
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
import {useMutation} from "@vue/apollo-composable";
|
|
||||||
import {REGISTER} from "@/graphql/mutations/auth.js";
|
|
||||||
import {h, ref} from "vue";
|
|
||||||
import {ElNotification} from "element-plus";
|
|
||||||
import {useI18n} from "vue-i18n";
|
|
||||||
import {useMailClient} from "@/composables/utils";
|
|
||||||
|
|
||||||
export function useRegister() {
|
|
||||||
const {t} = useI18n();
|
|
||||||
|
|
||||||
const { mutate: registerMutation } = useMutation(REGISTER);
|
|
||||||
|
|
||||||
const { mailClientUrl, detectMailClient, openMailClient } = useMailClient();
|
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
async function register(
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
phoneNumber,
|
|
||||||
email,
|
|
||||||
password,
|
|
||||||
confirmPassword
|
|
||||||
) {
|
|
||||||
loading.value = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await registerMutation({
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
phoneNumber,
|
|
||||||
email,
|
|
||||||
password,
|
|
||||||
confirmPassword
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.data?.createUser?.success) {
|
|
||||||
detectMailClient(email);
|
|
||||||
|
|
||||||
ElNotification({
|
|
||||||
message: h('div', [
|
|
||||||
h('p', t('popup.success.register')),
|
|
||||||
mailClientUrl.value ? h(
|
|
||||||
'button',
|
|
||||||
{
|
|
||||||
style: {
|
|
||||||
marginTop: '10px',
|
|
||||||
padding: '6px 12px',
|
|
||||||
backgroundColor: '#000000',
|
|
||||||
color: '#fff',
|
|
||||||
border: 'none',
|
|
||||||
cursor: 'pointer',
|
|
||||||
},
|
|
||||||
onClick: () => {
|
|
||||||
openMailClient()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
t('buttons.goEmail')
|
|
||||||
) : ''
|
|
||||||
]),
|
|
||||||
type: 'success'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("useRegister error:", error);
|
|
||||||
|
|
||||||
const errorMessage = error.graphQLErrors?.[0]?.message ||
|
|
||||||
error.message ||
|
|
||||||
t('popup.errors.defaultError');
|
|
||||||
|
|
||||||
ElNotification({
|
|
||||||
title: t('popup.errors.main'),
|
|
||||||
message: errorMessage,
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
register,
|
|
||||||
loading
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export * from './usePosts.js'
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import { useLazyQuery } from "@vue/apollo-composable";
|
|
||||||
import {GET_POST_BY_SLUG} from "@/graphql/queries/blog.js";
|
|
||||||
import {computed} from "vue";
|
|
||||||
|
|
||||||
export function usePostbySlug() {
|
|
||||||
const { result, loading, error, load } = useLazyQuery(GET_POST_BY_SLUG);
|
|
||||||
|
|
||||||
const post = computed(() => result.value?.posts.edges[0].node ?? []);
|
|
||||||
|
|
||||||
if (error.value) {
|
|
||||||
console.error("usePostbySlug error:", error.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const getPost = (slug) => {
|
|
||||||
return load(null, { slug });
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
post,
|
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
getPost
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import { useLazyQuery } from "@vue/apollo-composable";
|
|
||||||
import { GET_POSTS } from "@/graphql/queries/blog.js";
|
|
||||||
import {computed} from "vue";
|
|
||||||
|
|
||||||
export function usePosts() {
|
|
||||||
const { result, loading, error, load } = useLazyQuery(GET_POSTS);
|
|
||||||
|
|
||||||
const posts = computed(() => result.value?.posts.edges ?? []);
|
|
||||||
|
|
||||||
if (error.value) {
|
|
||||||
console.error("usePosts error:", error.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
posts,
|
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
getPosts: load
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
export * from './useBrands.js'
|
|
||||||
export * from './useBrandByUuid.js'
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import { useLazyQuery } from "@vue/apollo-composable";
|
|
||||||
import {computed} from "vue";
|
|
||||||
import {GET_BRAND_BY_UUID} from "@/graphql/queries/brands.js";
|
|
||||||
|
|
||||||
export function useBrandByUuid() {
|
|
||||||
const { result, loading, error, load } = useLazyQuery(GET_BRAND_BY_UUID);
|
|
||||||
|
|
||||||
const brand = computed(() => result.value?.brands.edges[0].node ?? []);
|
|
||||||
|
|
||||||
if (error.value) {
|
|
||||||
console.error("usePostbySlug error:", error.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const getBrand = (uuid) => {
|
|
||||||
return load(null, { uuid });
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
brand,
|
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
getBrand
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import { useLazyQuery } from "@vue/apollo-composable";
|
|
||||||
import {computed} from "vue";
|
|
||||||
import {GET_BRANDS} from "@/graphql/queries/brands.js";
|
|
||||||
|
|
||||||
export function useBrands() {
|
|
||||||
const { result, loading, error, load } = useLazyQuery(GET_BRANDS);
|
|
||||||
|
|
||||||
const brands = computed(() => result.value?.brands.edges ?? []);
|
|
||||||
|
|
||||||
if (error.value) {
|
|
||||||
console.error("useBrands error:", error.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
brands,
|
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
getBrands: load
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
export * from './useCategories.js'
|
|
||||||
export * from './useCategorybySlug.js'
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import { useLazyQuery } from "@vue/apollo-composable";
|
|
||||||
import {computed} from "vue";
|
|
||||||
import {GET_CATEGORIES} from "@/graphql/queries/categories.js";
|
|
||||||
|
|
||||||
export function useCategories() {
|
|
||||||
const { result, loading, error, load } = useLazyQuery(GET_CATEGORIES);
|
|
||||||
|
|
||||||
const categories = computed(() => result.value?.categories.edges ?? []);
|
|
||||||
|
|
||||||
if (error.value) {
|
|
||||||
console.error("useCategories error:", error.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
categories,
|
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
getCategories: load
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import { useLazyQuery } from "@vue/apollo-composable";
|
|
||||||
import {computed} from "vue";
|
|
||||||
import {GET_CATEGORY_BY_SLUG} from "@/graphql/queries/categories.js";
|
|
||||||
|
|
||||||
export function useCategorybySlug() {
|
|
||||||
const { result, loading, error, load } = useLazyQuery(GET_CATEGORY_BY_SLUG);
|
|
||||||
|
|
||||||
const category = computed(() => result.value?.categories.edges[0].node ?? []);
|
|
||||||
|
|
||||||
if (error.value) {
|
|
||||||
console.error("useCategorybySlug error:", error.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const getCategory = (slug) => {
|
|
||||||
return load(null, { slug });
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
category,
|
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
getCategory
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
export * from './useCompanyInfo.js';
|
export * from './useCompanyInfo';
|
||||||
|
|
@ -1,26 +1,34 @@
|
||||||
import {useLazyQuery} from "@vue/apollo-composable";
|
import { ref } from 'vue';
|
||||||
|
import { request } from 'graphql-request';
|
||||||
import {GET_COMPANY_INFO} from "@/graphql/queries/company.js";
|
import {GET_COMPANY_INFO} from "@/graphql/queries/company.js";
|
||||||
import {useCompanyStore} from "@/stores/company.js";
|
import { setCompanyInfo } from '@/stores/company.js';
|
||||||
import {watchEffect} from "vue";
|
import {APP_API_DOMAIN} from "@/config/index.js";
|
||||||
|
|
||||||
export function useCompanyInfo() {
|
export function useCompanyInfo() {
|
||||||
const companyStore = useCompanyStore()
|
const company = ref(null);
|
||||||
|
const loading = ref(false);
|
||||||
|
const error = ref(null);
|
||||||
|
|
||||||
const { result, loading, error, load } = useLazyQuery(GET_COMPANY_INFO);
|
const getCompanyInfo = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
if (error.value) {
|
try {
|
||||||
console.error("useCompanyInfo error:", error.value);
|
const data = await request(APP_API_DOMAIN, GET_COMPANY_INFO);
|
||||||
}
|
company.value = data.parameters;
|
||||||
|
setCompanyInfo(data.parameters);
|
||||||
watchEffect(() => {
|
} catch (err) {
|
||||||
if (result.value?.parameters) {
|
error.value = err;
|
||||||
companyStore.setCompanyInfo(result.value.parameters);
|
console.error('useCompanyInfo error:', err);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
company,
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
getCompanyInfo: load
|
getCompanyInfo
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export * from './useContactUs.js'
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
import {useMutation} from "@vue/apollo-composable";
|
|
||||||
import {ref} from "vue";
|
|
||||||
import {ElNotification} from "element-plus";
|
|
||||||
import {useI18n} from "vue-i18n";
|
|
||||||
import {CONTACT_US} from "@/graphql/mutations/contact.js";
|
|
||||||
|
|
||||||
export function useContactUs() {
|
|
||||||
const {t} = useI18n();
|
|
||||||
|
|
||||||
const { mutate: contactUsMutation } = useMutation(CONTACT_US);
|
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
async function contactUs(
|
|
||||||
name,
|
|
||||||
email,
|
|
||||||
phoneNumber,
|
|
||||||
subject,
|
|
||||||
message
|
|
||||||
) {
|
|
||||||
loading.value = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await contactUsMutation({
|
|
||||||
name,
|
|
||||||
email,
|
|
||||||
phoneNumber,
|
|
||||||
subject,
|
|
||||||
message
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.data?.contactUs.received) {
|
|
||||||
ElNotification({
|
|
||||||
message: t('popup.success.contactUs'),
|
|
||||||
type: 'success'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("useContactUs error:", error);
|
|
||||||
|
|
||||||
const errorMessage = error.graphQLErrors?.[0]?.message ||
|
|
||||||
error.message ||
|
|
||||||
t('popup.errors.defaultError');
|
|
||||||
|
|
||||||
ElNotification({
|
|
||||||
title: t('popup.errors.main'),
|
|
||||||
message: errorMessage,
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
contactUs,
|
|
||||||
loading
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
export * from './useLanguageSwitch.js'
|
export * from './useLanguageSwitch.js'
|
||||||
export * from './useLanguages.js'
|
export * from './useLanguages'
|
||||||
|
|
@ -1,62 +1,93 @@
|
||||||
import {useMutation} from "@vue/apollo-composable";
|
import {computed, ref} from 'vue';
|
||||||
import {computed, ref} from "vue";
|
import { request } from 'graphql-request';
|
||||||
import {ElNotification} from "element-plus";
|
import { SWITCH_LANGUAGE } from "@/graphql/mutations/languages.js";
|
||||||
import {useI18n} from "vue-i18n";
|
import {useCookies} from "@vueuse/integrations/useCookies";
|
||||||
import {useUserStore} from "@/stores/user.js";
|
import {APP_API_DOMAIN, COOKIES_ACCESS_TOKEN_KEY, COOKIES_LOCALE_KEY, SUPPORTED_LOCALES} from "@/config/index.js";
|
||||||
import translations from "@/core/helpers/translations.js";
|
import { useStore } from '@nanostores/vue';
|
||||||
import {SWITCH_LANGUAGE} from "@/graphql/mutations/languages.js";
|
import {setUser, user} from '@/stores/user';
|
||||||
import {LOCALE_STORAGE_ACCESS_TOKEN_KEY} from "@/config/index.js";
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
export function useLanguageSwitch() {
|
export function useLanguageSwitch() {
|
||||||
const userStore = useUserStore()
|
const cookie = useCookies(['/', null, null])
|
||||||
const {t} = useI18n();
|
const $user = useStore(user);
|
||||||
|
const { locale } = useI18n();
|
||||||
const { mutate: languageSwitchMutation } = useMutation(SWITCH_LANGUAGE);
|
|
||||||
|
|
||||||
const accessToken = computed(() => localStorage.getItem(LOCALE_STORAGE_ACCESS_TOKEN_KEY))
|
|
||||||
const userUuid = computed(() => userStore.user?.uuid)
|
|
||||||
|
|
||||||
|
const userUuid = computed(() => $user.uuid)
|
||||||
|
const accessToken = computed(() => cookie.get(COOKIES_ACCESS_TOKEN_KEY))
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const error = ref(null);
|
||||||
|
|
||||||
async function switchLanguage(
|
const updateUserLanguageOnServer = async (localeCode) => {
|
||||||
locale
|
|
||||||
) {
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
translations.switchLanguage(locale)
|
|
||||||
if (accessToken.value) {
|
if (accessToken.value) {
|
||||||
const response = await languageSwitchMutation(
|
const variables = {
|
||||||
userUuid.value,
|
userUuid: userUuid.value,
|
||||||
locale
|
locale: localeCode
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = await request(
|
||||||
|
APP_API_DOMAIN,
|
||||||
|
SWITCH_LANGUAGE,
|
||||||
|
variables
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.data?.updateUser) {
|
if (data?.updateUser) {
|
||||||
userStore.setUser({
|
setUser(data.updateUser.user)
|
||||||
user: response.data.updateUser.user,
|
|
||||||
accessToken: accessToken.value
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
return false;
|
||||||
console.error("useLanguageSet error:", error);
|
} catch (err) {
|
||||||
|
error.value = err;
|
||||||
const errorMessage = error.graphQLErrors?.[0]?.message ||
|
console.error('useLanguageSwitch error:', err);
|
||||||
error.message ||
|
return false;
|
||||||
t('popup.errors.defaultError');
|
|
||||||
|
|
||||||
ElNotification({
|
|
||||||
title: t('popup.errors.main'),
|
|
||||||
message: errorMessage,
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const switchLanguage = async (localeCode) => {
|
||||||
|
if (!SUPPORTED_LOCALES.some(locale => locale.code === localeCode)) {
|
||||||
|
console.error(`Locale ${localeCode} is not supported`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
cookie.set(COOKIES_LOCALE_KEY, localeCode);
|
||||||
|
|
||||||
|
locale.value = localeCode;
|
||||||
|
|
||||||
|
if (accessToken.value && userUuid.value) {
|
||||||
|
await updateUserLanguageOnServer(localeCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentPath = window.location.pathname;
|
||||||
|
const pathParts = currentPath.split('/');
|
||||||
|
|
||||||
|
if (pathParts.length > 1 && SUPPORTED_LOCALES.some(locale => locale.code === pathParts[1])) {
|
||||||
|
pathParts[1] = localeCode;
|
||||||
|
} else {
|
||||||
|
pathParts.splice(1, 0, localeCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.href = pathParts.join('/');
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error switching language:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
user,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
switchLanguage,
|
switchLanguage,
|
||||||
loading
|
updateUserLanguageOnServer
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -1,35 +1,37 @@
|
||||||
import { useLazyQuery } from "@vue/apollo-composable";
|
import {ref} from "vue";
|
||||||
import {watchEffect} from "vue";
|
|
||||||
import {GET_LANGUAGES} from "@/graphql/queries/languages.js";
|
import {GET_LANGUAGES} from "@/graphql/queries/languages.js";
|
||||||
import {useLanguageStore} from "@/stores/languages.js";
|
import {APP_API_DOMAIN, SUPPORTED_LOCALES} from "@/config/index.js";
|
||||||
import {LOCALE_STORAGE_LOCALE_KEY, SUPPORTED_LOCALES} from "@/config/index.js";
|
import {request} from "graphql-request";
|
||||||
|
|
||||||
export function useLanguages() {
|
export function useLanguages() {
|
||||||
const languageStore = useLanguageStore()
|
const languages = ref([])
|
||||||
|
const loading = ref(false);
|
||||||
|
const error = ref(null);
|
||||||
|
|
||||||
const { result, loading, error, load } = useLazyQuery(GET_LANGUAGES);
|
const getLanguages = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
if (error.value) {
|
try {
|
||||||
console.error("useLanguages error:", error.value);
|
const data = await request(APP_API_DOMAIN, GET_LANGUAGES);
|
||||||
}
|
|
||||||
|
|
||||||
watchEffect(() => {
|
languages.value = data.languages.filter((locale) =>
|
||||||
if (result.value?.languages) {
|
SUPPORTED_LOCALES.some(supportedLocale =>
|
||||||
languageStore.setLanguages(
|
supportedLocale.code === locale.code
|
||||||
result.value.languages.filter((locale) =>
|
|
||||||
SUPPORTED_LOCALES.some(supportedLocale =>
|
|
||||||
supportedLocale.code === locale.code
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
} catch (err) {
|
||||||
languageStore.setCurrentLocale(languageStore.languages.find((locale) => locale.code === localStorage.getItem(LOCALE_STORAGE_LOCALE_KEY)))
|
error.value = err;
|
||||||
|
console.error('useLanguage error:', err);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
languages,
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
getLanguages: load
|
getLanguages
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export * from './usePendingOrder.js';
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import {useMutation} from "@vue/apollo-composable";
|
|
||||||
import {GET_ORDERS} from "@/graphql/queries/orders.js";
|
|
||||||
import {useCartStore} from "@/stores/cart.js";
|
|
||||||
|
|
||||||
export function usePendingOrder() {
|
|
||||||
const cartStore = useCartStore()
|
|
||||||
|
|
||||||
const { mutate: pendingOrderMutation } = useMutation(GET_ORDERS);
|
|
||||||
|
|
||||||
async function getPendingOrder(userEmail) {
|
|
||||||
const response = await pendingOrderMutation({
|
|
||||||
status: "PENDING",
|
|
||||||
userEmail
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.errors) {
|
|
||||||
cartStore.setCurrentOrders(response.data.orders.edges[0].node)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
getPendingOrder
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
export * from './useProducts.js'
|
|
||||||
export * from './useProductBySlug.js'
|
|
||||||
export * from './useProductTags.js'
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import { useLazyQuery } from "@vue/apollo-composable";
|
|
||||||
import {computed} from "vue";
|
|
||||||
import {GET_PRODUCT_BY_SLUG} from "@/graphql/queries/products.js";
|
|
||||||
|
|
||||||
export function useProductbySlug() {
|
|
||||||
const { result, loading, error, load } = useLazyQuery(GET_PRODUCT_BY_SLUG);
|
|
||||||
|
|
||||||
const product = computed(() => result.value?.products.edges[0].node ?? []);
|
|
||||||
|
|
||||||
if (error.value) {
|
|
||||||
console.error("useProductbySlug error:", error.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const getProduct = (slug) => {
|
|
||||||
return load(null, { slug });
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
product,
|
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
getProduct
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import { useLazyQuery } from "@vue/apollo-composable";
|
|
||||||
import {computed} from "vue";
|
|
||||||
import {GET_PRODUCT_BY_SLUG, GET_PRODUCT_TAGS} from "@/graphql/queries/products.js";
|
|
||||||
|
|
||||||
export function useProductTags() {
|
|
||||||
const { result, loading, error, load } = useLazyQuery(GET_PRODUCT_TAGS);
|
|
||||||
|
|
||||||
const tags = computed(() => result.value?.productTags.edges ?? []);
|
|
||||||
|
|
||||||
if (error.value) {
|
|
||||||
console.error("useProductTags error:", error.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
tags,
|
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
getProductTags: load
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
import { useLazyQuery } from "@vue/apollo-composable";
|
|
||||||
import { computed, ref } from "vue";
|
|
||||||
import { GET_PRODUCTS } from "@/graphql/queries/products.js";
|
|
||||||
|
|
||||||
export function useProducts() {
|
|
||||||
const variables = ref({
|
|
||||||
first: 12
|
|
||||||
});
|
|
||||||
|
|
||||||
const { result, loading, error, load } = useLazyQuery(
|
|
||||||
GET_PRODUCTS,
|
|
||||||
() => variables.value
|
|
||||||
);
|
|
||||||
|
|
||||||
const products = computed(() => result.value?.products.edges ?? []);
|
|
||||||
const pageInfo = computed(() => result.value?.products.pageInfo ?? {});
|
|
||||||
|
|
||||||
if (error.value) {
|
|
||||||
console.error("useProducts error:", error.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const getProducts = async (params = {}) => {
|
|
||||||
const newVariables = { ...variables.value };
|
|
||||||
|
|
||||||
Object.entries(params).forEach(([key, value]) => {
|
|
||||||
if (value !== undefined && value !== null && value !== '') {
|
|
||||||
newVariables[key] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
variables.value = newVariables;
|
|
||||||
|
|
||||||
if (result.value) {
|
|
||||||
await refetch();
|
|
||||||
} else {
|
|
||||||
await load();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
products,
|
|
||||||
pageInfo,
|
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
getProducts
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
export * from './useSearch.js'
|
export * from './useSearch'
|
||||||
export * from './useSearchUi.js'
|
export * from './useSearchUi'
|
||||||
|
|
@ -1,52 +1,40 @@
|
||||||
import {useMutation} from "@vue/apollo-composable";
|
import { ref } from 'vue';
|
||||||
import {ref} from "vue";
|
import { request } from 'graphql-request';
|
||||||
import {ElNotification} from "element-plus";
|
import { SEARCH } from "@/graphql/mutations/search.js";
|
||||||
import {useI18n} from "vue-i18n";
|
import {APP_API_DOMAIN} from "@/config/index.js";
|
||||||
import {SEARCH} from "@/graphql/mutations/search.js";
|
|
||||||
|
|
||||||
export function useSearch() {
|
export function useSearch() {
|
||||||
const {t} = useI18n();
|
|
||||||
|
|
||||||
const { mutate: searchMutation } = useMutation(SEARCH);
|
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
const searchResults = ref(null);
|
const searchResults = ref(null);
|
||||||
|
const loading = ref(false);
|
||||||
|
const error = ref(null);
|
||||||
|
|
||||||
async function search(
|
const search = async (query) => {
|
||||||
query
|
|
||||||
) {
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
searchResults.value = null;
|
error.value = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await searchMutation({
|
const variables = { query };
|
||||||
query
|
const data = await request(
|
||||||
});
|
APP_API_DOMAIN,
|
||||||
|
SEARCH,
|
||||||
|
variables
|
||||||
|
);
|
||||||
|
|
||||||
if (response.data?.search) {
|
searchResults.value = data.search.results;
|
||||||
searchResults.value = response.data.search.results;
|
return data.search.results;
|
||||||
return response.data.search;
|
} catch (err) {
|
||||||
}
|
error.value = err;
|
||||||
} catch (error) {
|
console.error('useSearch error:', err);
|
||||||
console.error("useSearch error:", error);
|
return null;
|
||||||
|
|
||||||
const errorMessage = error.graphQLErrors?.[0]?.message ||
|
|
||||||
error.message ||
|
|
||||||
t('popup.errors.defaultError');
|
|
||||||
|
|
||||||
ElNotification({
|
|
||||||
title: t('popup.errors.main'),
|
|
||||||
message: errorMessage,
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
search,
|
searchResults,
|
||||||
loading,
|
loading,
|
||||||
searchResults
|
error,
|
||||||
|
search
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -24,7 +24,7 @@ export function useSearchUI() {
|
||||||
|
|
||||||
return Object.keys(searchResults.value).some(key => {
|
return Object.keys(searchResults.value).some(key => {
|
||||||
return Array.isArray(searchResults.value[key]) &&
|
return Array.isArray(searchResults.value[key]) &&
|
||||||
searchResults.value[key].length > 0;
|
searchResults.value[key].length > 0;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
export * from './useUserUpdating.js';
|
|
||||||
export * from './useUserActivation.js';
|
|
||||||
export * from '../languages/useLanguageSwitch.js';
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
import {useMutation} from "@vue/apollo-composable";
|
|
||||||
import {ref} from "vue";
|
|
||||||
import {ElNotification} from "element-plus";
|
|
||||||
import {useI18n} from "vue-i18n";
|
|
||||||
import {DEPOSIT} from "@/graphql/mutations/deposit.js";
|
|
||||||
|
|
||||||
export function useDeposit() {
|
|
||||||
const {t} = useI18n();
|
|
||||||
|
|
||||||
const { mutate: depositMutation } = useMutation(DEPOSIT);
|
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
async function deposit(
|
|
||||||
amount
|
|
||||||
) {
|
|
||||||
loading.value = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await depositMutation(
|
|
||||||
amount
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.data?.deposit) {
|
|
||||||
window.open(response.data.deposit.transaction.process.url)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("useDeposit error:", error);
|
|
||||||
|
|
||||||
const errorMessage = error.graphQLErrors?.[0]?.message ||
|
|
||||||
error.message ||
|
|
||||||
t('popup.errors.defaultError');
|
|
||||||
|
|
||||||
ElNotification({
|
|
||||||
title: t('popup.errors.main'),
|
|
||||||
message: errorMessage,
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
deposit,
|
|
||||||
loading
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
import {useMutation} from "@vue/apollo-composable";
|
|
||||||
import {computed, ref} from "vue";
|
|
||||||
import {ElNotification} from "element-plus";
|
|
||||||
import {useI18n} from "vue-i18n";
|
|
||||||
import {useRoute} from "vue-router";
|
|
||||||
import {ACTIVATE_USER} from "@/graphql/mutations/user.js";
|
|
||||||
|
|
||||||
export function useUserActivation() {
|
|
||||||
const {t} = useI18n();
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
const { mutate: userActivationMutation } = useMutation(ACTIVATE_USER);
|
|
||||||
|
|
||||||
const token = computed(() =>
|
|
||||||
route.query.token ? (route.query.token) : undefined,
|
|
||||||
);
|
|
||||||
const uid = computed(() =>
|
|
||||||
route.query.uid ? (route.query.uid) : undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
async function activateUser() {
|
|
||||||
loading.value = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await userActivationMutation({
|
|
||||||
token: token.value,
|
|
||||||
uid: uid.value
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.data?.activateUser) {
|
|
||||||
ElNotification({
|
|
||||||
message: t("popup.activationSuccess"),
|
|
||||||
type: "success"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("useUserActivation error:", error);
|
|
||||||
|
|
||||||
const errorMessage = error.graphQLErrors?.[0]?.message ||
|
|
||||||
error.message ||
|
|
||||||
t('popup.errors.defaultError');
|
|
||||||
|
|
||||||
ElNotification({
|
|
||||||
title: t('popup.errors.main'),
|
|
||||||
message: errorMessage,
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
activateUser,
|
|
||||||
loading
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
import {useMutation} from "@vue/apollo-composable";
|
|
||||||
import {computed, ref} from "vue";
|
|
||||||
import {ElNotification} from "element-plus";
|
|
||||||
import {useI18n} from "vue-i18n";
|
|
||||||
import {useUserStore} from "@/stores/user.js";
|
|
||||||
import translations from "@/core/helpers/translations.js";
|
|
||||||
import {useRoute, useRouter} from "vue-router";
|
|
||||||
import {useLogout} from "@/composables/auth";
|
|
||||||
import {UPDATE_USER} from "@/graphql/mutations/user.js";
|
|
||||||
import {LOCALE_STORAGE_ACCESS_TOKEN_KEY} from "@/config/index.js";
|
|
||||||
|
|
||||||
export function useUserUpdating() {
|
|
||||||
const router = useRouter();
|
|
||||||
const route = useRoute();
|
|
||||||
const userStore = useUserStore()
|
|
||||||
const {t} = useI18n();
|
|
||||||
|
|
||||||
const { mutate: userUpdatingMutation } = useMutation(UPDATE_USER);
|
|
||||||
|
|
||||||
const { logout } = useLogout();
|
|
||||||
|
|
||||||
const accessToken = computed(() => localStorage.getItem(LOCALE_STORAGE_ACCESS_TOKEN_KEY))
|
|
||||||
const userUuid = computed(() => userStore.user?.uuid)
|
|
||||||
const userEmail = computed(() => userStore.user?.email)
|
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
async function updateUser(
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
email,
|
|
||||||
phoneNumber,
|
|
||||||
password,
|
|
||||||
confirmPassword
|
|
||||||
) {
|
|
||||||
loading.value = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const fields = {
|
|
||||||
uuid: userUuid.value,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
email,
|
|
||||||
phoneNumber,
|
|
||||||
password,
|
|
||||||
confirmPassword
|
|
||||||
};
|
|
||||||
|
|
||||||
const params = Object.fromEntries(
|
|
||||||
Object.entries(fields).filter(([_, value]) =>
|
|
||||||
value !== undefined && value !== null && value !== ''
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// if (('password' in params && !('passwordConfirm' in params)) ||
|
|
||||||
// (!('password' in params) && 'passwordConfirm' in params)) {
|
|
||||||
// ElNotification({
|
|
||||||
// title: t('popup.errors.main'),
|
|
||||||
// message: t('popup.errors.noDataToUpdate'),
|
|
||||||
// type: 'error'
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (Object.keys(params).length === 0) {
|
|
||||||
ElNotification({
|
|
||||||
title: t('popup.errors.main'),
|
|
||||||
message: t('popup.errors.noDataToUpdate'),
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await userUpdatingMutation(
|
|
||||||
params
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.data?.updateUser) {
|
|
||||||
if (userEmail.value !== email) {
|
|
||||||
await logout()
|
|
||||||
|
|
||||||
ElNotification({
|
|
||||||
message: t("popup.success.confirmEmail"),
|
|
||||||
type: "success"
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
userStore.setUser({
|
|
||||||
user: response.data.updateUser.user,
|
|
||||||
accessToken: accessToken.value
|
|
||||||
})
|
|
||||||
|
|
||||||
ElNotification({
|
|
||||||
message: t("popup.successUpdate"),
|
|
||||||
type: "success"
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.data.updateUser.user.language !== translations.currentLocale) {
|
|
||||||
translations.switchLanguage(response.data.updateUser.user.language, router, route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("useUserUpdating error:", error);
|
|
||||||
|
|
||||||
const errorMessage = error.graphQLErrors?.[0]?.message ||
|
|
||||||
error.message ||
|
|
||||||
t('popup.errors.defaultError');
|
|
||||||
|
|
||||||
ElNotification({
|
|
||||||
title: t('popup.errors.main'),
|
|
||||||
message: errorMessage,
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
updateUser,
|
|
||||||
loading
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export * from './useMainClient.js';
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
import { ref } from 'vue';
|
|
||||||
|
|
||||||
export function useMailClient() {
|
|
||||||
const mailClientUrl = ref(null);
|
|
||||||
|
|
||||||
const mailClients = {
|
|
||||||
'gmail.com': 'https://mail.google.com/',
|
|
||||||
'outlook.com': 'https://outlook.live.com/',
|
|
||||||
'icloud.com': 'https://www.icloud.com/mail/',
|
|
||||||
'yahoo.com': 'https://mail.yahoo.com/',
|
|
||||||
'mail.ru': 'https://e.mail.ru/inbox/',
|
|
||||||
'yandex.ru': 'https://mail.yandex.ru/',
|
|
||||||
'proton.me': 'https://account.proton.me/mail',
|
|
||||||
'fastmail.com': 'https://fastmail.com/'
|
|
||||||
};
|
|
||||||
|
|
||||||
function detectMailClient(email) {
|
|
||||||
mailClientUrl.value = null;
|
|
||||||
|
|
||||||
if (!email) return;
|
|
||||||
|
|
||||||
const domain = email.split('@')[1];
|
|
||||||
|
|
||||||
Object.entries(mailClients).forEach((el) => {
|
|
||||||
if (domain === el[0]) mailClientUrl.value = el[1];
|
|
||||||
});
|
|
||||||
|
|
||||||
return mailClientUrl.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function openMailClient() {
|
|
||||||
if (mailClientUrl.value) {
|
|
||||||
window.open(mailClientUrl.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
mailClientUrl,
|
|
||||||
detectMailClient,
|
|
||||||
openMailClient
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export * from './useWishlist.js';
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
import {useMutation} from "@vue/apollo-composable";
|
|
||||||
import {GET_WISHLIST} from "@/graphql/queries/wishlist.js";
|
|
||||||
import {useWishlistStore} from "@/stores/wishlist.js";
|
|
||||||
|
|
||||||
export function useWishlist() {
|
|
||||||
const wishlistStore = useWishlistStore()
|
|
||||||
|
|
||||||
const { mutate: wishlistMutation } = useMutation(GET_WISHLIST);
|
|
||||||
|
|
||||||
async function getWishlist() {
|
|
||||||
const response = await wishlistMutation();
|
|
||||||
|
|
||||||
if (!response.errors) {
|
|
||||||
wishlistStore.setWishlist(response.data.wishlists.edges[0].node)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
getWishlist
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
// APP
|
// APP
|
||||||
|
|
||||||
export const APP_NAME = import.meta.env.EVIBES_PROJECT_NAME
|
// export const APP_NAME = import.meta.env.EVIBES_PROJECT_NAME
|
||||||
|
export const APP_NAME = "Flaucards"
|
||||||
|
|
||||||
export const APP_NAME_KEY = APP_NAME.toLowerCase()
|
export const APP_NAME_KEY = APP_NAME.toLowerCase()
|
||||||
|
|
||||||
|
// export const APP_API_DOMAIN = 'https://api.' + import.meta.env.EVIBES_BASE_DOMAIN + '/graphql/'
|
||||||
|
export const APP_API_DOMAIN = 'https://api.' + 'flaucards.com' + '/graphql/'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// LOCALES
|
// LOCALES
|
||||||
|
|
@ -79,10 +83,10 @@ export const DEFAULT_LOCALE = SUPPORTED_LOCALES.find(locale => locale.default)?.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// LOCAL STORAGE
|
// COOKIES
|
||||||
|
|
||||||
export const LOCALE_STORAGE_LOCALE_KEY = `${APP_NAME_KEY}-user-locale`;
|
export const COOKIES_LOCALE_KEY = `${APP_NAME_KEY}-user-locale`;
|
||||||
|
|
||||||
export const LOCALE_STORAGE_REFRESH_TOKEN_KEY = `${APP_NAME_KEY}-refresh`;
|
export const COOKIES_REFRESH_TOKEN_KEY = `${APP_NAME_KEY}-refresh`;
|
||||||
|
|
||||||
export const LOCALE_STORAGE_ACCESS_TOKEN_KEY = `${APP_NAME_KEY}-access`;
|
export const COOKIES_ACCESS_TOKEN_KEY = `${APP_NAME_KEY}-access`;
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
export async function loadLocaleMessages(locale) {
|
|
||||||
try {
|
|
||||||
const messages = await import(`../locales/${locale}.json`)
|
|
||||||
return messages.default || messages
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to load locale: ${locale}`, error)
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getLocaleFilename(localeCode, localesConfig) {
|
|
||||||
const localeInfo = localesConfig.find(locale => locale.code === localeCode)
|
|
||||||
return localeInfo?.file || `${localeCode}.json`
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadAllLocaleMessages(supportedLocales) {
|
|
||||||
const messages = {}
|
|
||||||
|
|
||||||
for (const locale of supportedLocales) {
|
|
||||||
try {
|
|
||||||
const localeMessages = await import(`../../locales/${locale.code}.json`)
|
|
||||||
messages[locale.code] = localeMessages.default || localeMessages
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to load locale: ${locale.code}`, error)
|
|
||||||
messages[locale.code] = {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return messages
|
|
||||||
}
|
|
||||||
|
|
@ -1,122 +0,0 @@
|
||||||
import i18n from '@/plugins/i18n.js';
|
|
||||||
import {DEFAULT_LOCALE, LOCALE_STORAGE_LOCALE_KEY, SUPPORTED_LOCALES} from "@/config/index.js";
|
|
||||||
|
|
||||||
const translations = {
|
|
||||||
get currentLocale() {
|
|
||||||
return i18n.global?.locale?.value || DEFAULT_LOCALE.code;
|
|
||||||
},
|
|
||||||
|
|
||||||
set currentLocale(newLocale) {
|
|
||||||
if (i18n.global?.locale) {
|
|
||||||
i18n.global.locale.value = newLocale;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
switchLanguage(newLocale) {
|
|
||||||
translations.currentLocale = newLocale;
|
|
||||||
|
|
||||||
if (typeof document !== 'undefined') {
|
|
||||||
document.querySelector('html')?.setAttribute('lang', newLocale);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof localStorage !== 'undefined') {
|
|
||||||
localStorage.setItem(LOCALE_STORAGE_LOCALE_KEY, newLocale);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
isLocaleSupported(locale) {
|
|
||||||
if (locale) {
|
|
||||||
return SUPPORTED_LOCALES.some(supportedLocale => supportedLocale.code === locale);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
getUserLocale() {
|
|
||||||
if (typeof window === 'undefined') {
|
|
||||||
return {
|
|
||||||
locale: DEFAULT_LOCALE.code,
|
|
||||||
localeNoRegion: DEFAULT_LOCALE.code.split('-')[0]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const locale = window.navigator.language || DEFAULT_LOCALE.code;
|
|
||||||
|
|
||||||
return {
|
|
||||||
locale: locale,
|
|
||||||
localeNoRegion: locale.split('-')[0]
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getPersistedLocale() {
|
|
||||||
if (typeof localStorage === 'undefined') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const persistedLocale = localStorage.getItem(LOCALE_STORAGE_LOCALE_KEY);
|
|
||||||
|
|
||||||
if (translations.isLocaleSupported(persistedLocale)) {
|
|
||||||
return persistedLocale;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
guessDefaultLocale() {
|
|
||||||
const userPersistedLocale = translations.getPersistedLocale();
|
|
||||||
if (userPersistedLocale) {
|
|
||||||
return userPersistedLocale;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
const userPreferredLocale = translations.getUserLocale();
|
|
||||||
|
|
||||||
if (translations.isLocaleSupported(userPreferredLocale.locale)) {
|
|
||||||
return userPreferredLocale.locale;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (translations.isLocaleSupported(userPreferredLocale.localeNoRegion)) {
|
|
||||||
return userPreferredLocale.localeNoRegion;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return DEFAULT_LOCALE.code;
|
|
||||||
},
|
|
||||||
|
|
||||||
getLocaleFromUrl(url) {
|
|
||||||
const pathParts = new URL(url).pathname.split('/').filter(Boolean);
|
|
||||||
if (pathParts.length > 0) {
|
|
||||||
const possibleLocale = pathParts[0];
|
|
||||||
if (translations.isLocaleSupported(possibleLocale)) {
|
|
||||||
return possibleLocale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return DEFAULT_LOCALE.code;
|
|
||||||
},
|
|
||||||
|
|
||||||
getLocaleFromRequest(request) {
|
|
||||||
if (request?.headers) {
|
|
||||||
const acceptLanguage = request.headers.get('accept-language');
|
|
||||||
if (acceptLanguage) {
|
|
||||||
const preferredLocales = acceptLanguage.split(',')
|
|
||||||
.map(lang => {
|
|
||||||
const [locale, priority = 'q=1.0'] = lang.trim().split(';');
|
|
||||||
return {
|
|
||||||
locale: locale.split('-')[0],
|
|
||||||
priority: parseFloat(priority.split('=')[1])
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.sort((a, b) => b.priority - a.priority);
|
|
||||||
|
|
||||||
for (const { locale } of preferredLocales) {
|
|
||||||
if (translations.isLocaleSupported(locale)) {
|
|
||||||
return locale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return DEFAULT_LOCALE.code;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default translations;
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
import i18n from '@/core/plugins/i18n.config'
|
|
||||||
|
|
||||||
const isEmail = (email) => {
|
|
||||||
if (!email) return required(email);
|
|
||||||
if (/.+@.+\..+/.test(email)) return true
|
|
||||||
const { t } = i18n.global
|
|
||||||
return t('errors.mail')
|
|
||||||
}
|
|
||||||
|
|
||||||
const required = (text) => {
|
|
||||||
if (text) return true
|
|
||||||
const { t } = i18n.global
|
|
||||||
return t('errors.required')
|
|
||||||
}
|
|
||||||
|
|
||||||
const isPasswordValid = (pass) => {
|
|
||||||
const { t } = i18n.global
|
|
||||||
|
|
||||||
if (pass.length < 8) {
|
|
||||||
return t('errors.needMin')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!/[a-z]/.test(pass)) {
|
|
||||||
return t('errors.needLower')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!/[A-Z]/.test(pass)) {
|
|
||||||
return t('errors.needUpper')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!/\d/.test(pass)) {
|
|
||||||
return t('errors.needNumber')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!/[#.?!@$%^&*'()_+=:;"'/>.<,|\-]/.test(pass)) {
|
|
||||||
return t('errors.needSpecial')
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
export { required, isEmail, isPasswordValid }
|
|
||||||
15
storefront/src/helpers/i18n-utils.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
export async function loadAllLocaleMessages(supportedLocales) {
|
||||||
|
const messages = {}
|
||||||
|
|
||||||
|
for (const locale of supportedLocales) {
|
||||||
|
try {
|
||||||
|
const localeMessages = await import(`@/locales/${locale.code}.json`)
|
||||||
|
messages[locale.code] = localeMessages.default || localeMessages
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Fetch locale error: ${locale.code}`, error)
|
||||||
|
messages[locale.code] = {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
|
@ -1,27 +1,70 @@
|
||||||
---
|
---
|
||||||
import AppInitializer from "@/components/app-initializer.vue";
|
import BaseHeader from '@/components/base/header/base-header.vue'
|
||||||
import BaseHeader from "@/components/base/header/base-header.vue";
|
import '@/assets/styles/global/fonts.scss'
|
||||||
import BaseFooter from "@/components/base/base-footer.vue";
|
import '@/assets/styles/main.scss'
|
||||||
|
import 'primeicons/primeicons.css'
|
||||||
|
import 'element-plus/dist/index.css'
|
||||||
|
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||||
|
import { useCompanyInfo } from '@/composables/company';
|
||||||
|
import {COOKIES_LOCALE_KEY, SUPPORTED_LOCALES} from "@/config/index.js";
|
||||||
|
import {useCookies} from "@vueuse/integrations/useCookies";
|
||||||
|
|
||||||
|
const cookie = useCookies(['/', null, null])
|
||||||
|
|
||||||
|
const { getCompanyInfo } = useCompanyInfo();
|
||||||
|
|
||||||
|
await getCompanyInfo()
|
||||||
|
|
||||||
|
const { locale } = Astro.params;
|
||||||
|
|
||||||
|
const availableLocales = SUPPORTED_LOCALES.map(locale => locale.code);
|
||||||
|
const currentLocale = availableLocales.includes(locale) ? locale : 'en-gb';
|
||||||
|
|
||||||
|
const translations = await import(`@/locales/${currentLocale}.json`);
|
||||||
|
|
||||||
|
function t(path) {
|
||||||
|
const parts = path.split('.');
|
||||||
|
let curr = translations;
|
||||||
|
for (const p of parts) {
|
||||||
|
if (curr && typeof curr === 'object' && p in curr) {
|
||||||
|
curr = curr[p];
|
||||||
|
} else {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return curr;
|
||||||
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<html lang="en">
|
<html lang={locale}>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<meta name="viewport" content="width=device-width" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="generator" content={Astro.generator} />
|
<meta name="generator" content={Astro.generator} />
|
||||||
<title>Astro</title>
|
<title>Astro Basics</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main class="main">
|
<BaseHeader
|
||||||
<BaseHeader client:only="vue" />
|
client:load
|
||||||
<slot />
|
locale={locale}
|
||||||
<BaseFooter client:only="vue" />
|
messages={translations}
|
||||||
<AppInitializer client:only="vue" />
|
/>
|
||||||
</main>
|
<main class="main">
|
||||||
</body>
|
<slot />
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
<style scoped>
|
<style lang="scss">
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
.main {
|
||||||
|
margin-top: 90px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<base-header />
|
|
||||||
<slot />
|
|
||||||
<base-footer />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {useRefresh} from "@/composables/auth";
|
|
||||||
import {useCompanyInfo} from "@/composables/company";
|
|
||||||
import {useLanguages} from "@/composables/languages/index.js";
|
|
||||||
import BaseHeader from "@/components/base/header/base-header.vue";
|
|
||||||
import BaseFooter from "@/components/base/base-footer.vue";
|
|
||||||
import {onMounted} from "vue";
|
|
||||||
import 'element-plus/dist/index.css'
|
|
||||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
|
||||||
import '@/assets/styles/global/fonts.scss'
|
|
||||||
import '@/assets/styles/main.scss'
|
|
||||||
|
|
||||||
const { refresh } = useRefresh();
|
|
||||||
const { getCompanyInfo } = useCompanyInfo();
|
|
||||||
const { getLanguages } = useLanguages();
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await refresh();
|
|
||||||
await getCompanyInfo();
|
|
||||||
await getLanguages();
|
|
||||||
|
|
||||||
setInterval(async () => {
|
|
||||||
await refresh();
|
|
||||||
}, 600000);
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"fields": {
|
||||||
|
"search": "Suche"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
8
storefront/src/pages/[locale]/index.astro
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
import DefaulLayout from '@/layouts/default-layout.astro';
|
||||||
|
import HomeHero from '@/components/home/home-hero.vue';
|
||||||
|
---
|
||||||
|
|
||||||
|
<DefaulLayout>
|
||||||
|
<HomeHero />
|
||||||
|
</DefaulLayout>
|
||||||
7
storefront/src/pages/[locale]/some.astro
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
import DefaulLayout from '@/layouts/default-layout.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<DefaulLayout>
|
||||||
|
|
||||||
|
</DefaulLayout>
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
---
|
---
|
||||||
import DefaultLayout from "../layouts/default-layout.astro";
|
import { DEFAULT_LOCALE, COOKIES_LOCALE_KEY, SUPPORTED_LOCALES } from '@/config/index.js';
|
||||||
---
|
|
||||||
|
|
||||||
<DefaultLayout>
|
const savedLocale = Astro.cookies.get(COOKIES_LOCALE_KEY)?.value;
|
||||||
<h1>Astro</h1>
|
let redirectLocale = DEFAULT_LOCALE;
|
||||||
</DefaultLayout>
|
|
||||||
|
if (savedLocale && SUPPORTED_LOCALES.some(locale => locale.code === savedLocale)) {
|
||||||
|
redirectLocale = savedLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Astro.redirect(`/${redirectLocale}`);
|
||||||
|
---
|
||||||
|
|
@ -1,61 +1,41 @@
|
||||||
import { createI18n } from 'vue-i18n'
|
import { createI18n } from 'vue-i18n'
|
||||||
import { DEFAULT_LOCALE, LOCALE_STORAGE_LOCALE_KEY, SUPPORTED_LOCALES } from "../config/index.js";
|
import {DEFAULT_LOCALE, COOKIES_LOCALE_KEY, SUPPORTED_LOCALES} from "@/config/index.js";
|
||||||
import { loadAllLocaleMessages } from "../core/helpers/i18n-utils.js";
|
import { useCookies } from '@vueuse/integrations/useCookies'
|
||||||
|
import {loadAllLocaleMessages} from "@/helpers/i18n-utils.js";
|
||||||
|
|
||||||
export async function setupI18n() {
|
const cookie = useCookies(['/', null, null])
|
||||||
const i18n = createI18n({
|
|
||||||
locale: DEFAULT_LOCALE,
|
|
||||||
fallbackLocale: DEFAULT_LOCALE,
|
|
||||||
messages: {},
|
|
||||||
allowComposition: true,
|
|
||||||
legacy: false,
|
|
||||||
globalInjection: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const messages = await loadAllLocaleMessages(SUPPORTED_LOCALES);
|
const savedLocale = cookie.get(COOKIES_LOCALE_KEY)
|
||||||
|
const currentLocale = savedLocale && SUPPORTED_LOCALES.some(locale => locale.code === savedLocale)
|
||||||
|
? savedLocale
|
||||||
|
: DEFAULT_LOCALE
|
||||||
|
|
||||||
Object.keys(messages).forEach(locale => {
|
if (!savedLocale) {
|
||||||
i18n.global.setLocaleMessage(locale, messages[locale]);
|
cookie.set(COOKIES_LOCALE_KEY, DEFAULT_LOCALE)
|
||||||
});
|
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
const savedLocale = localStorage.getItem(LOCALE_STORAGE_LOCALE_KEY);
|
|
||||||
const validLocale = savedLocale &&
|
|
||||||
SUPPORTED_LOCALES.some(locale => locale.code === savedLocale)
|
|
||||||
? savedLocale
|
|
||||||
: DEFAULT_LOCALE;
|
|
||||||
|
|
||||||
i18n.global.locale.value = validLocale;
|
|
||||||
|
|
||||||
if (!savedLocale) {
|
|
||||||
localStorage.setItem(LOCALE_STORAGE_LOCALE_KEY, DEFAULT_LOCALE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return i18n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCurrentLocale(request) {
|
const i18n = createI18n({
|
||||||
if (typeof window === 'undefined' && request) {
|
locale: currentLocale,
|
||||||
const acceptLanguageHeader = request.headers.get('accept-language');
|
fallbackLocale: DEFAULT_LOCALE,
|
||||||
if (acceptLanguageHeader) {
|
allowComposition: true,
|
||||||
const preferredLocale = acceptLanguageHeader.split(',')[0].trim().split('-')[0];
|
legacy: false,
|
||||||
const supportedLocale = SUPPORTED_LOCALES.find(locale =>
|
globalInjection: true,
|
||||||
locale.code.startsWith(preferredLocale)
|
messages: {}
|
||||||
);
|
})
|
||||||
if (supportedLocale) {
|
|
||||||
return supportedLocale.code;
|
export async function setupI18n() {
|
||||||
}
|
const messages = await loadAllLocaleMessages(SUPPORTED_LOCALES)
|
||||||
}
|
|
||||||
return DEFAULT_LOCALE;
|
Object.keys(messages).forEach(locale => {
|
||||||
|
i18n.global.setLocaleMessage(locale, messages[locale])
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentSavedLocale = cookie.get(COOKIES_LOCALE_KEY)
|
||||||
|
if (currentSavedLocale && SUPPORTED_LOCALES.some(locale => locale.code === currentSavedLocale)) {
|
||||||
|
i18n.global.locale.value = currentSavedLocale
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
return i18n
|
||||||
const savedLocale = localStorage.getItem(LOCALE_STORAGE_LOCALE_KEY);
|
}
|
||||||
return savedLocale && SUPPORTED_LOCALES.some(locale => locale.code === savedLocale)
|
|
||||||
? savedLocale
|
|
||||||
: DEFAULT_LOCALE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return DEFAULT_LOCALE;
|
export default i18n
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,11 @@
|
||||||
import { setupI18n } from './i18n.js'
|
|
||||||
import { pinia } from "../stores/index.js";
|
|
||||||
import { createApolloClient } from "../apollo/index.js";
|
|
||||||
import { DefaultApolloClient } from "@vue/apollo-composable";
|
|
||||||
import ElementPlus from 'element-plus';
|
import ElementPlus from 'element-plus';
|
||||||
|
import i18n, { setupI18n } from '@/plugins/i18n';
|
||||||
|
|
||||||
export default function (app) {
|
export default async function (app) {
|
||||||
app.use(pinia);
|
|
||||||
|
|
||||||
app.use(ElementPlus);
|
app.use(ElementPlus);
|
||||||
|
|
||||||
const apolloClient = createApolloClient();
|
await setupI18n();
|
||||||
app.provide(DefaultApolloClient, apolloClient);
|
app.use(i18n);
|
||||||
|
|
||||||
const i18n = setupI18n();
|
|
||||||
if (i18n instanceof Promise) {
|
|
||||||
i18n.then(i18nInstance => {
|
|
||||||
app.use(i18nInstance);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
app.use(i18n);
|
|
||||||
}
|
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
@ -1,14 +1,7 @@
|
||||||
import { defineStore } from 'pinia'
|
import { atom } from 'nanostores';
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
export const useCompanyStore = defineStore('company', () => {
|
export const companyInfo = atom(null);
|
||||||
const companyInfo = ref([]);
|
|
||||||
const setCompanyInfo = (payload) => {
|
|
||||||
companyInfo.value = payload
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
export function setCompanyInfo(data) {
|
||||||
companyInfo,
|
companyInfo.set(data);
|
||||||
setCompanyInfo
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
import { createPinia } from 'pinia'
|
|
||||||
|
|
||||||
export const pinia = createPinia()
|
|
||||||
|
|
@ -1,14 +1,23 @@
|
||||||
import { defineStore } from 'pinia'
|
import { map } from 'nanostores';
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
export const useLanguageStore = defineStore('language', () => {
|
export const languages = map({
|
||||||
const languages = ref([]);
|
languages: [],
|
||||||
const setLanguages = (payload) => {
|
currentLocale: null
|
||||||
languages.value = payload
|
});
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
export function setLanguages(languagesList) {
|
||||||
languages,
|
languages.setKey('languages', languagesList);
|
||||||
setLanguages
|
|
||||||
|
if (!languages.get().currentLocale && languagesList.length > 0) {
|
||||||
|
languages.setKey('currentLocale', languagesList[0]);
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
export function setCurrentLocale(locale) {
|
||||||
|
const allLanguages = languages.get().languages;
|
||||||
|
const selectedLocale = allLanguages.find(l => l.code === locale);
|
||||||
|
|
||||||
|
if (selectedLocale) {
|
||||||
|
languages.setKey('currentLocale', selectedLocale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
import {defineStore} from "pinia";
|
import { atom } from 'nanostores';
|
||||||
import {ref} from "vue";
|
|
||||||
|
|
||||||
export const useUserStore = defineStore('user', () => {
|
export const user = atom(null);
|
||||||
const user = ref(null);
|
|
||||||
const setUser = (payload) => {
|
|
||||||
user.value = payload.user
|
|
||||||
}
|
|
||||||
|
|
||||||
return { user, setUser }
|
export function setUser(data) {
|
||||||
})
|
user.set(data);
|
||||||
|
}
|
||||||
|
|
@ -11,4 +11,4 @@ export const useWishlistStore = defineStore('wishlist', () => {
|
||||||
wishlist,
|
wishlist,
|
||||||
setWishlist
|
setWishlist
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||