Merge branch 'storefront' into 'main'
* Features: 1) Add initial storefront configuration including app naming, local storage, and supported locales; 2) Introduce global styles with variables, mixins, and modules; 3) Implement router with locale-based navigation and home page setup; 4) Add Pinia stores for authentication and cart management; 5) Provide GraphQL queries for wishlist, categories, products, orders, languages, company info, and documents; 6) Add GraphQL mutations for authentication, cart, contact, deposit, and wishlist operations; 7) Include SVG assets for eye icons; 8) Add localization file for "en-gb" with buttons, errors, and fields text;
This commit is contained in:
commit
4533733b96
52 changed files with 6438 additions and 0 deletions
13
storefront/index.html
Normal file
13
storefront/index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>%EVIBES_PROJECT_NAME%</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
8
storefront/jsconfig.json
Normal file
8
storefront/jsconfig.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
4105
storefront/package-lock.json
generated
Normal file
4105
storefront/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
30
storefront/package.json
Normal file
30
storefront/package.json
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "evibes-frontend",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.13.8",
|
||||
"@vue/apollo-composable": "^4.2.2",
|
||||
"@vueuse/core": "^13.2.0",
|
||||
"element-plus": "^2.9.11",
|
||||
"graphql": "^16.11.0",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"pinia": "^3.0.1",
|
||||
"vue": "^3.5.13",
|
||||
"vue-i18n": "^11.1.4",
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.3",
|
||||
"sass": "^1.83.0",
|
||||
"sass-loader": "^16.0.4",
|
||||
"vite": "^6.2.4",
|
||||
"vite-plugin-vue-devtools": "^7.7.2"
|
||||
}
|
||||
}
|
||||
BIN
storefront/public/favicon.ico
Normal file
BIN
storefront/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
29
storefront/src/App.vue
Normal file
29
storefront/src/App.vue
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<script setup>
|
||||
import { RouterView } from 'vue-router'
|
||||
import {useRefresh} from "@/composables/auth/useRefresh.js";
|
||||
import {onMounted} from "vue";
|
||||
|
||||
const { refresh } = useRefresh();
|
||||
|
||||
onMounted(async () => {
|
||||
await refresh()
|
||||
|
||||
setInterval(async () => {
|
||||
await refresh()
|
||||
}, 600000)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="main" id="top">
|
||||
<RouterView v-slot="{ Component }">
|
||||
<Transition name="opacity" mode="out-in">
|
||||
<component :is="Component" />
|
||||
</Transition>
|
||||
</RouterView>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
44
storefront/src/apollo/index.js
Normal file
44
storefront/src/apollo/index.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client/core'
|
||||
import { setContext } from '@apollo/client/link/context'
|
||||
import {DEFAULT_LOCALE, LOCALE_STORAGE_LOCALE_KEY} from "@/config/index.js";
|
||||
import {computed} from "vue";
|
||||
import { useAuthStore } from "@/stores/auth.js";
|
||||
|
||||
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 authStore = useAuthStore()
|
||||
|
||||
const accessToken = computed(() => {
|
||||
return authStore.accessToken
|
||||
})
|
||||
|
||||
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',
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
5
storefront/src/assets/icons/eyeClosed.svg
Normal file
5
storefront/src/assets/icons/eyeClosed.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M19.7071 5.70711C20.0976 5.31658 20.0976 4.68342 19.7071 4.29289C19.3166 3.90237 18.6834 3.90237 18.2929 4.29289L14.032 8.55382C13.4365 8.20193 12.7418 8 12 8C9.79086 8 8 9.79086 8 12C8 12.7418 8.20193 13.4365 8.55382 14.032L4.29289 18.2929C3.90237 18.6834 3.90237 19.3166 4.29289 19.7071C4.68342 20.0976 5.31658 20.0976 5.70711 19.7071L9.96803 15.4462C10.5635 15.7981 11.2582 16 12 16C14.2091 16 16 14.2091 16 12C16 11.2582 15.7981 10.5635 15.4462 9.96803L19.7071 5.70711ZM12.518 10.0677C12.3528 10.0236 12.1792 10 12 10C10.8954 10 10 10.8954 10 12C10 12.1792 10.0236 12.3528 10.0677 12.518L12.518 10.0677ZM11.482 13.9323L13.9323 11.482C13.9764 11.6472 14 11.8208 14 12C14 13.1046 13.1046 14 12 14C11.8208 14 11.6472 13.9764 11.482 13.9323ZM15.7651 4.8207C14.6287 4.32049 13.3675 4 12 4C9.14754 4 6.75717 5.39462 4.99812 6.90595C3.23268 8.42276 2.00757 10.1376 1.46387 10.9698C1.05306 11.5985 1.05306 12.4015 1.46387 13.0302C1.92276 13.7326 2.86706 15.0637 4.21194 16.3739L5.62626 14.9596C4.4555 13.8229 3.61144 12.6531 3.18002 12C3.6904 11.2274 4.77832 9.73158 6.30147 8.42294C7.87402 7.07185 9.81574 6 12 6C12.7719 6 13.5135 6.13385 14.2193 6.36658L15.7651 4.8207ZM12 18C11.2282 18 10.4866 17.8661 9.78083 17.6334L8.23496 19.1793C9.37136 19.6795 10.6326 20 12 20C14.8525 20 17.2429 18.6054 19.002 17.0941C20.7674 15.5772 21.9925 13.8624 22.5362 13.0302C22.947 12.4015 22.947 11.5985 22.5362 10.9698C22.0773 10.2674 21.133 8.93627 19.7881 7.62611L18.3738 9.04043C19.5446 10.1771 20.3887 11.3469 20.8201 12C20.3097 12.7726 19.2218 14.2684 17.6986 15.5771C16.1261 16.9282 14.1843 18 12 18Z"
|
||||
fill="#000000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
3
storefront/src/assets/icons/eyeOpened.svg
Normal file
3
storefront/src/assets/icons/eyeOpened.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" fill="none">
|
||||
<path fill="#000000" fill-rule="evenodd" d="M3.415 10.242c-.067-.086-.13-.167-.186-.242a16.806 16.806 0 011.803-2.025C6.429 6.648 8.187 5.5 10 5.5c1.813 0 3.57 1.148 4.968 2.475A16.816 16.816 0 0116.771 10a16.9 16.9 0 01-1.803 2.025C13.57 13.352 11.813 14.5 10 14.5c-1.813 0-3.57-1.148-4.968-2.475a16.799 16.799 0 01-1.617-1.783zm15.423-.788L18 10l.838.546-.002.003-.003.004-.01.016-.037.054a17.123 17.123 0 01-.628.854 18.805 18.805 0 01-1.812 1.998C14.848 14.898 12.606 16.5 10 16.5s-4.848-1.602-6.346-3.025a18.806 18.806 0 01-2.44-2.852 6.01 6.01 0 01-.037-.054l-.01-.016-.003-.004-.001-.002c0-.001-.001-.001.837-.547l-.838-.546.002-.003.003-.004.01-.016a6.84 6.84 0 01.17-.245 18.804 18.804 0 012.308-2.66C5.151 5.1 7.394 3.499 10 3.499s4.848 1.602 6.346 3.025a18.803 18.803 0 012.44 2.852l.037.054.01.016.003.004.001.002zM18 10l.838-.546.355.546-.355.546L18 10zM1.162 9.454L2 10l-.838.546L.807 10l.355-.546zM9 10a1 1 0 112 0 1 1 0 01-2 0zm1-3a3 3 0 100 6 3 3 0 000-6z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1 KiB |
7
storefront/src/assets/styles/global/mixins.scss
Normal file
7
storefront/src/assets/styles/global/mixins.scss
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
@mixin hover {
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&:hover {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
}
|
||||
5
storefront/src/assets/styles/global/variables.scss
Normal file
5
storefront/src/assets/styles/global/variables.scss
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
$font_default: '', sans-serif;
|
||||
|
||||
$white: #ffffff;
|
||||
$black: #000000;
|
||||
$error: #f13838;
|
||||
4
storefront/src/assets/styles/main.scss
Normal file
4
storefront/src/assets/styles/main.scss
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
@use "modules/normalize";
|
||||
@use "modules/transitions";
|
||||
@use "global/mixins";
|
||||
@use "global/variables";
|
||||
49
storefront/src/assets/styles/modules/normalize.scss
vendored
Normal file
49
storefront/src/assets/styles/modules/normalize.scss
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
@use "../global/variables" as *;
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
overflow-x: hidden;
|
||||
font-family: $font_default;
|
||||
}
|
||||
|
||||
#app {
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
input, textarea, button {
|
||||
font-family: $font_default;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
button:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1500px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 1680px) {
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1300px) {
|
||||
.container {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
28
storefront/src/assets/styles/modules/transitions.scss
Normal file
28
storefront/src/assets/styles/modules/transitions.scss
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
.opacity-enter-active,
|
||||
.opacity-leave-active {
|
||||
transition: 0.3s ease all;
|
||||
}
|
||||
.opacity-enter-from,
|
||||
.opacity-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fromTop-enter-active,
|
||||
.fromTop-leave-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.fromTop-enter-from,
|
||||
.fromTop-leave-to {
|
||||
transform: translateY(-3rem);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fromLeft-enter-active,
|
||||
.fromLeft-leave-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.fromLeft-enter-from,
|
||||
.fromLeft-leave-to {
|
||||
transform: translateX(-3rem);
|
||||
opacity: 0;
|
||||
}
|
||||
59
storefront/src/components/forms/login-form.vue
Normal file
59
storefront/src/components/forms/login-form.vue
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<template>
|
||||
<div class="form">
|
||||
<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-button
|
||||
class="form__button"
|
||||
:isDisabled="!isFormValid"
|
||||
:isLoading="loading"
|
||||
@click="handleLogin()"
|
||||
>
|
||||
{{ t('buttons.signIn') }}
|
||||
</ui-button>
|
||||
</div>
|
||||
</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 {useLogin} from "@/composables/auth/useLogin.js";
|
||||
import UiButton from "@/components/ui/ui-button.vue";
|
||||
|
||||
const {t} = useI18n()
|
||||
|
||||
const email = ref('')
|
||||
const password = ref('')
|
||||
|
||||
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);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
</style>
|
||||
103
storefront/src/components/forms/register-form.vue
Normal file
103
storefront/src/components/forms/register-form.vue
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<template>
|
||||
<div class="form">
|
||||
<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="'text'"
|
||||
:placeholder="t('fields.phone')"
|
||||
:rules="[required]"
|
||||
v-model="phoneNumber"
|
||||
/>
|
||||
<ui-input
|
||||
:type="'email'"
|
||||
:placeholder="t('fields.email')"
|
||||
:rules="[isEmail]"
|
||||
v-model="email"
|
||||
/>
|
||||
<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"
|
||||
@click="handleRegister()"
|
||||
>
|
||||
{{ t('buttons.signUp') }}
|
||||
</ui-button>
|
||||
</div>
|
||||
</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/useRegister.js";
|
||||
|
||||
const {t} = useI18n()
|
||||
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
51
storefront/src/components/ui/ui-button.vue
Normal file
51
storefront/src/components/ui/ui-button.vue
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<template>
|
||||
<button class="button" :disabled="isDisabled" :class="[{active: isLoading}]">
|
||||
<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 $black;
|
||||
background-color: $white;
|
||||
padding-block: 5px;
|
||||
z-index: 1;
|
||||
|
||||
color: $black;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
|
||||
&:hover, &.active {
|
||||
background-color: $black;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
background-color: rgba($black, 0.5);
|
||||
}
|
||||
|
||||
&:disabled:hover, &.active {
|
||||
background-color: rgba($black, 0.5);
|
||||
}
|
||||
|
||||
&__loader {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
126
storefront/src/components/ui/ui-input.vue
Normal file
126
storefront/src/components/ui/ui-input.vue
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
<template>
|
||||
<div class="block">
|
||||
<div class="block__inner">
|
||||
<input
|
||||
:placeholder="placeholder"
|
||||
:type="isPasswordVisible"
|
||||
:value="modelValue"
|
||||
@input="onInput"
|
||||
class="block__input"
|
||||
>
|
||||
<button
|
||||
@click.prevent="setPasswordVisible"
|
||||
class="block__eyes"
|
||||
v-if="type === 'password'"
|
||||
>
|
||||
<img v-if="isPasswordVisible === 'password'" src="@icons/eyeClosed.svg" alt="eye" loading="lazy">
|
||||
<img v-else src="@icons/eyeOpened.svg" alt="eye" loading="lazy">
|
||||
</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
|
||||
})
|
||||
|
||||
const isPasswordVisible = ref(props.type)
|
||||
const setPasswordVisible = () => {
|
||||
if (isPasswordVisible.value === 'password') {
|
||||
isPasswordVisible.value = 'text'
|
||||
return
|
||||
}
|
||||
isPasswordVisible.value = 'password'
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
&__inner {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__input {
|
||||
width: 100%;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid $black;
|
||||
background-color: $white;
|
||||
|
||||
color: #1f1f1f;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0.14px;
|
||||
|
||||
&::placeholder {
|
||||
color: #2B2B2B;
|
||||
}
|
||||
}
|
||||
|
||||
&__eyes {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background-color: transparent;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
&__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>
|
||||
59
storefront/src/components/ui/ui-loader.vue
Normal file
59
storefront/src/components/ui/ui-loader.vue
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<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>
|
||||
24
storefront/src/composables/auth/useAuthOrder.js
Normal file
24
storefront/src/composables/auth/useAuthOrder.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import {useMutation} from "@vue/apollo-composable";
|
||||
import {GET_ORDERS} from "@/graphql/queries/orders.js";
|
||||
import {useCartStore} from "@/stores/cart.js";
|
||||
|
||||
export function useAuthOrder() {
|
||||
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
|
||||
};
|
||||
}
|
||||
21
storefront/src/composables/auth/useAuthWishlist.js
Normal file
21
storefront/src/composables/auth/useAuthWishlist.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import {useMutation} from "@vue/apollo-composable";
|
||||
import {GET_WISHLIST} from "@/graphql/queries/wishlist.js";
|
||||
import {useWishlistStore} from "@/stores/wishlist.js";
|
||||
|
||||
export function useAuthWishlist() {
|
||||
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
|
||||
};
|
||||
}
|
||||
78
storefront/src/composables/auth/useLogin.js
Normal file
78
storefront/src/composables/auth/useLogin.js
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
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 {useAuthStore} from "@/stores/auth.js";
|
||||
import translations from "@/core/helpers/translations.js";
|
||||
import {LOCALE_STORAGE_REFRESH_KEY} from "@/config/index.js";
|
||||
import { useAuthOrder } from './useAuthOrder';
|
||||
import { useAuthWishlist } from './useAuthWishlist';
|
||||
|
||||
export function useLogin() {
|
||||
const loading = ref(false);
|
||||
const userData = ref(null);
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const {t} = useI18n();
|
||||
|
||||
const { mutate: loginMutation } = useMutation(LOGIN);
|
||||
|
||||
const { getPendingOrder } = useAuthOrder();
|
||||
const { getWishlist } = useAuthWishlist();
|
||||
|
||||
async function login(
|
||||
email,
|
||||
password
|
||||
) {
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await loginMutation({
|
||||
email,
|
||||
password
|
||||
});
|
||||
|
||||
if (response.data?.obtainJwtToken) {
|
||||
authStore.setUser({
|
||||
user: response.data.obtainJwtToken.user,
|
||||
accessToken: response.data.obtainJwtToken.accessToken
|
||||
});
|
||||
|
||||
localStorage.setItem(LOCALE_STORAGE_REFRESH_KEY, response.data.obtainJwtToken.refreshToken)
|
||||
|
||||
ElNotification({
|
||||
message: t('popup.login.text'),
|
||||
type: 'success'
|
||||
})
|
||||
|
||||
if (response.data.obtainJwtToken.user.language !== translations.currentLocale) {
|
||||
translations.switchLanguage(response.data.obtainJwtToken.user.language)
|
||||
}
|
||||
|
||||
await getPendingOrder(response.data.obtainJwtToken.user.email);
|
||||
await getWishlist();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Login error:", error);
|
||||
|
||||
const errorMessage = error.graphQLErrors?.[0]?.message ||
|
||||
error.message ||
|
||||
t('popup.genericError');
|
||||
|
||||
ElNotification({
|
||||
title: t('popup.error'),
|
||||
message: errorMessage,
|
||||
type: 'error'
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
login,
|
||||
loading,
|
||||
userData
|
||||
};
|
||||
}
|
||||
38
storefront/src/composables/auth/useMainClient.js
Normal file
38
storefront/src/composables/auth/useMainClient.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
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/',
|
||||
'yahoo.com': 'https://mail.yahoo.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
|
||||
};
|
||||
}
|
||||
84
storefront/src/composables/auth/useRefresh.js
Normal file
84
storefront/src/composables/auth/useRefresh.js
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
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 {useAuthStore} from "@/stores/auth.js";
|
||||
import { useAuthOrder } from './useAuthOrder';
|
||||
import { useAuthWishlist } from './useAuthWishlist';
|
||||
import {DEFAULT_LOCALE, LOCALE_STORAGE_LOCALE_KEY, LOCALE_STORAGE_REFRESH_KEY} from "@/config/index.js";
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
import translations from "@/core/helpers/translations.js";
|
||||
|
||||
export function useRefresh() {
|
||||
const loading = ref(false);
|
||||
const userData = ref(null);
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const authStore = useAuthStore()
|
||||
const {t} = useI18n();
|
||||
|
||||
const { mutate: refreshMutation } = useMutation(REFRESH);
|
||||
|
||||
const { getPendingOrder } = useAuthOrder();
|
||||
const { getWishlist } = useAuthWishlist();
|
||||
|
||||
async function refresh() {
|
||||
loading.value = true;
|
||||
|
||||
const refreshToken = computed(() => {
|
||||
return localStorage.getItem(LOCALE_STORAGE_REFRESH_KEY)
|
||||
})
|
||||
|
||||
if (!refreshToken.value) return
|
||||
|
||||
try {
|
||||
const response = await refreshMutation({
|
||||
refreshToken: refreshToken.value
|
||||
});
|
||||
|
||||
if (response.data?.refreshJwtToken) {
|
||||
authStore.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)
|
||||
await router.push({
|
||||
name: route.name,
|
||||
params: {
|
||||
locale: localStorage.getItem(LOCALE_STORAGE_LOCALE_KEY) || DEFAULT_LOCALE
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
localStorage.setItem(LOCALE_STORAGE_REFRESH_KEY, response.data.refreshJwtToken.refreshToken)
|
||||
|
||||
await getPendingOrder(response.data.refreshJwtToken.user.email);
|
||||
await getWishlist();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Refresh error:", error);
|
||||
|
||||
const errorMessage = error.graphQLErrors?.[0]?.message ||
|
||||
error.message ||
|
||||
t('popup.genericError');
|
||||
|
||||
ElNotification({
|
||||
title: t('popup.error'),
|
||||
message: errorMessage,
|
||||
type: 'error'
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
refresh,
|
||||
loading,
|
||||
userData
|
||||
};
|
||||
}
|
||||
87
storefront/src/composables/auth/useRegister.js
Normal file
87
storefront/src/composables/auth/useRegister.js
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
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/auth/useMainClient.js";
|
||||
|
||||
export function useRegister() {
|
||||
const loading = ref(false);
|
||||
const mailClient = ref(null)
|
||||
|
||||
const {t} = useI18n();
|
||||
|
||||
const { mutate: registerMutation } = useMutation(REGISTER);
|
||||
|
||||
const { mailClientUrl, detectMailClient, openMailClient } = useMailClient();
|
||||
|
||||
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({
|
||||
title: t('popup.register.title'),
|
||||
message: h('div', [
|
||||
h('p', t('popup.register.text')),
|
||||
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("Register error:", error);
|
||||
|
||||
const errorMessage = error.graphQLErrors?.[0]?.message ||
|
||||
error.message ||
|
||||
t('popup.genericError');
|
||||
|
||||
ElNotification({
|
||||
title: t('popup.error'),
|
||||
message: errorMessage,
|
||||
type: 'error'
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
register,
|
||||
loading
|
||||
};
|
||||
}
|
||||
26
storefront/src/config/index.js
Normal file
26
storefront/src/config/index.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// APP
|
||||
|
||||
export const APP_NAME = import.meta.env.EVIBES_PROJECT_NAME
|
||||
|
||||
export const APP_NAME_KEY = import.meta.env.EVIBES_PROJECT_NAME.toLowerCase()
|
||||
|
||||
|
||||
|
||||
// LOCALES
|
||||
|
||||
export const SUPPORTED_LOCALES = [
|
||||
{
|
||||
code: 'en-gb',
|
||||
default: true
|
||||
}
|
||||
]
|
||||
|
||||
export const DEFAULT_LOCALE = SUPPORTED_LOCALES.find(locale => locale.default)?.code || 'en-gb'
|
||||
|
||||
|
||||
|
||||
// LOCAL STORAGE
|
||||
|
||||
export const LOCALE_STORAGE_LOCALE_KEY = `${APP_NAME_KEY}-user-locale`;
|
||||
|
||||
export const LOCALE_STORAGE_REFRESH_KEY = `${APP_NAME_KEY}-refresh`;
|
||||
30
storefront/src/core/helpers/i18n-utils.js
Normal file
30
storefront/src/core/helpers/i18n-utils.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
export async function loadLocaleMessages(locale) {
|
||||
try {
|
||||
const messages = await import(`../locales/${locale}.json`)
|
||||
return messages.default || messages
|
||||
} catch (error) {
|
||||
console.error(`Не удалось загрузить локаль: ${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(`Не удалось загрузить локаль: ${locale.code}`, error)
|
||||
messages[locale.code] = {}
|
||||
}
|
||||
}
|
||||
|
||||
return messages
|
||||
}
|
||||
91
storefront/src/core/helpers/translations.js
Normal file
91
storefront/src/core/helpers/translations.js
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import i18n from '@/core/plugins/i18n.config';
|
||||
import {DEFAULT_LOCALE, LOCALE_STORAGE_LOCALE_KEY, SUPPORTED_LOCALES} from "@/config/index.js";
|
||||
|
||||
const translation = {
|
||||
get currentLocale() {
|
||||
return i18n.global.locale.value
|
||||
},
|
||||
|
||||
set currentLocale(newLocale) {
|
||||
i18n.global.locale.value = newLocale
|
||||
},
|
||||
|
||||
switchLanguage(newLocale) {
|
||||
translation.currentLocale = newLocale
|
||||
|
||||
document.querySelector('html').setAttribute('lang', newLocale)
|
||||
|
||||
localStorage.setItem(LOCALE_STORAGE_LOCALE_KEY, newLocale)
|
||||
},
|
||||
|
||||
isLocaleSupported(locale) {
|
||||
if (locale) {
|
||||
return SUPPORTED_LOCALES.some(supportedLocale => supportedLocale.code === locale);
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
||||
getUserLocale() {
|
||||
const locale =
|
||||
window.navigator.language ||
|
||||
DEFAULT_LOCALE.code
|
||||
|
||||
return {
|
||||
locale: locale,
|
||||
localeNoRegion: locale.split('-')[0]
|
||||
}
|
||||
},
|
||||
|
||||
getPersistedLocale() {
|
||||
const persistedLocale = localStorage.getItem(LOCALE_STORAGE_LOCALE_KEY)
|
||||
|
||||
if (translation.isLocaleSupported(persistedLocale)) {
|
||||
return persistedLocale
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
},
|
||||
|
||||
guessDefaultLocale() {
|
||||
const userPersistedLocale = translation.getPersistedLocale()
|
||||
if (userPersistedLocale) {
|
||||
return userPersistedLocale
|
||||
}
|
||||
|
||||
const userPreferredLocale = translation.getUserLocale()
|
||||
|
||||
if (translation.isLocaleSupported(userPreferredLocale.locale)) {
|
||||
return userPreferredLocale.locale
|
||||
}
|
||||
|
||||
if (translation.isLocaleSupported(userPreferredLocale.localeNoRegion)) {
|
||||
return userPreferredLocale.localeNoRegion
|
||||
}
|
||||
|
||||
return DEFAULT_LOCALE.code
|
||||
},
|
||||
|
||||
async routeMiddleware(to, _from, next) {
|
||||
const paramLocale = to.params.locale
|
||||
|
||||
if (!translation.isLocaleSupported(paramLocale)) {
|
||||
return next(translation.guessDefaultLocale())
|
||||
}
|
||||
|
||||
await translation.switchLanguage(paramLocale)
|
||||
|
||||
return next()
|
||||
},
|
||||
|
||||
i18nRoute(to) {
|
||||
return {
|
||||
...to,
|
||||
params: {
|
||||
locale: translation.currentLocale,
|
||||
...to.params
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default translation
|
||||
33
storefront/src/core/plugins/i18n.config.js
Normal file
33
storefront/src/core/plugins/i18n.config.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { createI18n } from 'vue-i18n'
|
||||
import {DEFAULT_LOCALE, LOCALE_STORAGE_LOCALE_KEY, SUPPORTED_LOCALES} from "@/config/index.js";
|
||||
import {loadAllLocaleMessages} from "@/core/helpers/i18n-utils.js";
|
||||
|
||||
const savedLocale = localStorage.getItem(LOCALE_STORAGE_LOCALE_KEY)
|
||||
const currentLocale = savedLocale && SUPPORTED_LOCALES.some(locale => locale.code === savedLocale)
|
||||
? savedLocale
|
||||
: DEFAULT_LOCALE
|
||||
|
||||
if (!savedLocale) {
|
||||
localStorage.setItem(LOCALE_STORAGE_LOCALE_KEY, DEFAULT_LOCALE)
|
||||
}
|
||||
|
||||
const i18n = createI18n({
|
||||
locale: currentLocale,
|
||||
fallbackLocale: DEFAULT_LOCALE,
|
||||
allowComposition: true,
|
||||
legacy: false,
|
||||
globalInjection: true,
|
||||
messages: {}
|
||||
})
|
||||
|
||||
export async function setupI18n() {
|
||||
const messages = await loadAllLocaleMessages(SUPPORTED_LOCALES)
|
||||
|
||||
Object.keys(messages).forEach(locale => {
|
||||
i18n.global.setLocaleMessage(locale, messages[locale])
|
||||
})
|
||||
|
||||
return i18n
|
||||
}
|
||||
|
||||
export default i18n
|
||||
42
storefront/src/core/rules/textFieldRules.js
Normal file
42
storefront/src/core/rules/textFieldRules.js
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
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 }
|
||||
155
storefront/src/graphql/mutations/auth.js
Normal file
155
storefront/src/graphql/mutations/auth.js
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
import gql from 'graphql-tag'
|
||||
|
||||
export const REGISTER = gql`
|
||||
mutation register(
|
||||
$firstName: String!,
|
||||
$lastName: String!,
|
||||
$email: String!,
|
||||
$phoneNumber: String!,
|
||||
$password: String!,
|
||||
$confirmPassword: String!
|
||||
) {
|
||||
createUser(
|
||||
firstName: $firstName,
|
||||
lastName: $lastName,
|
||||
email: $email,
|
||||
phoneNumber: $phoneNumber,
|
||||
password: $password,
|
||||
confirmPassword: $confirmPassword
|
||||
) {
|
||||
success
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const LOGIN = gql`
|
||||
mutation login(
|
||||
$email: String!,
|
||||
$password: String!
|
||||
) {
|
||||
obtainJwtToken(
|
||||
email: $email,
|
||||
password: $password
|
||||
) {
|
||||
accessToken
|
||||
refreshToken
|
||||
user {
|
||||
avatar
|
||||
uuid
|
||||
attributes
|
||||
language
|
||||
email
|
||||
firstName
|
||||
lastName
|
||||
phoneNumber
|
||||
balance {
|
||||
amount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const REFRESH = gql`
|
||||
mutation refresh(
|
||||
$refreshToken: String!
|
||||
) {
|
||||
refreshJwtToken(
|
||||
refreshToken: $refreshToken
|
||||
) {
|
||||
accessToken
|
||||
refreshToken
|
||||
user {
|
||||
avatar
|
||||
uuid
|
||||
attributes
|
||||
language
|
||||
email
|
||||
firstName
|
||||
lastName
|
||||
phoneNumber
|
||||
balance {
|
||||
amount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const ACTIVATE_USER = gql`
|
||||
mutation activateUser(
|
||||
$token: String!,
|
||||
$uid: String!
|
||||
) {
|
||||
activateUser(
|
||||
token: $token,
|
||||
uid: $uid
|
||||
) {
|
||||
success
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const UPDATE_USER = gql`
|
||||
mutation updateUser(
|
||||
$firstName: String,
|
||||
$lastName: String,
|
||||
$email: String,
|
||||
$phoneNumber: String,
|
||||
$password: String,
|
||||
$confirmPassword: String,
|
||||
) {
|
||||
updateUser(
|
||||
firstName: $firstName,
|
||||
lastName: $lastName,
|
||||
email: $email,
|
||||
phoneNumber: $phoneNumber,
|
||||
password: $password,
|
||||
confirmPassword: $confirmPassword,
|
||||
) {
|
||||
user {
|
||||
avatar
|
||||
uuid
|
||||
attributes
|
||||
language
|
||||
email
|
||||
firstName
|
||||
lastName
|
||||
phoneNumber
|
||||
balance {
|
||||
amount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const RESET_PASSWORD = gql`
|
||||
mutation resetPassword(
|
||||
$email: String!,
|
||||
) {
|
||||
resetPassword(
|
||||
email: $email,
|
||||
) {
|
||||
success
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const CONFIRM_RESET_PASSWORD = gql`
|
||||
mutation confirmResetPassword(
|
||||
$password: String!,
|
||||
$confirmPassword: String!,
|
||||
$token: String!,
|
||||
$uid: String!,
|
||||
) {
|
||||
confirmResetPassword(
|
||||
password: $password,
|
||||
confirmPassword: $confirmPassword,
|
||||
token: $token,
|
||||
uid: $uid
|
||||
) {
|
||||
success
|
||||
}
|
||||
}
|
||||
`
|
||||
267
storefront/src/graphql/mutations/cart.js
Normal file
267
storefront/src/graphql/mutations/cart.js
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
import gql from 'graphql-tag'
|
||||
|
||||
export const ADD_TO_CART = gql`
|
||||
mutation addToCart(
|
||||
$orderUuid: String!,
|
||||
$productUuid: String!
|
||||
) {
|
||||
addOrderProduct(
|
||||
orderUuid: $orderUuid,
|
||||
productUuid: $productUuid
|
||||
) {
|
||||
order {
|
||||
status
|
||||
uuid
|
||||
totalPrice
|
||||
orderProducts {
|
||||
edges {
|
||||
node {
|
||||
uuid
|
||||
notifications
|
||||
attributes
|
||||
quantity
|
||||
status
|
||||
product {
|
||||
uuid
|
||||
price
|
||||
name
|
||||
description
|
||||
quantity
|
||||
slug
|
||||
category {
|
||||
name
|
||||
}
|
||||
images {
|
||||
edges {
|
||||
node {
|
||||
uuid
|
||||
image
|
||||
}
|
||||
}
|
||||
}
|
||||
category {
|
||||
name
|
||||
}
|
||||
attributeGroups {
|
||||
edges {
|
||||
node {
|
||||
name
|
||||
uuid
|
||||
attributes {
|
||||
name
|
||||
uuid
|
||||
values {
|
||||
value
|
||||
uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const REMOVE_FROM_CART = gql`
|
||||
mutation removeFromCart(
|
||||
$orderUuid: String!,
|
||||
$productUuid: String!
|
||||
) {
|
||||
removeOrderProduct(
|
||||
orderUuid: $orderUuid,
|
||||
productUuid: $productUuid
|
||||
) {
|
||||
order {
|
||||
status
|
||||
uuid
|
||||
totalPrice
|
||||
orderProducts {
|
||||
edges {
|
||||
node {
|
||||
uuid
|
||||
notifications
|
||||
attributes
|
||||
quantity
|
||||
status
|
||||
product {
|
||||
uuid
|
||||
price
|
||||
name
|
||||
description
|
||||
quantity
|
||||
slug
|
||||
category {
|
||||
name
|
||||
}
|
||||
images {
|
||||
edges {
|
||||
node {
|
||||
uuid
|
||||
image
|
||||
}
|
||||
}
|
||||
}
|
||||
category {
|
||||
name
|
||||
}
|
||||
attributeGroups {
|
||||
edges {
|
||||
node {
|
||||
name
|
||||
uuid
|
||||
attributes {
|
||||
name
|
||||
uuid
|
||||
values {
|
||||
value
|
||||
uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const REMOVE_KIND_FROM_CART = gql`
|
||||
mutation removeKindFromCart(
|
||||
$orderUuid: String!,
|
||||
$productUuid: String!
|
||||
) {
|
||||
removeOrderProductsOfAKind(
|
||||
orderUuid: $orderUuid,
|
||||
productUuid: $productUuid
|
||||
) {
|
||||
order {
|
||||
status
|
||||
uuid
|
||||
totalPrice
|
||||
orderProducts {
|
||||
edges {
|
||||
node {
|
||||
uuid
|
||||
notifications
|
||||
attributes
|
||||
quantity
|
||||
status
|
||||
product {
|
||||
uuid
|
||||
price
|
||||
name
|
||||
description
|
||||
quantity
|
||||
slug
|
||||
category {
|
||||
name
|
||||
}
|
||||
images {
|
||||
edges {
|
||||
node {
|
||||
uuid
|
||||
image
|
||||
}
|
||||
}
|
||||
}
|
||||
category {
|
||||
name
|
||||
}
|
||||
attributeGroups {
|
||||
edges {
|
||||
node {
|
||||
name
|
||||
uuid
|
||||
attributes {
|
||||
name
|
||||
uuid
|
||||
values {
|
||||
value
|
||||
uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const REMOVE_ALL_FROM_CART = gql`
|
||||
mutation removeAllFromCart(
|
||||
$orderUuid: String!
|
||||
) {
|
||||
removeAllOrderProducts(
|
||||
orderUuid: $orderUuid
|
||||
) {
|
||||
order {
|
||||
status
|
||||
uuid
|
||||
totalPrice
|
||||
orderProducts {
|
||||
edges {
|
||||
node {
|
||||
uuid
|
||||
notifications
|
||||
attributes
|
||||
quantity
|
||||
status
|
||||
product {
|
||||
uuid
|
||||
price
|
||||
name
|
||||
description
|
||||
quantity
|
||||
slug
|
||||
category {
|
||||
name
|
||||
}
|
||||
images {
|
||||
edges {
|
||||
node {
|
||||
uuid
|
||||
image
|
||||
}
|
||||
}
|
||||
}
|
||||
category {
|
||||
name
|
||||
}
|
||||
attributeGroups {
|
||||
edges {
|
||||
node {
|
||||
name
|
||||
uuid
|
||||
attributes {
|
||||
name
|
||||
uuid
|
||||
values {
|
||||
value
|
||||
uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
20
storefront/src/graphql/mutations/contact.js
Normal file
20
storefront/src/graphql/mutations/contact.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import gql from 'graphql-tag'
|
||||
|
||||
export const CONTACT_US = gql`
|
||||
mutation contactUs(
|
||||
$email: String!,
|
||||
$name: String!,
|
||||
$subject: String!,
|
||||
$message: String!,
|
||||
) {
|
||||
contactUs(
|
||||
email: $email,
|
||||
name: $name,
|
||||
subject: $subject,
|
||||
message: $message
|
||||
) {
|
||||
error
|
||||
received
|
||||
}
|
||||
}
|
||||
`
|
||||
14
storefront/src/graphql/mutations/deposit.js
Normal file
14
storefront/src/graphql/mutations/deposit.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import gql from 'graphql-tag'
|
||||
|
||||
export const DEPOSIT = gql`
|
||||
mutation deposit(
|
||||
$amount: Number!
|
||||
) {
|
||||
contactUs(
|
||||
amount: $amount,
|
||||
) {
|
||||
error
|
||||
received
|
||||
}
|
||||
}
|
||||
`
|
||||
138
storefront/src/graphql/mutations/wishlist.js
Normal file
138
storefront/src/graphql/mutations/wishlist.js
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
import gql from 'graphql-tag'
|
||||
|
||||
export const ADD_TO_WISHLIST = gql`
|
||||
mutation addToWishlist(
|
||||
$wishlistUuid: String!,
|
||||
$productUuid: String!
|
||||
) {
|
||||
addWishlistProduct(
|
||||
wishlistUuid: $wishlistUuid,
|
||||
productUuid: $productUuid
|
||||
) {
|
||||
wishlist {
|
||||
uuid
|
||||
products {
|
||||
edges {
|
||||
node {
|
||||
uuid
|
||||
price
|
||||
name
|
||||
description
|
||||
quantity
|
||||
slug
|
||||
images {
|
||||
edges {
|
||||
node {
|
||||
uuid
|
||||
image
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const REMOVE_FROM_WISHLIST = gql`
|
||||
mutation removeFromWishlist(
|
||||
$wishlistUuid: String!,
|
||||
$productUuid: String!
|
||||
) {
|
||||
removeWishlistProduct(
|
||||
wishlistUuid: $wishlistUuid,
|
||||
productUuid: $productUuid
|
||||
) {
|
||||
wishlist {
|
||||
uuid
|
||||
products {
|
||||
edges {
|
||||
node {
|
||||
uuid
|
||||
price
|
||||
name
|
||||
description
|
||||
quantity
|
||||
slug
|
||||
images {
|
||||
edges {
|
||||
node {
|
||||
uuid
|
||||
image
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const REMOVE_ALL_FROM_WISHLIST = gql`
|
||||
mutation removeAllFromCart(
|
||||
$wishlistUuid: String!
|
||||
) {
|
||||
removeAllWishlistProducts(
|
||||
wishlistUuid: $wishlistUuid
|
||||
) {
|
||||
order {
|
||||
status
|
||||
uuid
|
||||
totalPrice
|
||||
orderProducts {
|
||||
edges {
|
||||
node {
|
||||
uuid
|
||||
notifications
|
||||
attributes
|
||||
quantity
|
||||
status
|
||||
product {
|
||||
uuid
|
||||
price
|
||||
name
|
||||
description
|
||||
quantity
|
||||
slug
|
||||
category {
|
||||
name
|
||||
}
|
||||
images {
|
||||
edges {
|
||||
node {
|
||||
uuid
|
||||
image
|
||||
}
|
||||
}
|
||||
}
|
||||
category {
|
||||
name
|
||||
}
|
||||
attributeGroups {
|
||||
edges {
|
||||
node {
|
||||
name
|
||||
uuid
|
||||
attributes {
|
||||
name
|
||||
uuid
|
||||
values {
|
||||
value
|
||||
uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
45
storefront/src/graphql/queries/categories.js
Normal file
45
storefront/src/graphql/queries/categories.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import gql from 'graphql-tag'
|
||||
|
||||
export const GET_CATEGORIES = gql`
|
||||
query getCategories {
|
||||
categories {
|
||||
edges {
|
||||
node {
|
||||
name
|
||||
uuid
|
||||
image
|
||||
description
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const GET_CATEGORY_BY_SLUG = gql`
|
||||
query getCategoryBySlug(
|
||||
$slug: String!
|
||||
) {
|
||||
categories(
|
||||
slug: $slug
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
name
|
||||
uuid
|
||||
image
|
||||
description
|
||||
slug
|
||||
filterableAttributes {
|
||||
possibleValues
|
||||
attributeName
|
||||
}
|
||||
minMaxPrices {
|
||||
maxPrice
|
||||
minPrice
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
14
storefront/src/graphql/queries/company.js
Normal file
14
storefront/src/graphql/queries/company.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import gql from 'graphql-tag'
|
||||
|
||||
export const GET_COMPANY_INFO = gql`
|
||||
query getCompanyInfo {
|
||||
parameters {
|
||||
companyAddress
|
||||
companyName
|
||||
companyPhoneNumber
|
||||
emailFrom
|
||||
emailHostUser
|
||||
projectName
|
||||
}
|
||||
}
|
||||
`
|
||||
17
storefront/src/graphql/queries/docs.js
Normal file
17
storefront/src/graphql/queries/docs.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import gql from 'graphql-tag'
|
||||
|
||||
export const GET_DOCS = gql`
|
||||
query getDocs(
|
||||
$slug: String!
|
||||
) {
|
||||
posts(
|
||||
slug: $slug
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
11
storefront/src/graphql/queries/languages.js
Normal file
11
storefront/src/graphql/queries/languages.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import gql from 'graphql-tag'
|
||||
|
||||
export const GET_LANGUAGES = gql`
|
||||
query getLanguages {
|
||||
languages {
|
||||
code
|
||||
flag
|
||||
name
|
||||
}
|
||||
}
|
||||
`
|
||||
74
storefront/src/graphql/queries/orders.js
Normal file
74
storefront/src/graphql/queries/orders.js
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import gql from 'graphql-tag'
|
||||
|
||||
export const GET_ORDERS = gql`
|
||||
query getOrders(
|
||||
$status: String!,
|
||||
$userEmail: String!
|
||||
) {
|
||||
orders(
|
||||
status: $status,
|
||||
orderBy: "-buyTime",
|
||||
userEmail: $userEmail
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
totalPrice
|
||||
uuid
|
||||
status
|
||||
buyTime
|
||||
totalPrice
|
||||
humanReadableId
|
||||
orderProducts {
|
||||
edges {
|
||||
node {
|
||||
uuid
|
||||
notifications
|
||||
attributes
|
||||
quantity
|
||||
status
|
||||
product {
|
||||
uuid
|
||||
price
|
||||
name
|
||||
description
|
||||
quantity
|
||||
slug
|
||||
category {
|
||||
name
|
||||
}
|
||||
images {
|
||||
edges {
|
||||
node {
|
||||
uuid
|
||||
image
|
||||
}
|
||||
}
|
||||
}
|
||||
category {
|
||||
name
|
||||
}
|
||||
attributeGroups {
|
||||
edges {
|
||||
node {
|
||||
name
|
||||
uuid
|
||||
attributes {
|
||||
name
|
||||
uuid
|
||||
values {
|
||||
value
|
||||
uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
116
storefront/src/graphql/queries/products.js
Normal file
116
storefront/src/graphql/queries/products.js
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import gql from 'graphql-tag'
|
||||
|
||||
export const GET_PRODUCTS = gql`
|
||||
query getProducts(
|
||||
$after: String,
|
||||
$first: Number,
|
||||
$categorySlugs: String,
|
||||
$orderBy: String,
|
||||
$minPrice: String,
|
||||
$maxPrice: String,
|
||||
$name: String
|
||||
) {
|
||||
products(
|
||||
after: $after,
|
||||
first: $first,
|
||||
categorySlugs: $categorySlugs,
|
||||
orderBy: $orderBy,
|
||||
minPrice: $minPrice,
|
||||
maxPrice: $maxPrice,
|
||||
name: $name
|
||||
) {
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
uuid
|
||||
name
|
||||
price
|
||||
quantity
|
||||
slug
|
||||
images {
|
||||
edges {
|
||||
node {
|
||||
image
|
||||
}
|
||||
}
|
||||
}
|
||||
attributeGroups {
|
||||
edges {
|
||||
node {
|
||||
name
|
||||
uuid
|
||||
attributes {
|
||||
name
|
||||
uuid
|
||||
values {
|
||||
value
|
||||
uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const GET_PRODUCT_BY_SLUG = gql`
|
||||
query getProductBySlug(
|
||||
$slug: String!
|
||||
) {
|
||||
products(
|
||||
slug: $slug
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
uuid
|
||||
name
|
||||
price
|
||||
quantity
|
||||
description
|
||||
slug
|
||||
category {
|
||||
name
|
||||
slug
|
||||
}
|
||||
images {
|
||||
edges {
|
||||
node {
|
||||
image
|
||||
}
|
||||
}
|
||||
}
|
||||
attributeGroups {
|
||||
edges {
|
||||
node {
|
||||
name
|
||||
uuid
|
||||
attributes {
|
||||
name
|
||||
uuid
|
||||
values {
|
||||
value
|
||||
uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
feedbacks {
|
||||
edges {
|
||||
node {
|
||||
rating
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
48
storefront/src/graphql/queries/wishlist.js
Normal file
48
storefront/src/graphql/queries/wishlist.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import gql from 'graphql-tag'
|
||||
|
||||
export const GET_WISHLIST = gql`
|
||||
query getWishlist {
|
||||
wishlists {
|
||||
edges {
|
||||
node {
|
||||
uuid
|
||||
products {
|
||||
edges {
|
||||
node {
|
||||
uuid
|
||||
price
|
||||
name
|
||||
description
|
||||
slug
|
||||
images {
|
||||
edges {
|
||||
node {
|
||||
uuid
|
||||
image
|
||||
}
|
||||
}
|
||||
}
|
||||
attributeGroups {
|
||||
edges {
|
||||
node {
|
||||
name
|
||||
uuid
|
||||
attributes {
|
||||
name
|
||||
uuid
|
||||
values {
|
||||
value
|
||||
uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
58
storefront/src/locales/en-gb.json
Normal file
58
storefront/src/locales/en-gb.json
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"buttons": {
|
||||
"signIn": "Sign In",
|
||||
"signUp": "Sign Up",
|
||||
"addToCart": "Add To Cart",
|
||||
"send": "Send",
|
||||
"goEmail": "Take me to my inbox",
|
||||
"logout": "Log Out",
|
||||
"buy": "Buy Now",
|
||||
"save": "Save"
|
||||
},
|
||||
"errors": {
|
||||
"required": "This field is required!",
|
||||
"mail": "Email must be valid!",
|
||||
"compare": "Passwords don't match!",
|
||||
"needLower": "Please include lowercase letter.",
|
||||
"needUpper": "Please include uppercase letter.",
|
||||
"needNumber": "Please include number.",
|
||||
"needMin": "Min. 8 characters",
|
||||
"needSpecial": "Please include a special character: #.?!$%^&*'()_+=:;\"'/>.<,|\\-",
|
||||
"pageNotFound": "Page not found"
|
||||
},
|
||||
"fields": {
|
||||
"search": "Search Cards",
|
||||
"firstName": "First name",
|
||||
"lastName": "Last name",
|
||||
"phone": "Phone number",
|
||||
"email": "Email",
|
||||
"message": "Your message",
|
||||
"password": "Password",
|
||||
"newPassword": "New password",
|
||||
"confirmPassword": "Confirm password",
|
||||
"confirmNewPassword": "Confirm new password"
|
||||
},
|
||||
"popup": {
|
||||
"error": "Error!",
|
||||
"login": {
|
||||
"title": "Wellcome back!",
|
||||
"text": "Sign in successes"
|
||||
},
|
||||
"register": {
|
||||
"title": "Welcome!",
|
||||
"text": "Account successfully created. Please confirm your Email before Sign In!"
|
||||
},
|
||||
"addToCart": "{product} has been added to the cart!",
|
||||
"addToCartLimit": "Total quantity limit is {quantity}!",
|
||||
"failAdd": "Please log in to make a purchase",
|
||||
"contactSuccess": "Your message was sent successfully!",
|
||||
"activationSuccess": "E-mail successfully verified. Please Sign In!",
|
||||
"successUpdate": "Profile successfully updated!",
|
||||
"confirmEmail": "Verification E-mail link successfully sent!",
|
||||
"payment": "Your purchase is being processed! Please stand by",
|
||||
"reset": "If specified email exists in our system, we will send a password recovery email!",
|
||||
"successNewPassword": "You have successfully changed your password!",
|
||||
"successCheckout": "Order purchase successful!",
|
||||
"addToWishlist": "{product} has been added to the wishlist!"
|
||||
}
|
||||
}
|
||||
32
storefront/src/main.js
Normal file
32
storefront/src/main.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import '@/assets/styles/global/fonts.scss'
|
||||
import '@/assets/styles/main.scss'
|
||||
import {createApp, h, provide} from 'vue'
|
||||
import { DefaultApolloClient } from '@vue/apollo-composable'
|
||||
import { createApolloClient } from './apollo'
|
||||
import { createPinia } from 'pinia'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import {setupI18n} from "@/core/plugins/i18n.config.js";
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
|
||||
const pinia = createPinia()
|
||||
const i18n = await setupI18n()
|
||||
|
||||
const app = createApp({
|
||||
setup() {
|
||||
const apolloClient = createApolloClient()
|
||||
|
||||
provide(DefaultApolloClient, apolloClient)
|
||||
},
|
||||
render: () => h(App)
|
||||
})
|
||||
|
||||
app
|
||||
.use(pinia)
|
||||
.use(i18n)
|
||||
.use(router)
|
||||
.use(ElementPlus)
|
||||
|
||||
app.mount('#app')
|
||||
13
storefront/src/pages/home-page.vue
Normal file
13
storefront/src/pages/home-page.vue
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<template>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
41
storefront/src/router/index.js
Normal file
41
storefront/src/router/index.js
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import {createRouter, createWebHistory, RouterView} from 'vue-router'
|
||||
import HomePage from "@/pages/home-page.vue";
|
||||
import translation from "@/core/helpers/translations.js";
|
||||
import {APP_NAME} from "@/config/index.js";
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/:locale?',
|
||||
component: RouterView,
|
||||
beforeEnter: translation.routeMiddleware,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'home',
|
||||
component: HomePage,
|
||||
meta: {
|
||||
title: "Home"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes,
|
||||
scrollBehavior() {
|
||||
document.querySelector('#top').scrollIntoView({ behavior: 'smooth' })
|
||||
return {
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
router.beforeEach((to, from) => {
|
||||
document.title = to.meta.title ? `${APP_NAME} | ` + to.meta?.title : APP_NAME
|
||||
})
|
||||
|
||||
export default router
|
||||
14
storefront/src/stores/auth.js
Normal file
14
storefront/src/stores/auth.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import {defineStore} from "pinia";
|
||||
import {ref} from "vue";
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const user = ref(null)
|
||||
const accessToken = ref(null)
|
||||
|
||||
const setUser = (payload) => {
|
||||
user.value = payload.user
|
||||
accessToken.value = payload.accessToken
|
||||
}
|
||||
|
||||
return { user, accessToken, setUser }
|
||||
})
|
||||
14
storefront/src/stores/cart.js
Normal file
14
storefront/src/stores/cart.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import {defineStore} from "pinia";
|
||||
import {ref} from "vue";
|
||||
|
||||
export const useCartStore = defineStore('cart', () => {
|
||||
const currentOrder = ref({})
|
||||
const setCurrentOrders = (order) => {
|
||||
currentOrder.value = order
|
||||
}
|
||||
|
||||
return {
|
||||
currentOrder,
|
||||
setCurrentOrders
|
||||
}
|
||||
})
|
||||
14
storefront/src/stores/company.js
Normal file
14
storefront/src/stores/company.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useCompanyStore = defineStore('company', () => {
|
||||
const companyInfo = ref([])
|
||||
const setCompanyInfo = (payload) => {
|
||||
companyInfo.value = payload
|
||||
}
|
||||
|
||||
return {
|
||||
companyInfo,
|
||||
setCompanyInfo
|
||||
}
|
||||
})
|
||||
14
storefront/src/stores/wishlist.js
Normal file
14
storefront/src/stores/wishlist.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useWishlistStore = defineStore('wishlist', () => {
|
||||
const wishlist = ref({})
|
||||
const setWishlist = (order) => {
|
||||
wishlist.value = order
|
||||
}
|
||||
|
||||
return {
|
||||
wishlist,
|
||||
setWishlist
|
||||
}
|
||||
})
|
||||
37
storefront/vite.config.js
Normal file
37
storefront/vite.config.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { fileURLToPath, URL } from 'node:url'
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
vueDevTools(),
|
||||
],
|
||||
envDir: '../',
|
||||
envPrefix: 'EVIBES_',
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
'@core': fileURLToPath(new URL('./src/core', import.meta.url)),
|
||||
'@graphql': fileURLToPath(new URL('./src/graphql', import.meta.url)),
|
||||
'@styles': fileURLToPath(new URL('./src/assets/styles', import.meta.url)),
|
||||
'@icons': fileURLToPath(new URL('./src/assets/icons', import.meta.url)),
|
||||
'@images': fileURLToPath(new URL('./src/assets/images', import.meta.url)),
|
||||
},
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData: `
|
||||
@use "@/assets/styles/global/variables.scss" as *;
|
||||
@use "@/assets/styles/global/mixins.scss" as *;
|
||||
`
|
||||
}
|
||||
}
|
||||
},
|
||||
build: {
|
||||
sourcemap: true,
|
||||
target: 'ES2022',
|
||||
}
|
||||
})
|
||||
Loading…
Reference in a new issue