refs #4922 password recovery, app store, error handler, fixes
gitea/hedera-web/pipeline/head This commit looks good
Details
gitea/hedera-web/pipeline/head This commit looks good
Details
This commit is contained in:
parent
0d0be4ee5f
commit
7e26aa773c
|
@ -58,8 +58,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"front": "webpack serve --open",
|
||||
"back": "cd ../salix && gulp backOnly",
|
||||
"db": "cd ../vn-database && myvc run -d",
|
||||
"back": "cd ../vn-database && myvc start && cd ../salix && gulp backOnly",
|
||||
"build": "rm -rf build/ ; webpack",
|
||||
"clean": "rm -rf build/",
|
||||
"lint": "eslint --ext .js,.vue ./"
|
||||
|
|
|
@ -26,7 +26,9 @@ module.exports = configure(function (ctx) {
|
|||
// https://v2.quasar.dev/quasar-cli-webpack/boot-files
|
||||
boot: [
|
||||
'i18n',
|
||||
'axios'
|
||||
'axios',
|
||||
'error-handler',
|
||||
'app'
|
||||
],
|
||||
|
||||
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-css
|
||||
|
@ -129,7 +131,9 @@ module.exports = configure(function (ctx) {
|
|||
// directives: [],
|
||||
|
||||
// Quasar plugins
|
||||
plugins: []
|
||||
plugins: [
|
||||
'Notify'
|
||||
]
|
||||
},
|
||||
|
||||
// animations: 'all', // --- includes all animations
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import { boot } from 'quasar/wrappers'
|
||||
import { appStore } from 'stores/app'
|
||||
|
||||
export default boot(({ app }) => {
|
||||
const myApp = appStore()
|
||||
app.config.globalProperties.$app = myApp
|
||||
})
|
|
@ -1,5 +1,6 @@
|
|||
import { boot } from 'quasar/wrappers'
|
||||
import { Connection } from '../js/db/connection'
|
||||
import { userStore } from 'stores/user'
|
||||
import axios from 'axios'
|
||||
|
||||
// Be careful when using SSR for cross-request state pollution
|
||||
|
@ -11,17 +12,20 @@ import axios from 'axios'
|
|||
const api = axios.create({
|
||||
baseURL: `//${location.hostname}:${location.port}/api/`
|
||||
})
|
||||
api.interceptors.request.use(function addToken (config) {
|
||||
const token = localStorage.getItem('vnToken')
|
||||
if (token) {
|
||||
config.headers.Authorization = token
|
||||
}
|
||||
return config
|
||||
})
|
||||
|
||||
const jApi = new Connection()
|
||||
|
||||
export default boot(({ app }) => {
|
||||
const user = userStore()
|
||||
function addToken (config) {
|
||||
if (user.token) {
|
||||
config.headers.Authorization = user.token
|
||||
}
|
||||
return config
|
||||
}
|
||||
api.interceptors.request.use(addToken)
|
||||
jApi.use(addToken)
|
||||
|
||||
// for use inside Vue files (Options API) through this.$axios and this.$api
|
||||
|
||||
app.config.globalProperties.$axios = axios
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
|
||||
export default async ({ app }) => {
|
||||
/*
|
||||
window.addEventListener('error',
|
||||
e => onWindowError(e));
|
||||
window.addEventListener('unhandledrejection',
|
||||
e => onWindowRejection(e));
|
||||
|
||||
,onWindowError(event) {
|
||||
errorHandler(event.error);
|
||||
}
|
||||
,onWindowRejection(event) {
|
||||
errorHandler(event.reason);
|
||||
}
|
||||
*/
|
||||
app.config.errorHandler = (err, vm, info) => {
|
||||
errorHandler(err, vm)
|
||||
}
|
||||
|
||||
function errorHandler (err, vm) {
|
||||
let message
|
||||
let tMessage
|
||||
const res = err.response
|
||||
|
||||
if (res) {
|
||||
const status = res.status
|
||||
|
||||
if (status >= 400 && status < 500) {
|
||||
switch (status) {
|
||||
case 401:
|
||||
tMessage = 'loginFailed'
|
||||
break
|
||||
case 403:
|
||||
tMessage = 'authenticationRequired'
|
||||
vm.$router.push('/login')
|
||||
break
|
||||
case 404:
|
||||
tMessage = 'notFound'
|
||||
break
|
||||
default:
|
||||
message = res.data.error.message
|
||||
}
|
||||
} else if (status >= 500) {
|
||||
tMessage = 'internalServerError'
|
||||
}
|
||||
} else {
|
||||
tMessage = 'somethingWentWrong'
|
||||
console.error(err)
|
||||
}
|
||||
|
||||
if (tMessage) {
|
||||
message = vm.$t(tMessage)
|
||||
}
|
||||
vm.$q.notify({
|
||||
message,
|
||||
type: 'negative'
|
||||
})
|
||||
}
|
||||
}
|
|
@ -4,8 +4,10 @@ import messages from 'src/i18n'
|
|||
|
||||
export default boot(({ app }) => {
|
||||
const i18n = createI18n({
|
||||
locale: 'en-US',
|
||||
locale: 'es-ES',
|
||||
globalInjection: true,
|
||||
silentTranslationWarn: true,
|
||||
silentFallbackWarn: true,
|
||||
messages
|
||||
})
|
||||
|
||||
|
|
|
@ -13,6 +13,14 @@ body {
|
|||
font-family: 'Poppins', 'Verdana', 'Sans';
|
||||
background-color: #fafafa;
|
||||
}
|
||||
a.link {
|
||||
text-decoration: none;
|
||||
color: #6a1;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
.q-card {
|
||||
border-radius: 7px;
|
||||
box-shadow: 0 0 3px rgba(0, 0, 0, .1);
|
||||
|
|
|
@ -3,5 +3,10 @@
|
|||
|
||||
export default {
|
||||
failed: 'Action failed',
|
||||
success: 'Action was successful'
|
||||
success: 'Action was successful',
|
||||
internalServerError: 'Internal server error',
|
||||
somethingWentWrong: 'Something went wrong',
|
||||
loginFailed: 'Login failed',
|
||||
authenticationRequired: 'Authentication required',
|
||||
notFound: 'Not found'
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
// This is just an example,
|
||||
// so you can safely delete all default props below
|
||||
|
||||
export default {
|
||||
failed: 'Acción fallida',
|
||||
success: 'Acción exitosa',
|
||||
internalServerError: 'Error interno del servidor',
|
||||
somethingWentWrong: 'Algo salió mal',
|
||||
loginFailed: 'Usuario o contraseña incorrectos',
|
||||
authenticationRequired: 'Autenticación requerida',
|
||||
notFound: 'No encontrado'
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
import enUS from './en-US'
|
||||
import esES from './es-ES'
|
||||
|
||||
export default {
|
||||
'en-US': enUS
|
||||
'en-US': enUS,
|
||||
'es-ES': esES
|
||||
}
|
||||
|
|
|
@ -9,6 +9,11 @@ export class JsonConnection extends VnObject {
|
|||
_connected = false
|
||||
_requestsCount = 0
|
||||
token = null
|
||||
interceptors = []
|
||||
|
||||
use (fn) {
|
||||
this.interceptors.push(fn)
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the specified REST service with the given params and calls
|
||||
|
@ -70,8 +75,9 @@ export class JsonConnection extends VnObject {
|
|||
const request = new XMLHttpRequest()
|
||||
request.open(config.method, config.url, true)
|
||||
|
||||
const token = localStorage.getItem('vnToken')
|
||||
if (token) { request.setRequestHeader('Authorization', token) }
|
||||
for (const fn of this.interceptors) {
|
||||
config = fn(config)
|
||||
}
|
||||
|
||||
const headers = config.headers
|
||||
if (headers) {
|
||||
|
|
|
@ -8,26 +8,22 @@
|
|||
round
|
||||
icon="menu"
|
||||
aria-label="Menu"
|
||||
@click="toggleLeftDrawer"
|
||||
/>
|
||||
|
||||
@click="toggleLeftDrawer"/>
|
||||
<q-toolbar-title>
|
||||
Home
|
||||
</q-toolbar-title>
|
||||
</q-toolbar>
|
||||
</q-header>
|
||||
|
||||
<q-drawer
|
||||
v-model="leftDrawerOpen"
|
||||
:width="250"
|
||||
show-if-above
|
||||
>
|
||||
show-if-above>
|
||||
<q-toolbar class="logo">
|
||||
<img src="statics/logo-dark.svg">
|
||||
</q-toolbar>
|
||||
<div class="user-info">
|
||||
<div>
|
||||
<span id="user-name">{{user.nickname}}</span>
|
||||
<span id="user-name">{{(user.nickname)}}</span>
|
||||
<q-btn flat icon="logout" alt="_Exit" @click="logout()"/>
|
||||
</div>
|
||||
<div id="supplant" class="supplant">
|
||||
|
@ -63,7 +59,6 @@
|
|||
</q-expansion-item>
|
||||
</q-list>
|
||||
</q-drawer>
|
||||
|
||||
<q-page-container>
|
||||
<router-view />
|
||||
</q-page-container>
|
||||
|
@ -156,6 +151,8 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
async mounted () {
|
||||
await this.user.loadData()
|
||||
await this.$app.loadConfig()
|
||||
await this.fetchData()
|
||||
},
|
||||
|
||||
|
@ -192,3 +189,10 @@ export default defineComponent({
|
|||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
en-US:
|
||||
visitor: Visitor
|
||||
es-ES:
|
||||
visitor: Visitante
|
||||
</i18n>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
v-for="myNew in news"
|
||||
:key="myNew.id">
|
||||
<q-card>
|
||||
<q-img :src="`https://verdnatura.es/vn-image-data/news/full/${myNew.image}`">
|
||||
<q-img :src="`${$app.imageUrl}/news/full/${myNew.image}`">
|
||||
</q-img>
|
||||
<q-card-section>
|
||||
<div class="text-h5">{{ myNew.title }}</div>
|
||||
|
@ -22,7 +22,7 @@
|
|||
fab
|
||||
icon="add_shopping_cart"
|
||||
color="accent"
|
||||
:to="{name: 'catalog'}"
|
||||
to="/catalog"
|
||||
:title="$t('startOrder')"
|
||||
/>
|
||||
</q-page-sticky>
|
||||
|
@ -33,27 +33,18 @@
|
|||
.new-card {
|
||||
width: 100%;
|
||||
|
||||
@media screen and (min-width: 1000px) and (max-width: 1399px) {
|
||||
@media screen and (min-width: 800px) and (max-width: 1400px) {
|
||||
width: 50%;
|
||||
}
|
||||
@media screen and (min-width: 1400px) and (max-width: 1699px) {
|
||||
@media screen and (min-width: 1401px) and (max-width: 1920px) {
|
||||
width: 33.33%;
|
||||
}
|
||||
@media screen and (min-width: 1700px) {
|
||||
@media screen and (min-width: 19021) {
|
||||
width: 25%;
|
||||
}
|
||||
}
|
||||
.new-body {
|
||||
font-family: 'Open Sans';
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #6a1;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<template>
|
||||
<div class="vn-pp row justify-center">
|
||||
<div
|
||||
v-if="orders && !orders.length"
|
||||
class="text-subtitle1 text-center text-grey-7 q-pa-md">
|
||||
{{$t('noOrdersFound')}}
|
||||
</div>
|
||||
<q-card
|
||||
v-if="orders && orders.length"
|
||||
class="vn-w-md">
|
||||
<q-list bordered separator>
|
||||
<q-item
|
||||
v-for="order in orders"
|
||||
:key="order.id"
|
||||
:to="`/order/${order.id}/`"
|
||||
clickable
|
||||
v-ripple>
|
||||
<q-item-section>
|
||||
<q-item-label>{{order.landed}}</q-item-label>
|
||||
<q-item-label caption>#{{order.id}}</q-item-label>
|
||||
<q-item-label caption>{{order.address.nickname}}</q-item-label>
|
||||
<q-item-label caption>{{order.address.city}}</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side top>
|
||||
{{order.taxableBase}}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-card>
|
||||
<q-page-sticky position="bottom-right" :offset="[18, 18]">
|
||||
<q-btn
|
||||
fab
|
||||
icon="add_shopping_cart"
|
||||
color="accent"
|
||||
to="/catalog"
|
||||
:title="$t('startOrder')"/>
|
||||
</q-page-sticky>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'OrdersPendingIndex',
|
||||
data () {
|
||||
return {
|
||||
orders: null
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
this.orders = await this.$jApi.query(
|
||||
'CALL myTicket_list(NULL, NULL)'
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
en-US:
|
||||
startOrder: Start order
|
||||
noOrdersFound: No orders found
|
||||
es-ES:
|
||||
startOrder: Empezar pedido
|
||||
noOrdersFound: No se encontrado pedidos
|
||||
</i18n>
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="fullscreen bg-blue text-white text-center q-pa-md flex flex-center">
|
||||
<div class="fullscreen bg-accent text-white text-center q-pa-md flex flex-center">
|
||||
<div>
|
||||
<div style="font-size: 30vh">
|
||||
404
|
||||
|
@ -12,7 +12,7 @@
|
|||
<q-btn
|
||||
class="q-mt-xl"
|
||||
color="white"
|
||||
text-color="blue"
|
||||
text-color="accent"
|
||||
unelevated
|
||||
to="/"
|
||||
label="Go Home"
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
</div>
|
||||
<div class="justify-center">
|
||||
<q-btn
|
||||
type="submit"
|
||||
to="/"
|
||||
:label="$t('logInAsGuest')"
|
||||
class="full-width"
|
||||
color="primary"
|
||||
|
@ -58,7 +58,7 @@
|
|||
outline
|
||||
/>
|
||||
</div>
|
||||
<p class="password-forgotten text-center">
|
||||
<p class="password-forgotten text-center q-mt-lg">
|
||||
<router-link to="/remember-password" class="link">
|
||||
{{$t('haveForgottenPassword')}}
|
||||
</router-link>
|
||||
|
@ -67,7 +67,9 @@
|
|||
<div class="footer text-center">
|
||||
<p>
|
||||
{{$t('notACustomerYet')}}
|
||||
<a href="//verdnatura.es/register/" target="_blank">{{$t('signUp')}}</a>
|
||||
<a href="//verdnatura.es/register/" target="_blank" class="link">
|
||||
{{$t('signUp')}}
|
||||
</a>
|
||||
</p>
|
||||
<p class="contact">
|
||||
{{$t('loginPhone')}} · {{$t('loginMail')}}
|
||||
|
@ -84,12 +86,7 @@ $login-margin-between: 55px;
|
|||
max-width: 280px;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
.header {
|
||||
margin-top: $login-margin-top;
|
||||
|
@ -110,7 +107,6 @@ a {
|
|||
}
|
||||
.password-forgotten {
|
||||
font-size: .8rem;
|
||||
margin-top: 30px;
|
||||
}
|
||||
.footer {
|
||||
margin-bottom: $login-margin-top;
|
||||
|
@ -159,7 +155,7 @@ export default {
|
|||
|
||||
methods: {
|
||||
async onLogin () {
|
||||
await this.user.login(this.email, this.password)
|
||||
await this.user.login(this.email, this.password, this.remember)
|
||||
this.$router.push('/')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<div class="text-center">
|
||||
<q-card-section>
|
||||
<div>
|
||||
<q-icon
|
||||
name="contact_support"
|
||||
class="block q-mx-auto text-accent"
|
||||
style="font-size: 120px;"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
</div>
|
||||
<div>
|
||||
<q-form @submit="onSend" class="q-gutter-y-md text-grey-8">
|
||||
<div class="text-h5">
|
||||
<div>
|
||||
|
@ -19,29 +19,31 @@
|
|||
</div>
|
||||
<q-input
|
||||
v-model="email"
|
||||
:label="$t('email')"
|
||||
:label="$t('user')"
|
||||
:rules="[ val => !!val || $t('inputEmail')]"
|
||||
autofocus
|
||||
filled
|
||||
/>
|
||||
<div>
|
||||
<div class="q-mt-lg">
|
||||
{{$t('weSendEmail')}}
|
||||
</div>
|
||||
<div>
|
||||
<q-btn
|
||||
type="submit"
|
||||
:label="$t('send')"
|
||||
class="full-width"
|
||||
color="black"
|
||||
class="full-width q-mt-md"
|
||||
color="primary"
|
||||
rounded
|
||||
no-caps
|
||||
unelevated
|
||||
/>
|
||||
<div class="text-center q-mt-xs">
|
||||
<div class="text-center q-mt-md">
|
||||
<router-link to="/login" class="link">
|
||||
{{$t('return')}}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</q-form>
|
||||
</q-card-section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -49,6 +51,13 @@
|
|||
#image {
|
||||
height: 190px;
|
||||
}
|
||||
.q-btn {
|
||||
height: 50px;
|
||||
}
|
||||
a {
|
||||
color: inherit;
|
||||
font-size: .8rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
@ -64,7 +73,7 @@ export default {
|
|||
const params = {
|
||||
email: this.email
|
||||
}
|
||||
await this.$axios.post('users/reset', params)
|
||||
await this.$axios.post('Users/reset', params)
|
||||
this.$q.notify({
|
||||
message: this.$t('weHaveSentEmailToRecover'),
|
||||
type: 'positive'
|
||||
|
@ -74,3 +83,26 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
en-US:
|
||||
user: User
|
||||
inputEmail: Input email
|
||||
rememberPassword: Rememeber password
|
||||
dontWorry: Don't worry!
|
||||
fillData: Fill the data
|
||||
weSendEmail: We will sent you an email to recover your password
|
||||
weHaveSentEmailToRecover: We've sent you an email where you can recover your password
|
||||
send: Send
|
||||
return: Return
|
||||
es-ES:
|
||||
user: Usuario
|
||||
inputEmail: Introduce el correo electrónico
|
||||
rememberPassword: Recordar contraseña
|
||||
dontWorry: ¡No te preocupes!
|
||||
fillData: Rellena los datos
|
||||
weSendEmail: Te enviaremos un correo para restablecer tu contraseña
|
||||
weHaveSentEmailToRecover: Te hemos enviado un correo donde podrás recuperar tu contraseña
|
||||
send: Enviar
|
||||
return: Volver
|
||||
</i18n>
|
||||
|
|
|
@ -25,11 +25,15 @@ const routes = [
|
|||
{
|
||||
name: '',
|
||||
path: '',
|
||||
component: () => import('pages/Home.vue')
|
||||
component: () => import('src/pages/Cms/Home.vue')
|
||||
}, {
|
||||
name: 'home',
|
||||
path: '/cms/home',
|
||||
component: () => import('pages/Home.vue')
|
||||
component: () => import('src/pages/Cms/Home.vue')
|
||||
}, {
|
||||
name: 'orders',
|
||||
path: '/ecomerce/orders',
|
||||
component: () => import('pages/Ecomerce/Orders.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { jApi } from 'boot/axios'
|
||||
|
||||
export const appStore = defineStore('hedera', {
|
||||
state: () => ({
|
||||
imageUrl: ''
|
||||
}),
|
||||
|
||||
actions: {
|
||||
async loadConfig () {
|
||||
const imageUrl = await jApi.getValue(
|
||||
'SELECT url FROM imageConfig'
|
||||
)
|
||||
this.$patch({ imageUrl })
|
||||
}
|
||||
}
|
||||
})
|
|
@ -1,15 +0,0 @@
|
|||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useCounterStore = defineStore('counter', {
|
||||
state: () => ({
|
||||
counter: 0
|
||||
}),
|
||||
getters: {
|
||||
doubleCount: (state) => state.counter * 2
|
||||
},
|
||||
actions: {
|
||||
increment () {
|
||||
this.counter++
|
||||
}
|
||||
}
|
||||
})
|
|
@ -2,41 +2,58 @@ import { defineStore } from 'pinia'
|
|||
import { api, jApi } from 'boot/axios'
|
||||
|
||||
export const userStore = defineStore('user', {
|
||||
state: () => ({
|
||||
token: null,
|
||||
state: () => {
|
||||
const token =
|
||||
localStorage.getItem('vnToken') ||
|
||||
sessionStorage.getItem('vnToken')
|
||||
|
||||
return {
|
||||
token,
|
||||
id: null,
|
||||
name: null,
|
||||
nickname: null
|
||||
}),
|
||||
}
|
||||
},
|
||||
|
||||
getters: {
|
||||
loggedIn: state => state.token != null
|
||||
},
|
||||
|
||||
actions: {
|
||||
async login (user, password) {
|
||||
async login (user, password, remember) {
|
||||
const params = { user, password }
|
||||
const res = await api.post('Accounts/login', params)
|
||||
localStorage.setItem('vnToken', res.data.token)
|
||||
|
||||
if (remember) {
|
||||
localStorage.setItem('vnToken', res.data.token)
|
||||
} else {
|
||||
sessionStorage.setItem('vnToken', res.data.token)
|
||||
}
|
||||
|
||||
this.$patch({
|
||||
token: res.data.token,
|
||||
name: user
|
||||
})
|
||||
},
|
||||
|
||||
async logout () {
|
||||
if (this.token != null) {
|
||||
await api.post('Accounts/logout')
|
||||
localStorage.removeItem('vnToken')
|
||||
sessionStorage.removeItem('vnToken')
|
||||
}
|
||||
this.$reset()
|
||||
},
|
||||
|
||||
async loadData () {
|
||||
const userData = await jApi.getObject(
|
||||
'SELECT id, nickname FROM account.myUser'
|
||||
)
|
||||
|
||||
this.$patch({
|
||||
token: res.data.token,
|
||||
name: user,
|
||||
id: userData.id,
|
||||
nickname: userData.nickname
|
||||
})
|
||||
},
|
||||
|
||||
async logout () {
|
||||
if (localStorage.getItem('vnToken') != null) {
|
||||
await api.post('Accounts/logout')
|
||||
}
|
||||
localStorage.removeItem('vnToken')
|
||||
this.$reset()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue