Login, register & password recovery

This commit is contained in:
Juan Ferrer 2020-05-06 18:44:42 +02:00
parent 703d6932ff
commit 85a2b667c2
35 changed files with 8635 additions and 2451 deletions

View File

@ -1,13 +0,0 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

View File

@ -1,3 +0,0 @@
{
"extends": "loopback"
}

View File

@ -0,0 +1,18 @@
{
"name": "MainAccountBank",
"base": "PersistedModel",
"options": {
"mysql": {
"table": "hedera.mainAccountBank"
}
},
"properties": {
"name": {
"type": "String",
"id": true
},
"iban": {
"type": "String"
}
}
}

View File

@ -0,0 +1,79 @@
module.exports = Self => {
Self.remoteMethod('checkoutInfo', {
description: 'Get the checkout information for an order',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'Number',
description: 'The order id'
}
],
returns: {
type: 'Object',
description: 'The checkout information',
root: true,
},
http: {
path: `/:id/checkoutInfo`,
verb: 'GET'
}
});
Self.checkoutInfo = async id => {
let $ = Self.app.models;
let order = await Self.findById(id, {
fields: ['taxableBase', 'tax', 'total', 'clientFk']
});
let {credit} = await $.Client.findById(order.clientFk, {
fields: ['credit']
});
let [{debt}] = await queryP(
`SELECT vn.clientGetDebt(?, CURDATE()) debt`,
[order.clientFk]
);
let bank = await $.MainAccountBank.findOne();
return {order, credit, debt, bank};
};
Self.remoteMethod('confirm', {
description: 'Confirms an order',
accessType: 'WRITE',
accepts: [
{
arg: 'id',
type: 'Number',
description: 'The order id'
}
],
returns: {
type: 'Boolean',
description: 'The confirm result',
root: true,
},
http: {
path: `/:id/confirm`,
verb: 'POST'
}
});
Self.confirm = async id => {
await queryP(`CALL hedera.order_confirm(?)`, [id]);
return true;
};
async function queryP(sql, params) {
return new Promise((resolve, reject) => {
Self.dataSource.connector.query(sql, params, (err, res) => {
if (err) return reject(err);
return resolve(res);
})
});
}
};

View File

@ -0,0 +1,46 @@
const app = require('../../server/server');
const loopback = require('loopback');
const path = require('path');
const config = {
proto: 'http',
host: 'localhost',
port: 3000,
from: 'nocontestar@verdnatura.es'
// app.dataSources.email.settings.transports[0].auth.user
};
module.exports = function (Self) {
const hostBase = `${config.proto}://${config.host}`
const urlBase = `${hostBase}:8080`;
const apiBase = `${hostBase}:3000/api`;
Self.afterRemote('create', async function(ctx, instance) {
const options = {
type: 'email',
to: instance.email,
from: config.from,
subject: 'Thanks for registering',
template: path.resolve(__dirname, '../../views/verify.ejs'),
redirect: `${urlBase}/#/login?emailConfirmed`,
user: Self
};
const res = await instance.verify(options);
console.log('> verification email sent:', res);
});
Self.on('resetPasswordRequest', async function(info) {
const renderer = loopback.template(path.resolve(__dirname, '../../views/reset-password.ejs'));
const html = renderer({
url: `${urlBase}/#/reset-password?access_token=${info.accessToken.id}`
});
await app.models.Email.send({
to: info.email,
from: config.from,
subject: 'Password reset',
html
});
console.log('> sending password reset email to:', info.email);
});
};

View File

@ -0,0 +1,19 @@
{
"name": "user",
"base": "User",
"idInjection": true,
"properties": {},
"restrictResetPasswordTokenScope": true,
"emailVerificationRequired": true,
"validations": [],
"relations": {},
"acls": [
{
"principalType": "ROLE",
"principalId": "$everyone",
"accessType": "READ",
"permission": "ALLOW"
}
],
"methods": []
}

1002
back/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,16 +14,16 @@
"compression": "^1.0.3",
"cors": "^2.5.2",
"fs-extra": "^8.1.0",
"helmet": "^3.19.0",
"loopback": "^3.26.0",
"loopback-boot": "^2.6.5",
"loopback-component-explorer": "^6.2.0",
"helmet": "^3.22.0",
"loopback": "^3.27.0",
"loopback-boot": "^2.28.0",
"loopback-component-explorer": "^6.5.1",
"loopback-component-storage": "^3.6.2",
"loopback-connector-mysql": "^5.4.1",
"loopback-connector-mysql": "^5.4.3",
"md5": "^2.2.1",
"serve-favicon": "^2.0.1",
"sharp": "^0.22.1",
"strong-error-handler": "^3.0.0"
"strong-error-handler": "^3.4.0"
},
"devDependencies": {
"eslint": "^3.17.1",

View File

@ -47,12 +47,21 @@
},
"User": {
"dataSource": "vn",
"public": false
},
"user": {
"dataSource": "db",
"public": true,
"options": {
"mysql": {
"table": "salix.user"
}
},
"emailVerificationRequired": true
}
},
"Email": {
"dataSource": "email"
},
"Account": {
"dataSource": "vn"
},
@ -110,6 +119,9 @@
"Link": {
"dataSource": "vn"
},
"MainAccountBank": {
"dataSource": "vn"
},
"New": {
"dataSource": "vn"
},

View File

@ -0,0 +1,3 @@
<p>
Click <a href="<%=url%>">here</a> to reset your password
</p>

6
back/views/verify.ejs Normal file
View File

@ -0,0 +1,6 @@
<p>
Thanks for registering,
</p>
<p>
Click <a href="<%=verifyHref%>">here</a> to confirm your email address.
</p>

8924
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,13 +14,13 @@
"back": "nodemon --inspect --watch back back/server/server.js"
},
"dependencies": {
"@quasar/extras": "^1.2.0",
"@quasar/extras": "^1.7.0",
"axios": "^0.18.1",
"quasar": "^1.0.5",
"quasar": "^1.10.5",
"vue-i18n": "^7.3.3"
},
"devDependencies": {
"@quasar/app": "^1.0.4",
"@quasar/app": "^1.8.4",
"@vue/eslint-config-standard": "^4.0.0",
"babel-eslint": "^10.0.1",
"eslint": "^5.16.0",

View File

@ -8,7 +8,8 @@ module.exports = function (ctx) {
'i18n',
'axios',
'filters',
'state'
'state',
'error-handler'
],
css: [
@ -42,6 +43,7 @@ module.exports = function (ctx) {
'QDrawer',
'QEditor',
'QExpansionItem',
'QForm',
'QHeader',
'QIcon',
'QImg',
@ -111,7 +113,10 @@ module.exports = function (ctx) {
devServer: {
// https: true,
// port: 8080,
open: true // opens browser window automatically
open: true, // opens browser window automatically
proxy: {
'/api': 'http://localhost:3000'
}
},
// animations: 'all' --- includes all animations

9
src-pwa/pwa-flag.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
// THIS FEATURE-FLAG FILE IS AUTOGENERATED,
// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
import "quasar/dist/types/feature-flag";
declare module "quasar/dist/types/feature-flag" {
interface QuasarFeatureFlags {
pwa: true;
}
}

View File

@ -1,6 +1,8 @@
<template>
<div id="q-app">
<router-view />
<transition name="slide-right">
<router-view class="child-view" />
</transition>
</div>
</template>

View File

@ -1,7 +1,15 @@
import axios from 'axios'
export default async ({ Vue }) => {
Vue.prototype.$apiBase = `//${location.hostname}:3000/api/`
export default async ({ app, Vue }) => {
Vue.prototype.$apiBase = `//${location.hostname}:${location.port}/api/`
Vue.prototype.$axios = axios
axios.defaults.baseURL = Vue.prototype.$apiBase
axios.interceptors.request.use(function (config) {
const $state = Vue.prototype.$state
if ($state.user.loggedIn) {
config.headers.Authorization = $state.user.token
}
return config
})
}

41
src/boot/error-handler.js Normal file
View File

@ -0,0 +1,41 @@
export default async ({ app, Vue }) => {
/*
window.onunhandledrejection = function (event) {
if (event.reason.response.data.error) {
errorHandler(event.reason)
event.preventDefault()
}
}
*/
Vue.config.errorHandler = (err, vm, info) => {
errorHandler(err, vm)
}
function errorHandler (err, vm) {
let message
const res = err.response
if (res) {
const status = res.status
if (status === 403) {
vm.$router.push('/login')
}
if (status >= 400 && status < 500) {
message = res.data.error.message
} else if (status >= 500) {
message = vm.$t('internalServerError')
}
} else {
message = vm.$t('somethingWentWrong')
console.error(err)
}
vm.$q.notify({
message: message,
type: 'negative'
})
}
}

View File

@ -3,8 +3,9 @@ export default async ({ app, Vue }) => {
Vue.prototype.$imageBase = '//verdnatura.es/vn-image-data'
let state = Vue.observable({
userId: 1437,
userName: null,
user: {
loggedIn: false
},
layout: null,
title: null,
subtitle: null,

View File

@ -1,5 +1,26 @@
// app global css
a
text-decoration none
color inherit
&:focus
outline 1px solid rgba(0, 0, 0, .1)
.link
color #44629e
&:active
color #44629e
.slide-left-leave-active, .slide-right-enter
transform translate(-30px, 0)
opacity 0
.slide-left-enter, .slide-right-leave-active
transform translate(30px, 0)
opacity 0
.child-view
position absolute
transition all .15s ease-out
.vn-w-md
width 30em
.vn-w-lg

View File

@ -24,6 +24,8 @@ export default {
tomorrow: 'Tomorrow',
fromDate: 'From',
toDate: 'To',
return: 'Return',
send: 'Send',
date: {
days: [
'Sunday',
@ -83,6 +85,26 @@ export default {
email: 'E-Mail',
password: 'Password',
remember: 'Don not close session',
notRememberPassword: 'I don\'t remember my password',
inputEmail: 'Input email',
inputPassword: 'Input password',
// register
register: 'Register',
fillData: 'Fill the data',
notYetUser: 'You are not yet a user?',
userRegistered: 'User registered successfully',
repeatPasswordError: 'Passwords doesn\'t match',
// recover
rememberPassword: 'Rememeber password',
dontWorry: 'Don\'t worry!',
weSendEmail: 'We will sent you an email to recover your password',
weHaveSentEmailToRecover: 'We\'ve sent you an email where you can recover your password',
// reset
resetPassword: 'Reset password',
passwordResetSuccessfully: 'Password changed successfully',
// menu
home: 'Home',
@ -105,7 +127,6 @@ export default {
user: 'User',
addresses: 'Addresses',
addressEdit: 'Edit address',
register: 'Register',
// home
recentNews: 'Recent news',
@ -264,11 +285,5 @@ export default {
province: 'Province',
country: 'Country',
phone: 'Phone',
mobile: 'Mobile',
// register
registerAsNew: 'Registrarse como nuevo usuario',
notYetUser: 'You are not yet a user, register now and start enjoying everything that Verdnatura offers you.',
receiveOffers: 'Receive offers and promotions by e-mail',
userRegistered: 'User registered successfully'
mobile: 'Mobile'
}

View File

@ -24,6 +24,8 @@ export default {
tomorrow: 'Mañana',
fromDate: 'Desde',
toDate: 'Hasta',
return: 'Volver',
send: 'Enviar',
date: {
days: [
'Domingo',
@ -83,6 +85,26 @@ export default {
email: 'Correo electrónico',
password: 'Contraseña',
remember: 'No cerrar sesión',
notRememberPassword: 'No recuerdo mi contraseña',
inputEmail: 'Introduce el correo electrónico',
inputPassword: 'Introduce la contraseña',
// register
register: 'Registrarse',
fillData: 'Rellena los datos',
notYetUser: '¿Todavía no eres usuario?',
userRegistered: 'Usuario registrado correctamente',
repeatPasswordError: 'Las contraseñas no coinciden',
// recover
rememberPassword: 'Recordar contraseña',
dontWorry: '¡No te preocupes!',
weSendEmail: 'Te enviaremos un correo para restablecer tu contraseña',
weHaveSentEmailToRecover: 'Te hemos enviado un correo donde podrás recuperar tu contraseña',
// reset
resetPassword: 'Restaurar contraseña',
passwordResetSuccessfully: 'Contraseña modificada correctamente',
// menu
home: 'Inicio',
@ -105,7 +127,6 @@ export default {
user: 'Usuario',
addresses: 'Direcciones',
addressEdit: 'Editar dirección',
register: 'Registrarse',
// home
recentNews: 'Noticias recientes',
@ -264,11 +285,5 @@ export default {
province: 'Provincia',
country: 'País',
phone: 'Teléfono',
mobile: 'Móvil',
// register
registerAsNew: 'Registrarse como nuevo usuario',
notYetUser: '¿Todavía no eres usuari@?, registrate y empieza a disfrutar de todo lo que Verdnatura puede ofrecerte.',
receiveOffers: 'Recibir ofertas y promociones por correo electrónico',
userRegistered: 'Usuario registrado correctamente'
mobile: 'Móvil'
}

View File

@ -7,7 +7,7 @@
<meta name="description" content="<%= htmlWebpackPlugin.options.productDescription %>">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (htmlWebpackPlugin.options.ctx.mode.cordova) { %>, viewport-fit=cover<% } %>">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (htmlWebpackPlugin.options.ctx.mode.cordova || htmlWebpackPlugin.options.ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>">
<link rel="icon" href="statics/logo.png" type="image/x-icon">
<link rel="icon" type="image/png" sizes="32x32" href="statics/icons/favicon-32x32.png">

View File

@ -1,65 +0,0 @@
<template>
<div class="login fullscreen row justify-center items-center bg-primary">
<q-card class="q-pa-md">
<q-card-section class="q-mb-lg">
<img src="statics/logo.svg" alt="Logo" />
</q-card-section>
<q-card-section class="q-gutter-md">
<q-input v-model="email" :label="$t('email')" />
<q-input v-model="password" :label="$t('password')" :type="showPwd ? 'password' : 'text'">
<template v-slot:append>
<q-icon
:name="showPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="showPwd = !showPwd"
/>
</template>
</q-input>
</q-card-section>
<q-card-section class="q-gutter-md">
<q-checkbox v-model="remember" :label="$t('remember')" />
</q-card-section>
<q-card-actions class="justify-center">
<q-btn flat :label="$t('register')" to="/register" />
<q-btn flat :label="$t('enter')" @click="login()" />
</q-card-actions>
</q-card>
</div>
</template>
<style lang="stylus" scoped>
.login
position: fixed;
.q-card
width: 320px;
</style>
<script>
export default {
name: 'Login',
data () {
return {
email: '',
password: '',
remember: false,
showPwd: true
}
},
methods: {
login () {
let params = {
username: this.email,
password: this.password
}
this.$axios.post('users/login', params)
.then(res => {
localStorage.setItem('token', res.data.id)
this.$router.push('home')
})
.catch(res => {
this.$q.notify(res.response.data.error.message)
})
}
}
}
</script>

View File

@ -0,0 +1,25 @@
<template>
<q-layout id="bg" class="fullscreen row justify-center items-center layout-view scroll bg-primary">
<q-card class="q-pa-md row items-center justify-center">
<transition name="slide-right">
<router-view class="child-view"/>
</transition>
</q-card>
</q-layout>
</template>
<style lang="stylus" scoped>
.q-card
border-radius 0
width 600px
height 100%
min-height 580px
.q-card > *
width 280px
</style>
<script>
export default {
name: 'LoginLayout'
}
</script>

View File

@ -19,7 +19,7 @@
</div>
</q-toolbar-title>
<q-btn flat
v-if="!$state.userId"
v-if="!$state.user.loggedIn"
class="q-ml-md"
:label="$t('login')"
to="/login"
@ -41,11 +41,32 @@
behavior="mobile"
elevated
overlay>
<div class="q-pa-md shadow-1 q-mb-md bg-grey-10" style="color: white;">
<img src="statics/logo-dark.svg" alt="Verdnatura" class="logo q-mb-md"/>
<div class="row items-center full-width justify-between">
<span class="text-subtitle1">{{$t('visitor')}}</span>
<q-btn flat round dense icon="exit_to_app" :title="$t('logout')" @click="logout()" />
<div class="q-pa-md shadow-1 q-mb-md bg-grey-9 text-white">
<img
src="statics/logo-dark.svg"
alt="Verdnatura"
class="logo q-mb-md"
style="width: 80%"
/>
<div class="text-subtitle1">
<div
v-if="$state.user.loggedIn"
class="row items-center full-width justify-between">
<span>{{$state.user.name}}</span>
<q-btn
icon="exit_to_app"
:title="$t('logout')"
@click="onLogout"
flat
dense
round
/>
</div>
<div v-else>
<router-link to="/login">
{{$t('login')}}
</router-link>
</div>
</div>
</div>
<q-list
@ -141,8 +162,11 @@ export default {
},
methods: {
openURL,
logout () {
onLogout () {
localStorage.removeItem('token')
Object.assign(this.$state.user, {
loggedIn: false
})
this.$router.push('/login')
}
}

102
src/pages/Login.vue Normal file
View File

@ -0,0 +1,102 @@
<template>
<div>
<q-card-section>
<router-link to="/" class="block">
<img
src="statics/banner.svg"
alt="Verdnatura"
class="block full-width"
/>
</router-link>
</q-card-section>
<q-card-section>
<q-form @submit="onLogin" class="q-gutter-y-md">
<div class="q-gutter-y-sm">
<q-input
v-model="email"
:label="$t('email')"
:rules="[ val => !!val || $t('inputEmail')]"
filled
/>
<q-input
v-model="password"
:label="$t('password')"
:type="showPwd ? 'password' : 'text'"
:rules="[ val => !!val || $t('inputPassword')]"
filled>
<template v-slot:append>
<q-icon
:name="showPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="showPwd = !showPwd"
/>
</template>
</q-input>
<q-checkbox
v-model="remember"
:label="$t('remember')"
dense
/>
</div>
<div class="justify-center">
<q-btn
type="submit"
:label="$t('enter')"
class="full-width"
color="primary"
/>
<div class="text-center q-mt-xs">
<router-link to="/remember-password" class="link">
{{$t('notRememberPassword')}}
</router-link>
</div>
</div>
</q-form>
</q-card-section>
<q-card-section>
<div class="text-center q-mb-xs text-grey-8">
{{$t('notYetUser')}}
</div>
<q-btn
:label="$t('register')"
to="/register"
class="full-width"
color="grey-9"
/>
</q-card-section>
</div>
</template>
<style lang="stylus" scoped>
</style>
<script>
export default {
name: 'Login',
data () {
return {
email: '',
password: '',
remember: false,
showPwd: true
}
},
methods: {
async onLogin () {
const params = {
username: this.email,
password: this.password
}
const res = await this.$axios.post('users/login', params)
localStorage.setItem('token', res.data.id)
Object.assign(this.$state.user, {
loggedIn: true,
token: res.data.id,
id: res.data.userId,
name: this.email
})
this.$router.push('/home')
}
}
}
</script>

View File

@ -11,18 +11,18 @@
<tbody>
<tr>
<td>{{$t('previousBalance')}}</td>
<td class="text-right">{{data.debt | currency}}</td>
<td class="text-right">{{debt | currency}}</td>
</tr>
</tbody>
<tbody
class="border">
<tr>
<td>{{$t('orderTotal')}}</td>
<td class="text-right">{{data.total | currency}}</td>
<td class="text-right">{{order.taxableBase | currency}}</td>
</tr>
<tr>
<td>{{$t('vat')}}</td>
<td class="text-right">{{data.vat | currency}}</td>
<td class="text-right">{{order.tax | currency}}</td>
</tr>
</tbody>
<tbody
@ -33,7 +33,7 @@
</tr>
<tr v-if="hasCredit">
<td class="text-left">{{$t('credit')}}</td>
<td class="text-right">{{data.credit | currency}}</td>
<td class="text-right">{{credit | currency}}</td>
</tr>
</tbody>
<tbody
@ -93,10 +93,10 @@
{{$t('makeTransfer')}}
</div>
<div class="q-mt-sm">
{{bankName}}
{{bank.name}}
</div>
<div>
{{bankAccount}}
{{bank.iban | iban}}
</div>
</div>
</div>
@ -154,28 +154,24 @@ export default {
mixins: [Page],
data () {
return {
data: {
debt: -27.52,
total: 163.13,
vat: 16.31,
credit: 50
},
order: {},
credit: 0,
debt: 0,
bank: {},
payAmount: null,
payMethod: null,
bankName: 'BANKINTER SA',
bankAccount: 'ES81 0128 7635 0205 0000 2291'
payMethod: null
}
},
computed: {
totalDebt () {
let data = this.data
return data.debt + data.total + data.vat
let order = this.order
return this.debt + order.taxableBase + order.tax
},
execeededCredit () {
return this.totalDebt - this.data.credit
return this.totalDebt - this.credit
},
hasCredit () {
return this.data.credit > 0
return this.credit > 0
},
isCreditExceeded () {
return this.hasCredit && this.execeededCredit > 0
@ -188,27 +184,43 @@ export default {
}
},
mounted () {
this.payAmount = this.isCreditExceeded ? 'exceeded' : 'total'
if (!this.hasDebt) {
this.payMethod = 'balance'
} else if (this.canUseCredit) {
this.payMethod = 'credit'
} else {
this.payMethod = 'card'
this.$axios.get(`Orders/${this.$route.params.id}/checkoutInfo`)
.then(res => this.onData(res.data))
},
filters: {
iban (val) {
if ((typeof val) !== 'string') return val
return val.match(/.{1,4}/g).join(' ')
}
},
methods: {
onData (data) {
this.order = data.order
this.credit = data.credit
this.debt = data.debt
this.bank = data.bank
this.payAmount = this.isCreditExceeded ? 'exceeded' : 'total'
if (!this.hasDebt) {
this.payMethod = 'balance'
} else if (this.canUseCredit) {
this.payMethod = 'credit'
} else {
this.payMethod = 'card'
}
},
onConfirm () {
this.$q.dialog({
message: this.$t('doYouWantToConfirm'),
cancel: true
}).onOk(() => {
this.$q.dialog({
message: this.$t('orderConfirmedSuccessfully')
})
// this.$axios.delete(`Orders/${this.$route.params.id}/confirm`)
// .then(() => {})
this.$axios.post(`Orders/${this.$route.params.id}/confirm`)
.then(() => {
this.$q.dialog({
message: this.$t('orderConfirmedSuccessfully')
})
})
})
}
}

View File

@ -1,30 +1,72 @@
<template>
<div class="q-pa-md row justify-center q-gutter-md">
<q-card>
<q-card-section>
<div class="text-h4">{{$t('registerAsNew')}}</div>
</q-card-section>
<q-card-section>
{{$t('notYetUser')}}
</q-card-section>
<q-card-section class="q-gutter-md">
<q-input v-model="email" :label="$t('email')" />
<q-input v-model="password" :label="$t('password')" type="password" />
<q-input v-model="repeatPassword" :label="$t('repeatPassword')" type="password" />
</q-card-section>
<q-card-section class="q-gutter-md">
<q-checkbox v-model="receiveOffers" :label="$t('receiveOffers')" />
</q-card-section>
<q-card-actions class="justify-center">
<q-btn flat :label="$t('register')" @click="onRegister"/>
</q-card-actions>
</q-card>
<div>
<q-card-section>
<q-icon
name="nature_people"
class="block q-mx-auto text-accent"
style="font-size: 120px;"
/>
</q-card-section>
<q-card-section>
<q-form @submit="onRegister" ref="form" class="q-gutter-y-md">
<div class="text-grey-8 text-h5 text-center">
{{$t('fillData')}}
</div>
<div class="q-gutter-y-sm">
<q-input
v-model="email"
:label="$t('email')"
hint=""
filled
/>
<q-input
v-model="password"
:label="$t('password')"
:type="showPwd ? 'password' : 'text'"
hint=""
filled>
<template v-slot:append>
<q-icon
:name="showPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="showPwd = !showPwd"
/>
</template>
</q-input>
<q-input
v-model="repeatPassword"
:label="$t('repeatPassword')"
:type="showRpPwd ? 'password' : 'text'"
:rules="[value => value == password || $t('repeatPasswordError')]"
hint=""
filled>
<template v-slot:append>
<q-icon
:name="showRpPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="showRpPwd = !showRpPwd"
/>
</template>
</q-input>
</div>
<div>
<q-btn
type="submit"
:label="$t('register')"
class="full-width"
color="primary"
/>
<div class="text-center q-mt-xs">
<router-link to="/login" class="link">
{{$t('return')}}
</router-link>
</div>
</div>
</q-form>
</q-card-section>
</div>
</template>
<style lang="stylus" scoped>
</style>
<script>
export default {
name: 'Register',
@ -33,12 +75,21 @@ export default {
email: '',
password: '',
repeatPassword: '',
receiveOffers: false
showPwd: true,
showRpPwd: true
}
},
methods: {
onRegister: function () {
this.$q.notify(this.$t('userRegistered'))
async onRegister () {
await this.$axios.post('users', {
email: this.email,
password: this.password
})
this.$q.notify({
message: this.$t('userRegistered'),
type: 'positive'
})
this.$router.push('/login')
}
}
}

View File

@ -0,0 +1,74 @@
<template>
<div class="text-center">
<q-card-section>
<q-icon
name="contact_support"
class="block q-mx-auto text-accent"
style="font-size: 120px;"
/>
</q-card-section>
<q-card-section>
<q-form @submit="onSend" class="q-gutter-y-md text-grey-8">
<div class="text-h5">
<div>
{{$t('dontWorry')}}
</div>
<div>
{{$t('fillData')}}
</div>
</div>
<q-input
v-model="email"
:label="$t('email')"
:rules="[ val => !!val || $t('inputEmail')]"
filled
/>
<div>
{{$t('weSendEmail')}}
</div>
<div>
<q-btn
type="submit"
:label="$t('send')"
class="full-width"
color="black"
/>
<div class="text-center q-mt-xs">
<router-link to="/login" class="link">
{{$t('return')}}
</router-link>
</div>
</div>
</q-form>
</q-card-section>
</div>
</template>
<style lang="stylus" scoped>
#image
height 190px
</style>
<script>
export default {
name: 'RememberPasword',
data () {
return {
email: ''
}
},
methods: {
async onSend () {
const params = {
email: this.email
}
await this.$axios.post('users/reset', params)
this.$q.notify({
message: this.$t('weHaveSentEmailToRecover'),
type: 'positive'
})
this.$router.push('/login')
}
}
}
</script>

View File

@ -0,0 +1,92 @@
<template>
<div>
<q-card-section>
<q-icon
name="check"
class="block q-mx-auto text-accent"
style="font-size: 120px;"
/>
</q-card-section>
<q-card-section>
<q-form @submit="onRegister" ref="form" class="q-gutter-y-md">
<div class="text-grey-8 text-h5 text-center">
{{$t('fillData')}}
</div>
<div class="q-gutter-y-sm">
<q-input
v-model="password"
:label="$t('password')"
:type="showPwd ? 'password' : 'text'"
hint=""
filled>
<template v-slot:append>
<q-icon
:name="showPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="showPwd = !showPwd"
/>
</template>
</q-input>
<q-input
v-model="repeatPassword"
:label="$t('repeatPassword')"
:type="showRpPwd ? 'password' : 'text'"
:rules="[value => value == password || $t('repeatPasswordError')]"
hint=""
filled>
<template v-slot:append>
<q-icon
:name="showRpPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="showRpPwd = !showRpPwd"
/>
</template>
</q-input>
</div>
<div>
<q-btn
type="submit"
:label="$t('resetPassword')"
class="full-width"
color="primary"
/>
<div class="text-center q-mt-xs">
<router-link to="/login" class="link">
{{$t('return')}}
</router-link>
</div>
</div>
</q-form>
</q-card-section>
</div>
</template>
<script>
export default {
name: 'Register',
data () {
return {
password: '',
repeatPassword: '',
showPwd: true,
showRpPwd: true
}
},
methods: {
async onRegister () {
const headers = {
Authorization: this.$route.query.access_token
}
await this.$axios.post(`users/reset-password`, {
newPassword: this.password
}, { headers })
this.$q.notify({
message: this.$t('passwordResetSuccessfully'),
type: 'positive'
})
this.$router.push('/login')
}
}
}
</script>

View File

@ -1,8 +1,30 @@
const routes = [
{
path: '/login',
component: () => import('layouts/LoginLayout.vue'),
children: [
{
name: 'login',
path: '',
component: () => import('pages/Login.vue')
}, {
name: 'register',
path: '/register',
component: () => import('pages/Register.vue')
}, {
name: 'rememberPassword',
path: '/remember-password',
component: () => import('pages/RememberPassword.vue')
}, {
name: 'resetPassword',
path: '/reset-password',
component: () => import('pages/ResetPassword.vue')
}
]
}, {
path: '/',
component: () => import('layouts/MyLayout.vue'),
component: () => import('layouts/MainLayout.vue'),
children: [
{
name: '',
@ -116,16 +138,8 @@ const routes = [
name: 'addressEdit',
path: '/address/:id?',
component: () => import('pages/Address.vue')
}, {
name: 'register',
path: '/register',
component: () => import('pages/Register.vue')
}
]
}, {
name: 'login',
path: '/login',
component: () => import('layouts/Login.vue')
}
]

101
src/statics/banner.svg Normal file
View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:ns="http://www.w3.org/2000/svg"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Capa_1"
x="0px"
y="0px"
width="279"
height="80.577003"
viewBox="0 0 279 80.577004"
enable-background="new 0 0 315 91"
xml:space="preserve"
inkscape:version="0.91 r13725"
sodipodi:docname="banner.svg"><defs
id="defs71" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="0"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1016"
id="namedview69"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="3.9555556"
inkscape:cx="70.89371"
inkscape:cy="17.671808"
inkscape:window-x="1920"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="Capa_1"
showborder="false"
inkscape:showpageshadow="false" /><metadata
id="metadata3"><ns:sfw><ns:slices /><ns:sliceSourceBounds
height="92.88"
width="291.36"
y="-92.691"
x="11.469"
bottomLeftOrigin="true" /></ns:sfw><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><path
style="fill:#3e3d3d"
inkscape:connector-curvature="0"
id="path13"
d="m 279,75.352 c 0,2.874 -2.351,5.225 -5.225,5.225 l -268.551,0 C 2.351,80.577 0,78.226 0,75.352 L 0,5.224 C 0,2.351 2.351,0 5.224,0 L 273.775,0 C 276.649,0 279,2.351 279,5.224 l 0,70.128 z" /><path
d="m 38.12,53.065 -10.226,-29.708 8.08,0 4.182,15.251 0.303,1.229 c 0.544,1.868 0.911,3.371 1.169,4.62 0.146,-0.596 0.347,-1.3 0.56,-2.165 0.214,-0.797 0.521,-1.879 0.891,-3.178 l 4.385,-15.757 8.021,0 -10.262,29.708 -7.103,0 z"
id="path41"
inkscape:connector-curvature="0"
style="clip-rule:evenodd;fill:#ffffff;fill-rule:evenodd" /><path
d="m 245.89,53.065 -7.322,0 c 0.029,-0.359 0.068,-0.751 0.13,-1.163 l 0.095,-1.152 c -1.248,1.042 -2.559,1.737 -3.862,2.304 -1.211,0.431 -2.587,0.648 -3.912,0.645 -2.143,0 -3.605,-0.572 -4.486,-1.736 -0.922,-1.161 -1.13,-2.66 -0.552,-4.681 0.521,-1.806 1.43,-3.252 2.627,-4.412 1.339,-1.152 2.985,-1.914 5.084,-2.385 1.145,-0.216 2.575,-0.505 4.315,-0.79 2.578,-0.412 3.963,-1.012 4.165,-1.744 l 0.203,-0.568 c 0.128,-0.603 0.067,-1.107 -0.323,-1.447 -0.432,-0.297 -1.041,-0.471 -1.97,-0.471 -1.096,0 -1.96,0.183 -2.607,0.614 -0.786,0.437 -1.313,1.013 -1.668,1.872 l -6.645,0 c 1.127,-2.486 2.606,-4.326 4.62,-5.551 2.042,-1.228 4.621,-1.818 7.808,-1.818 1.97,0 3.468,0.241 4.694,0.729 1.224,0.431 2.064,1.156 2.518,2.125 0.339,0.669 0.543,1.484 0.468,2.449 -0.069,0.921 -0.402,2.479 -0.996,4.543 l -2.323,8.303 c -0.273,1.011 -0.338,1.743 -0.314,2.313 0,0.572 0.148,0.977 0.461,1.157 l -0.208,0.864 z m -5.028,-10.341 c -0.687,0.339 -1.7,0.674 -3.18,0.972 -0.714,0.129 -1.288,0.231 -1.597,0.333 -0.948,0.259 -1.699,0.581 -2.102,0.919 -0.489,0.377 -0.794,0.885 -1.005,1.475 -0.191,0.858 -0.09,1.441 0.211,1.961 0.331,0.429 0.898,0.646 1.739,0.646 1.179,0 2.313,-0.3 3.271,-1.021 1.021,-0.727 1.652,-1.593 1.924,-2.774 l 0.739,-2.511 z"
id="path43"
inkscape:connector-curvature="0"
style="clip-rule:evenodd;fill:#a9d42d;fill-rule:evenodd" /><path
d="m 209.604,53.065 6.166,-21.935 6.877,0 -1.034,3.801 c 1.173,-1.461 2.378,-2.601 3.789,-3.278 1.293,-0.673 2.893,-1.065 4.624,-1.073 l -2.033,7.09 c -0.276,-0.015 -0.58,-0.064 -0.857,-0.09 -0.301,-0.029 -0.575,-0.041 -0.886,-0.041 -1.094,0 -2.003,0.171 -2.879,0.519 -0.781,0.332 -1.441,0.822 -2.072,1.492 -0.442,0.467 -0.824,1.152 -1.107,2.003 -0.384,0.844 -0.873,2.192 -1.414,4.119 l -2.049,7.393 -7.125,0 z"
id="path45"
inkscape:connector-curvature="0"
style="clip-rule:evenodd;fill:#a9d42d;fill-rule:evenodd" /><path
d="m 212.244,31.131 -6.163,21.935 -6.994,0 0.873,-3.06 c -1.181,1.248 -2.438,2.154 -3.723,2.735 -1.199,0.573 -2.556,0.857 -3.945,0.857 -2.455,0 -4.078,-0.639 -4.948,-1.915 -0.885,-1.226 -0.976,-3.044 -0.299,-5.543 l 4.235,-15.01 7.223,0 -3.465,12.243 c -0.55,1.812 -0.594,3.041 -0.344,3.712 0.235,0.722 0.925,1.061 2.067,1.061 1.165,0 2.261,-0.447 3.025,-1.253 0.772,-0.804 1.477,-2.174 1.977,-4.043 l 3.326,-11.72 7.155,0 z"
id="path47"
inkscape:connector-curvature="0"
style="clip-rule:evenodd;fill:#a9d42d;fill-rule:evenodd" /><path
d="m 174.604,35.464 1.238,-4.333 2.95,0 1.734,-6.179 7.156,0 -1.734,6.179 3.637,0 -1.195,4.333 -3.681,0 -2.671,9.478 c -0.338,1.474 -0.463,2.389 -0.235,2.923 0.235,0.436 0.903,0.647 1.965,0.647 l 0.568,-0.019 0.291,-0.043 -1.211,4.615 c -0.805,0.147 -1.613,0.289 -2.293,0.303 -0.819,0.109 -1.458,0.174 -2.173,0.169 -2.431,0 -4.006,-0.62 -4.575,-1.856 -0.574,-1.223 -0.307,-3.816 0.771,-7.753 l 2.406,-8.464 -2.948,0 z"
id="path49"
inkscape:connector-curvature="0"
style="clip-rule:evenodd;fill:#a9d42d;fill-rule:evenodd" /><path
d="m 169.694,53.065 -7.316,0 c 0.013,-0.359 0.049,-0.751 0.118,-1.163 l 0.164,-1.152 c -1.291,1.042 -2.595,1.737 -3.82,2.304 -1.318,0.427 -2.574,0.645 -3.98,0.645 -2.063,0 -3.601,-0.572 -4.528,-1.736 -0.92,-1.161 -1.045,-2.66 -0.505,-4.681 0.476,-1.806 1.38,-3.254 2.709,-4.412 1.204,-1.152 2.909,-1.913 4.959,-2.385 1.133,-0.216 2.58,-0.505 4.31,-0.79 2.591,-0.412 4.046,-1.012 4.285,-1.744 l 0.088,-0.568 c 0.247,-0.603 0.106,-1.107 -0.322,-1.447 -0.299,-0.297 -0.998,-0.471 -1.979,-0.471 -0.955,0 -1.825,0.183 -2.609,0.614 -0.648,0.437 -1.2,1.013 -1.593,1.872 l -6.576,0 c 0.983,-2.486 2.564,-4.326 4.619,-5.55 2.046,-1.229 4.618,-1.819 7.729,-1.819 1.903,0 3.544,0.243 4.736,0.729 1.156,0.431 2.019,1.156 2.534,2.125 0.347,0.669 0.419,1.484 0.353,2.449 -0.006,0.921 -0.287,2.479 -0.87,4.543 l -2.323,8.303 c -0.277,1.011 -0.456,1.743 -0.429,2.313 0.024,0.572 0.151,0.977 0.449,1.157 l -0.203,0.864 z m -5.003,-10.341 c -0.586,0.339 -1.735,0.673 -3.184,0.972 -0.671,0.129 -1.202,0.231 -1.614,0.333 -0.965,0.259 -1.574,0.581 -2.118,0.919 -0.407,0.377 -0.75,0.885 -0.894,1.475 -0.28,0.858 -0.188,1.441 0.183,1.956 0.383,0.434 0.958,0.65 1.722,0.65 1.252,0 2.327,-0.3 3.298,-1.021 0.936,-0.727 1.612,-1.593 1.947,-2.774 l 0.66,-2.51 z"
id="path51"
inkscape:connector-curvature="0"
style="clip-rule:evenodd;fill:#a9d42d;fill-rule:evenodd" /><path
d="m 122.357,23.357 -8.328,29.708 -7.168,0 0.806,-2.891 c -1.152,1.155 -2.318,2.024 -3.524,2.599 -1.242,0.581 -2.543,0.872 -3.961,0.872 -2.733,0 -4.651,-1.074 -5.782,-3.188 -1.087,-2.065 -1.183,-4.854 -0.208,-8.311 0.944,-3.456 2.619,-6.229 4.938,-8.413 2.313,-2.179 4.783,-3.292 7.493,-3.292 1.432,0 2.631,0.29 3.59,0.868 0.937,0.579 1.642,1.446 2.067,2.604 l 2.91,-10.556 7.167,0 z m -22.175,18.504 c -0.532,1.943 -0.607,3.464 -0.166,4.532 0.445,0.979 1.365,1.481 2.741,1.481 1.358,0 2.552,-0.489 3.531,-1.46 0.984,-1.09 1.782,-2.565 2.331,-4.554 0.499,-1.866 0.525,-3.28 0.065,-4.284 -0.436,-1.009 -1.329,-1.5 -2.712,-1.5 -1.262,0 -2.424,0.511 -3.476,1.558 -1.052,1.042 -1.779,2.437 -2.314,4.227"
id="path53"
inkscape:connector-curvature="0"
style="clip-rule:evenodd;fill:#ffffff;fill-rule:evenodd" /><path
d="m 73.414,53.065 6.114,-21.935 6.888,0 -1.041,3.801 c 1.192,-1.461 2.442,-2.601 3.788,-3.278 1.303,-0.679 5.873,-1.065 7.651,-1.073 l -2.013,7.09 c -0.289,-0.015 -0.577,-0.064 -0.862,-0.09 -0.3,-0.029 -0.586,-0.041 -0.868,-0.041 -1.109,0 -5.057,0.171 -5.869,0.516 -0.792,0.334 -1.501,0.823 -2.141,1.494 -0.384,0.467 -0.795,1.152 -1.112,2.003 -0.385,0.844 -0.833,2.192 -1.349,4.119 l -2.072,7.393 -7.114,0 z"
id="path55"
inkscape:connector-curvature="0"
style="clip-rule:evenodd;fill:#ffffff;fill-rule:evenodd" /><path
d="m 65.892,46.124 6.905,0 c -1.322,2.398 -3.138,4.312 -5.368,5.573 -2.254,1.368 -4.776,1.95 -7.595,1.95 -3.366,0 -5.792,-0.979 -7.182,-3.053 -1.368,-2.077 -1.596,-4.854 -0.606,-8.447 1.02,-3.609 2.843,-6.497 5.415,-8.598 2.574,-2.1 5.539,-3.171 8.913,-3.171 3.482,0 5.905,1.071 7.325,3.246 1.364,2.146 1.529,5.085 0.446,8.892 l -0.205,0.787 -0.178,0.475 -14.808,0 c -0.429,1.548 -0.429,2.702 0,3.504 0.438,0.865 1.308,1.23 2.599,1.23 0.965,0 1.789,-0.168 2.525,-0.615 0.738,-0.323 1.354,-0.933 1.814,-1.773 m -6.012,-6.393 8.186,-0.027 c 0.355,-1.388 0.293,-2.473 -0.205,-3.283 -0.53,-0.775 -1.392,-1.196 -2.637,-1.194 -1.188,-10e-4 -2.225,0.419 -3.146,1.167 -0.948,0.804 -1.675,1.894 -2.198,3.337"
id="path57"
inkscape:connector-curvature="0"
style="clip-rule:evenodd;fill:#ffffff;fill-rule:evenodd" /><path
d="m 138.36,53.354 -6.27,-18.261 -6.328,18.261 -7.092,0 9.373,-29.695 8.013,0 4.334,15.277 0.365,1.227 c 0.503,1.844 0.876,3.37 1.145,4.582 l 0.522,-2.128 c 0.244,-0.798 0.548,-1.897 0.925,-3.216 l 4.355,-15.741 8.021,0 -10.267,29.695 -7.096,0 z"
id="path59"
inkscape:connector-curvature="0"
style="fill:#a9d42d" /></svg>

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 23.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Capa_1"
x="0px"
y="0px"
viewBox="0 0 40 40"
xml:space="preserve"
sodipodi:docname="12.svg"
width="40"
height="40"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><metadata
id="metadata11"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs9" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1043"
id="namedview7"
showgrid="false"
inkscape:zoom="21.300925"
inkscape:cx="19.978779"
inkscape:cy="20.00345"
inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="Capa_1" />
<style
type="text/css"
id="style2">
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#E55C1F;}
.st1{fill:url(#SVGID_1_);}
.st2{fill:#FFFFFF;}
.st3{fill:#E55C1F;}
</style>
<path
class="st3"
d="M 33.944952,5.6404 C 26.038565,-2.0493 13.379013,-1.856 5.6626188,6.0737 -2.0504417,14.0001 -1.8937806,26.663 6.0126064,34.3527 13.918993,42.0424 26.578546,41.8491 34.294939,33.9194 42.008,25.993 41.851339,13.3301 33.944952,5.6404 Z M 32.861657,32.4428 C 26.005233,39.4892 14.755631,39.6625 7.7292124,32.8261 0.70279422,25.993 0.56613238,14.7401 7.4225566,7.6937 14.278981,0.6472 25.528583,0.4772 32.555001,7.3103 c 7.023085,6.8331 7.159747,18.0861 0.306656,25.1325 z M 5.5926213,23.1331 C 4.1326729,21.1132 5.3526298,17.25 5.9926071,16.8267 c 1.0966279,-0.7233 6.2097799,1.0699 7.2197449,1.5199 1.009964,0.4533 3.86653,1.1766 3.819865,2.2033 -0.05666,1.2199 -2.473246,1.7666 -4.733166,2.1899 -2.256587,0.43 -5.9231241,1.4766 -6.7064297,0.3933 z m 4.3698454,7.7431 C 7.6992134,30.1695 6.4292583,26.953 6.6825827,26.2697 c 0.433318,-1.17 5.3664773,-2.7033 6.3997733,-2.9333 1.029964,-0.23 3.629872,-1.2966 4.176519,-0.4866 0.646644,0.96 -1.086628,2.5832 -2.576575,4.1832 -1.493281,1.5999 -3.506543,4.2232 -4.7198333,3.8432 z M 15.828926,13.1001 C 14.822295,11.0835 13.015692,8.2003 13.828997,7.157 15.34561,5.2104 18.928816,5.1237 19.498796,5.6104 c 0.976632,0.8366 0.656644,6.1698 0.503316,7.2497 -0.156661,1.08 -0.07333,3.9832 -1.05663,4.2266 -1.169958,0.2899 -2.106592,-1.97 -3.116556,-3.9866 z m 5.189816,-1.0199 c 0.263324,-2.3233 0.319989,-5.8665 1.613277,-6.2831 2.416581,-0.78 5.583135,1.2532 5.803128,2.0066 0.376653,1.2932 -2.909897,5.6864 -3.656538,6.5231 -0.74664,0.8366 -2.309918,3.3798 -3.303216,3.0099 -1.179958,-0.44 -0.719975,-2.9333 -0.456651,-5.2565 z m -1.769937,16.8427 c -0.286657,1.9633 -0.429985,4.9565 -1.536612,5.3065 -2.063261,0.65 -4.683168,-1.08 -4.849829,-1.72 -0.283323,-1.0932 2.613241,-4.7964 3.266551,-5.4998 0.653311,-0.7066 2.043261,-2.8465 2.873232,-2.5299 0.986632,0.3734 0.529981,2.4799 0.246658,4.4432 z M 12.435713,17.2467 C 10.392452,16.3534 7.1858983,15.3034 7.1492329,13.9968 7.0825689,11.5635 9.7758067,9.2336 10.519114,9.2436 c 1.276621,0.017 4.396511,4.2665 4.959824,5.1865 0.563314,0.92 2.459913,3.0699 1.856601,3.8732 -0.709975,0.9566 -2.856565,-0.1633 -4.899826,-1.0566 z m 22.229213,1.4232 c 1.489948,1.9966 0.336655,5.7865 -0.666643,6.4098 -1.089961,0.6767 -5.599802,-1.4266 -6.616432,-1.8633 -1.016631,-0.4366 -3.883196,-1.1199 -3.849864,-2.1465 0.04,-1.22 2.509911,-1.5467 4.759831,-2.0033 2.253254,-0.4567 5.573137,-1.47 6.373108,-0.3967 z m -4.096521,-7.9263 c 2.223254,0.7166 3.499876,3.8765 3.259884,4.5398 -0.413318,1.1399 -5.226482,2.5832 -6.233113,2.7966 -1.006631,0.2133 -3.539874,1.2266 -4.083189,0.4299 -0.64331,-0.9466 1.036631,-2.5132 2.479913,-4.0631 1.443282,-1.5467 3.386547,-4.0899 4.576505,-3.7032 z m -2.296586,13.9295 c 1.936598,0.7499 4.079856,1.7532 3.936528,2.9599 -0.229992,1.9466 -2.446581,4.4431 -3.759867,4.4898 -1.186625,0.04 -3.579874,-4.2332 -4.009859,-5.1398 -0.429984,-0.9067 -1.973263,-3.0832 -1.339952,-3.7699 0.75664,-0.8166 3.083224,0.65 5.17315,1.46 z m -4.489841,3.5165 c 0.806638,1.7633 2.273253,4.2965 1.543279,5.1698 -1.359952,1.6266 -4.443176,1.5966 -4.919826,1.16 -0.813305,-0.7467 -0.38332,-5.3232 -0.216659,-6.2465 0.16666,-0.9266 0.179993,-3.4232 1.033296,-3.5998 1.016631,-0.2167 1.753272,1.7532 2.55991,3.5165 z"
id="path4"
inkscape:connector-curvature="0"
style="fill:#e55c1f;stroke-width:0.03333215" />
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

9
src/store/store-flag.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
// THIS FEATURE-FLAG FILE IS AUTOGENERATED,
// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
import "quasar/dist/types/feature-flag";
declare module "quasar/dist/types/feature-flag" {
interface QuasarFeatureFlags {
store: true;
}
}