Replace old vee-validate version and rewrite the usages

This commit is contained in:
Ramon Wenger 2021-12-20 17:17:28 +01:00
parent 52caced8b0
commit 31f3145cbd
10 changed files with 295 additions and 519 deletions

View File

@ -270,3 +270,8 @@ Command:
``` ```
./bin/pg-backup-to-s3 ./bin/pg-backup-to-s3
``` ```
# Note on component
Our own components remain in kebap-case, imported components from third party libraries will be used in PascalCase.
E.g. `<password-change-form/>` vs. `<ValidationProvider/>`

View File

@ -2209,6 +2209,21 @@
} }
} }
}, },
"@vue/composition-api": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@vue/composition-api/-/composition-api-1.4.2.tgz",
"integrity": "sha512-EqybqmMq835GISvlQXdDaV8dsbunpdmhClrnAqUJZLyxxV9pQXQYRtNDf+0e+fEwMfimLIsv7YmbKCbqxGRqXg==",
"requires": {
"tslib": "^2.3.1"
},
"dependencies": {
"tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
}
}
},
"@vue/test-utils": { "@vue/test-utils": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-1.2.2.tgz", "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-1.2.2.tgz",
@ -19743,9 +19758,9 @@
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
}, },
"vee-validate": { "vee-validate": {
"version": "2.2.15", "version": "3.4.14",
"resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-2.2.15.tgz", "resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-3.4.14.tgz",
"integrity": "sha512-4TOsI8XwVkKVLkg8Nhmy+jyoJrR6XcTRDyxBarzcCvYzU61zamipS1WsB6FlDze8eJQpgglS4NXAS6o4NDPs1g==" "integrity": "sha512-Hqqic8G9WcRSIzCxiCPqMZv4qB8JE1lIQqIOLDm2K5BXUiL8d4a2+kqkanv8gQSGDzYpnCQZ7BO/T99Aj05T1Q=="
}, },
"vendors": { "vendors": {
"version": "1.0.4", "version": "1.0.4",

View File

@ -31,6 +31,7 @@
"@babel/preset-stage-2": "^7.0.0", "@babel/preset-stage-2": "^7.0.0",
"@babel/runtime": "^7.5.4", "@babel/runtime": "^7.5.4",
"@iam4x/cypress-graphql-mock": "0.0.1", "@iam4x/cypress-graphql-mock": "0.0.1",
"@vue/composition-api": "^1.4.2",
"apollo-cache-inmemory": "^1.6.5", "apollo-cache-inmemory": "^1.6.5",
"apollo-client": "^2.6.8", "apollo-client": "^2.6.8",
"apollo-link": "^1.2.13", "apollo-link": "^1.2.13",
@ -84,16 +85,16 @@
"uploadcare-widget": "^3.6.0", "uploadcare-widget": "^3.6.0",
"url-loader": "^1.0.1", "url-loader": "^1.0.1",
"uuid": "^3.2.1", "uuid": "^3.2.1",
"vee-validate": "^2.2.15", "vee-validate": "^3.4.14",
"vue": "^2.6.14", "vue": "^2.6.14",
"vue-analytics": "^5.16.2", "vue-analytics": "^5.16.2",
"vue-apollo": "^3.0.0-beta.16", "vue-apollo": "^3.0.0-beta.16",
"vue-loader": "^15.9.8", "vue-loader": "^15.9.8",
"vue-template-compiler": "^2.6.14",
"vue-matomo": "^3.13.4-0", "vue-matomo": "^3.13.4-0",
"vue-router": "^3.0.1", "vue-router": "^3.0.1",
"vue-scrollto": "^2.11.0", "vue-scrollto": "^2.11.0",
"vue-style-loader": "^3.0.1", "vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.6.14",
"vue-toast-notification": "^0.4.1", "vue-toast-notification": "^0.4.1",
"vue-vimeo-player": "0.0.6", "vue-vimeo-player": "0.0.6",
"vuejs-logger": "1.5.5", "vuejs-logger": "1.5.5",

View File

@ -1,6 +1,6 @@
<template> <template>
<button <button
:disabled="loading" :disabled="loading || disabled"
class="loading-button button button--primary button--big"> class="loading-button button button--primary button--big">
<template v-if="!loading">{{ label }}</template> <template v-if="!loading">{{ label }}</template>
<loading-icon <loading-icon
@ -18,6 +18,10 @@
type: Boolean, type: Boolean,
default: false default: false
}, },
disabled: {
type: Boolean,
default: false
},
label: { label: {
type: String, type: String,
default: '' default: ''
@ -30,7 +34,7 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/styles/_helpers.scss"; @import "~styles/helpers";
.loading-button { .loading-button {
height: 52px; height: 52px;

View File

@ -1,96 +0,0 @@
<template>
<!-- Not currently in use, but keeping the file in case it's needed again -->
<div class="password-reset">
<h2 class="password-reset__header">Passwort ändern</h2>
<div
class="success-message"
v-if="showSuccess">
<p
class="success-message__msg"
data-cy="password-change-success">Dein Password wurde erfolgreich geändert.</p>
</div>
<password-change-form
:old-password-errors="oldPasswordErrors"
:new-password-errors="newPasswordErrors"
@passwordSubmited="resetPassword"/>
</div>
</template>
<script>
import UPDATE_PASSWORD_MUTATION from '@/graphql/gql/mutations/updatePassword.gql';
import PasswordChangeForm from '@/components/profile/PasswordChangeForm';
export default {
components: {
PasswordChangeForm
},
data() {
return {
oldPasswordErrors: [],
newPasswordErrors: [],
showSuccess: false
};
},
methods: {
resetPassword(passwords) {
this.$apollo.mutate({
mutation: UPDATE_PASSWORD_MUTATION,
variables: {
input: {
passwordInput: passwords
}
}
}).then(({data}) => {
if (data.updatePassword.success) {
this.oldPasswordErrors = [];
this.newPasswordErrors = [];
this.showSuccess = true;
this.$root.$emit('reset-password-form');
} else {
// currently we just have one error per field
const error = data.updatePassword.errors[0];
if (error.field === 'old_password') {
this.handleOldPasswordError(error);
} else {
this.handleNewPasswordError(error);
}
}
}).catch((error) => {
console.log('fail', error);
});
},
handleOldPasswordError(error) {
this.oldPasswordErrors = error.errors.map((fieldError) => {
if (fieldError.code === 'invalid') {
return 'Die Eingabe ist falsch';
}
});
},
handleNewPasswordError(error) {
this.newPasswordErrors = error.errors.map((fieldError) => {
if (fieldError.code === 'invalid') {
return 'Das Passwort muss Grossbuchstaben, Zahlen und Sonderzeichen beinhalten';
} else if (fieldError.code === 'min_length') {
return 'Das Passwort muss mindestens 8 Zeichen lang sein.';
}
});
}
}
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
.success-message {
margin-bottom: 20px;
&__msg {
color: $color-accent-4-dark;
font-family: $sans-serif-font-family;
}
}
</style>

View File

@ -1,112 +0,0 @@
<template>
<div class="pw-change">
<form
class="pw-change__form change-form"
novalidate
@submit.prevent="validateBeforeSubmit">
<div class="change-form__field skillboxform-input">
<label
for="old-pw"
class="skillboxform-input__label">Aktuelles Passwort</label>
<input
v-model="oldPassword"
v-validate="'required'"
:class="{ 'skillboxform-input__input--error': errors.has('oldPassword') }"
name="oldPassword"
type="text"
class="change-form__old skillbox-input skillboxform-input__input"
autocomplete="off"
data-vv-as="Altes Passwort"
data-cy="old-password"
id="old-pw">
<small
class="skillboxform-input__error"
data-cy="old-password-local-errors"
v-if="errors.has('oldPassword') && submitted">{{ errors.first('oldPassword') }}</small>
<small
:key="error"
class=" skillboxform-input__error"
data-cy="old-password-remote-errors"
v-for="error in oldPasswordErrors">{{ error }}</small>
</div>
<div class="change-form__field skillboxform-input">
<label
for="new-pw"
class="skillboxform-input__label">Neues Passwort</label>
<input
v-model="newPassword"
v-validate="'required|min:8|strongPassword'"
:class="{ 'skillboxform-input__input--error': errors.has('newPassword') }"
name="newPassword"
type="text"
data-vv-as="Neues Passwort"
class="change-form__new skillbox-input skillboxform-input__input"
autocomplete="off"
data-cy="new-password"
id="new-pw">
<small
class=" skillboxform-input__error"
data-cy="new-password-local-errors"
v-if="errors.has('newPassword') && submitted">{{ errors.first('newPassword') }}</small>
<small
:key="error"
class=" skillboxform-input__error"
data-cy="new-password-remote-errors"
v-for="error in newPasswordErrors">{{ error }}</small>
<p class="skillboxform-input__hint">Das Passwort muss mindestens 8 Zeichen lang sein und Grossbuchstaben, Zahlen und Sonderzeichen beinhalten.</p>
</div>
<button
class="button button--primary change-form__submit"
data-cy="change-password-button">Speichern</button>
</form>
</div>
</template>
<script>
export default {
props: ['newPasswordErrors', 'oldPasswordErrors'],
data: () => ({
oldPassword: '',
newPassword: '',
submitted: false
}),
mounted() {
this.$root.$on('reset-password-form', () => {
this.resetForm();
});
},
methods: {
validateBeforeSubmit () {
this.$validator.validate().then((result) => {
this.submitted = true;
if (result) {
this.$emit('passwordSubmited', {
oldPassword: this.oldPassword,
newPassword: this.newPassword
});
}
});
},
resetForm () {
this.oldPassword = '';
this.newPassword = '';
this.submitted = false;
this.$validator.reset();
}
},
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_buttons.scss";
.change-form {
width: 50%;
@media screen and (max-width: 1024px) {
width: 100%;
}
}
</style>

View File

@ -7,10 +7,8 @@ import App from './App';
import {router, postLoginRedirectUrlKey} from './router'; import {router, postLoginRedirectUrlKey} from './router';
import store from '@/store/index'; import store from '@/store/index';
import VueScrollTo from 'vue-scrollto'; import VueScrollTo from 'vue-scrollto';
import {Validator} from 'vee-validate/dist/vee-validate.minimal.esm.js'; import {extend, localize} from 'vee-validate';
import VeeValidate from 'vee-validate'; import {required, min, double, confirmed} from 'vee-validate/dist/rules';
import {required, min, decimal, confirmed} from 'vee-validate/dist/rules.esm.js';
import veeDe from 'vee-validate/dist/locale/de';
import {dateFilter, dateTimeFilter} from './filters/date-filter'; import {dateFilter, dateTimeFilter} from './filters/date-filter';
import autoGrow from '@/directives/auto-grow'; import autoGrow from '@/directives/auto-grow';
import clickOutside from '@/directives/click-outside'; import clickOutside from '@/directives/click-outside';
@ -20,6 +18,7 @@ import VueRemoveEdges from '@/plugins/edges';
import VueMatomo from 'vue-matomo'; import VueMatomo from 'vue-matomo';
import VueToast from 'vue-toast-notification'; import VueToast from 'vue-toast-notification';
import VueLogger from 'vuejs-logger'; import VueLogger from 'vuejs-logger';
import de from 'vee-validate/dist/locale/de.json';
Vue.config.productionTip = false; Vue.config.productionTip = false;
const isProduction = process.env.NODE_ENV === 'production'; const isProduction = process.env.NODE_ENV === 'production';
@ -63,28 +62,36 @@ const apolloProvider = new VueApollo({
defaultClient: privateApolloClient defaultClient: privateApolloClient
}); });
Validator.extend('required', required); extend('required', required);
Validator.extend('min', min); extend('min', min);
Validator.extend('decimal', decimal); extend('decimal', double);
Validator.extend('confirmed', confirmed); extend('confirmed', confirmed);
const dict = { // const dict = {
custom: { // custom: {
oldPassword: { // oldPassword: {
required: 'Dein aktuelles Passwort fehlt' // required: 'Dein aktuelles Passwort fehlt'
}, // },
newPassword: { // newPassword: {
required: 'Dein neues Passwort fehlt', // required: 'Dein neues Passwort fehlt',
min: 'Das neue Passwort muss mindestens 8 Zeichen lang sein' // min: 'Das neue Passwort muss mindestens 8 Zeichen lang sein'
// }
// }
// };
localize('de', {
de: {
...de,
names: {
password: 'Password',
email: 'E-Mail',
coupon: 'Coupon'
} }
} }
}; });
Validator.localize('de', veeDe);
Validator.localize('de', dict);
// https://github.com/baianat/vee-validate/issues/51 // https://github.com/baianat/vee-validate/issues/51
Validator.extend('strongPassword', { extend('strongPassword', {
getMessage: field => 'Das Passwort muss Grossbuchstaben, Zahlen und Sonderzeichen beinhalten und mindestens 8 Zeichen lang sein', getMessage: field => 'Das Passwort muss Grossbuchstaben, Zahlen und Sonderzeichen beinhalten und mindestens 8 Zeichen lang sein',
validate: value => { validate: value => {
const strongRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*?(),.":{}|<>+])(?=.{8,})/; const strongRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*?(),.":{}|<>+])(?=.{8,})/;
@ -92,7 +99,7 @@ Validator.extend('strongPassword', {
} }
}); });
Validator.extend('email', { extend('email', {
getMessage: field => 'Bitte geben Sie eine gülitge E-Mail an', getMessage: field => 'Bitte geben Sie eine gülitge E-Mail an',
validate: value => { validate: value => {
const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/; const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/;
@ -100,10 +107,6 @@ Validator.extend('email', {
} }
}); });
Vue.use(VeeValidate, {
locale: 'de'
});
Vue.filter('date', dateFilter); Vue.filter('date', dateFilter);
Vue.filter('datetime', dateTimeFilter); Vue.filter('datetime', dateTimeFilter);

View File

@ -1,175 +1,137 @@
<template> <template>
<div class="login public-page"> <div class="login public-page">
<h1 class="login__title public-page__title">Melden Sie sich jetzt an</h1> <h1 class="login__title public-page__title">Melden Sie sich jetzt an</h1>
<form <ValidationObserver v-slot="{invalid, handleSubmit}">
class="login__form login-form" <form
novalidate class="login__form login-form"
@submit.prevent="validateBeforeSubmit"> novalidate
<div class="login-form__field skillboxform-input"> @submit.prevent="handleSubmit(validateBeforeSubmit)">
<label
for="email" <validated-input
class="skillboxform-input__label">E-Mail</label>
<input
v-model="email" v-model="email"
v-validate="'required'" :remote-errors="emailErrors"
:class="{ 'skillboxform-input__input--error': errors.has('email') }" rules="required"
name="email" name="email"
type="text" label="E-Mail"
data-vv-as="E-Mail"
class="change-form__email skillbox-input skillboxform-input__input"
autocomplete="off"
data-cy="email-input" data-cy="email-input"
id="email" id="email"
> />
<small
class="skillboxform-input__error" <validated-input
data-cy="email-local-errors" v-slot="{errors}"
v-if="errors.has('email') && submitted"
>{{ errors.first('email') }}</small>
<small
:key="error"
class="skillboxform-input__error"
data-cy="email-remote-errors"
v-for="error in emailErrors"
>{{ error }}</small>
</div>
<div class="change-form__field skillboxform-input">
<label
for="pw"
class="skillboxform-input__label">Passwort</label>
<input
v-model="password" v-model="password"
v-validate="'required'" :remote-errors="passwordErrors"
:class="{ 'skillboxform-input__input--error': errors.has('password') }" label="Passwort"
rules="required"
name="password" name="password"
type="password" type="password"
data-vv-as="Passwort"
class="change-form__new skillbox-input skillboxform-input__input"
autocomplete="off"
data-cy="password-input" data-cy="password-input"
id="pw" />
>
<small <div class="skillboxform-input">
class="skillboxform-input__error" <small
data-cy="password-local-errors" class="skillboxform-input__error"
v-if="errors.has('password') && submitted" data-cy="login-error"
>{{ errors.first('password') }}</small> v-if="loginError">{{ loginError }}</small>
<small </div>
:key="error" <div class="actions">
class="skillboxform-input__error" <button
data-cy="password-remote-errors" :disabled="invalid"
v-for="error in passwordErrors" class="button button--primary button--big actions__submit"
>{{ error }}</small> data-cy="login-button">Anmelden
</div> </button>
<div class="skillboxform-input"> </div>
<small </form>
class="skillboxform-input__error" </ValidationObserver>
data-cy="login-error"
v-if="loginError">{{ loginError }}</small>
</div>
<div class="actions">
<button
class="button button--primary button--big actions__submit"
data-cy="login-button">Anmelden</button>
</div>
</form>
</div> </div>
</template> </template>
<script> <script>
import BETA_LOGIN_MUTATION from '@/graphql/gql/mutations/betaLogin.gql'; import BETA_LOGIN_MUTATION from '@/graphql/gql/mutations/betaLogin.gql';
import {ValidationObserver, ValidationProvider} from 'vee-validate';
import ValidatedInput from '@/components/validation/ValidatedInput';
export default { export default {
components: {}, components: {
ValidatedInput,
data() { ValidationProvider,
return { ValidationObserver,
email: '',
password: '',
emailErrors: [],
passwordErrors: [],
loginError: '',
submitted: false
};
},
methods: {
validateBeforeSubmit() {
this.$validator.validate().then(result => {
this.submitted = true;
let that = this;
if (result) {
this.$apollo.mutate({
client: 'publicClient',
mutation: BETA_LOGIN_MUTATION,
variables: {
input: {
usernameInput: this.email,
passwordInput: this.password
}
},
update(
store,
{
data: {
betaLogin
}
}
) {
try {
if (betaLogin.success) {
const redirectUrl = that.$route.query.redirect ? that.$route.query.redirect : '/';
that.$router.push(redirectUrl);
}
} catch (e) {
console.warn(e);
that.loginError = 'Es ist ein Fehler aufgetreten. Bitte versuchen Sie nochmals.';
}
}
}).catch(error => {
const firstError = error.graphQLErrors[0];
switch (firstError.message) {
case 'invalid_credentials':
that.loginError = 'Die E-Mail oder das Passwort ist falsch. Bitte versuchen Sie nochmals.';
break;
case 'license_inactive':
that.loginError = 'Ihre Lizenz ist nicht mehr aktiv.';
break;
}
});
}
});
}, },
resetForm() {
this.email = ''; data() {
this.password = ''; return {
this.submitted = false; email: '',
this.$validator.reset(); password: '',
} emailErrors: [],
}, passwordErrors: [],
}; loginError: '',
submitted: false,
};
},
methods: {
validateBeforeSubmit() {
this.submitted = true;
const variables = {
input: {
usernameInput: this.email,
passwordInput: this.password,
},
};
const redirectUrl = this.$route.query.redirect ? this.$route.query.redirect : '/';
this.$apollo.mutate({
client: 'publicClient',
mutation: BETA_LOGIN_MUTATION,
variables,
update: (store, {data: {betaLogin}}) => {
try {
if (betaLogin.success) {
console.log(this);
this.$router.push(redirectUrl);
}
} catch (e) {
console.warn(e);
this.loginError = 'Es ist ein Fehler aufgetreten. Bitte versuchen Sie nochmals.';
}
},
}).catch(error => {
const firstError = error.graphQLErrors[0];
switch (firstError.message) {
case 'invalid_credentials':
this.loginError = 'Die E-Mail oder das Passwort ist falsch. Bitte versuchen Sie nochmals.';
break;
case 'license_inactive':
this.loginError = 'Ihre Lizenz ist nicht mehr aktiv.';
break;
}
});
},
},
};
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/styles/_variables.scss"; @import "~styles/helpers";
@import "@/styles/_mixins.scss";
.text-link { .text-link {
font-family: $sans-serif-font-family; font-family: $sans-serif-font-family;
color: $color-brand; color: $color-brand;
} }
.actions { .actions {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
&__reset { &__reset {
display: inline-block; display: inline-block;
margin-left: $large-spacing; margin-left: $large-spacing;
padding: 15px; padding: 15px;
line-height: 19px; line-height: 19px;
}
} }
}
</style> </style>

View File

@ -1,57 +1,44 @@
<template> <template>
<div class="license-activation public-page"> <div class="license-activation public-page">
<header class="info-header"> <header class="info-header">
<p class="info-header__text small-emph">Für <span class="info-header__emph">{{ me.email }}</span> haben wir keine gültige Lizenz gefunden</p> <p class="info-header__text small-emph">Für <span class="info-header__emph">{{ me.email }}</span> haben wir keine
gültige Lizenz gefunden</p>
</header> </header>
<section class="coupon"> <section class="coupon">
<form <ValidationObserver
class="license-activation__form license-activation-form" v-slot="{handleSubmit, invalid}"
novalidate >
@submit.prevent="validateBeforeSubmit"> <form
<h2>Geben Sie einen Coupon-Code ein</h2> class="license-activation__form license-activation-form"
<div class="change-form__field skillboxform-input"> novalidate
<label @submit.prevent="handleSubmit(validateBeforeSubmit)">
for="coupon" <h2>Geben Sie einen Coupon-Code ein</h2>
class="skillboxform-input__label">Coupon-Code</label> <validated-input
<input
v-model="coupon" v-model="coupon"
v-validate="'required'" :remote-errors="couponErrors"
:class="{ 'skillboxform-input__input--error': errors.has('coupon') }" rules="required"
name="coupon" name="coupon"
type="coupon" label="Coupon-Code"
data-vv-as="Coupon"
class="change-form__new skillbox-input skillboxform-input__input"
autocomplete="off"
data-cy="coupon-input" data-cy="coupon-input"
class="login-form__field"
tabindex="0" tabindex="0"
id="coupon"
>
<small
class="skillboxform-input__error"
data-cy="coupon-local-errors"
v-if="errors.has('coupon') && submitted"
>{{ errors.first('coupon') }}</small>
<small
:key="error"
class="skillboxform-input__error"
data-cy="coupon-remote-errors"
v-for="error in couponErrors"
>{{ error }}</small>
</div>
<div class="actions">
<loading-button
:loading="loading"
class="actions__submit"
data-cy="coupon-button"
label="Coupon abschicken"
/> />
<a <div class="actions">
class="button button--big" <loading-button
data-cy="license-activation-cancel" :loading="loading"
@click="logout">Abmelden :disabled="invalid"
</a> class="actions__submit"
</div> data-cy="coupon-button"
</form> label="Coupon abschicken"
/>
<a
class="button button--big"
data-cy="license-activation-cancel"
@click="logout">Abmelden
</a>
</div>
</form>
</ValidationObserver>
</section> </section>
<section class="get-license"> <section class="get-license">
<h2>Oder, kaufen Sie eine Lizenz</h2> <h2>Oder, kaufen Sie eine Lizenz</h2>
@ -69,108 +56,103 @@
<script> <script>
import REDEEM_COUPON from '@/graphql/gql/mutations/redeemCoupon.gql'; import REDEEM_COUPON from '@/graphql/gql/mutations/redeemCoupon.gql';
import ME_QUERY from '@/graphql/gql/queries/meQuery.gql'; import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
import LoadingButton from '@/components/LoadingButton'; import LoadingButton from '@/components/LoadingButton';
import ValidatedInput from '@/components/validation/ValidatedInput';
import {ValidationObserver} from 'vee-validate';
import me from '@/mixins/me'; import me from '@/mixins/me';
import logout from '@/mixins/logout'; import logout from '@/mixins/logout';
import pageTitleMixin from '@/mixins/page-title'; import pageTitleMixin from '@/mixins/page-title';
export default { export default {
mixins: [me, logout, pageTitleMixin], mixins: [me, logout, pageTitleMixin],
components: {LoadingButton}, components: {
LoadingButton,
ValidationObserver,
ValidatedInput,
},
data() { data() {
return { return {
coupon: '', coupon: '',
couponErrors: [], couponErrors: [],
loginError: '', loginError: '',
submitted: false, submitted: false,
me: { me: {
email: '' email: '',
}, },
teacherEditionUrl: `${process.env.HEP_URL}/myskillbox-lehrpersonen`, teacherEditionUrl: `${process.env.HEP_URL}/myskillbox-lehrpersonen`,
studentEditionUrl: `${process.env.HEP_URL}/myskillbox-fur-lernende`, studentEditionUrl: `${process.env.HEP_URL}/myskillbox-fur-lernende`,
loading: false loading: false,
}; };
}, },
methods: { methods: {
validateBeforeSubmit() { validateBeforeSubmit() {
this.$validator.validate().then(result => {
this.submitted = true; this.submitted = true;
let that = this; this.loading = true;
if (result) { this.$apollo.mutate({
this.loading = true; mutation: REDEEM_COUPON,
this.$apollo.mutate({ variables: {
mutation: REDEEM_COUPON, input: {
variables: { couponCode: this.coupon,
input: {
couponCode: this.coupon
}
}, },
update( },
store, update(
{ store,
data: {coupon} {
} data: {coupon},
) { },
if (coupon.success) { ) {
that.loading = false; if (coupon.success) {
that.couponErrors = []; this.loading = false;
that.$apollo.query({ this.couponErrors = [];
query: ME_QUERY, this.$apollo.query({
fetchPolicy: 'network-only', query: ME_QUERY,
}).then(() => that.$router.push('/')); fetchPolicy: 'network-only',
} }).then(() => this.$router.push('/'));
} }
}).catch(({message}) => { },
}).catch(({message}) => {
this.loading = false; this.loading = false;
if (message.indexOf('invalid_coupon') > -1) { if (message.indexOf('invalid_coupon') > -1) {
that.couponErrors = ['Der angegebene Coupon-Code ist ungültig.']; this.couponErrors = ['Der angegebene Coupon-Code ist ungültig.'];
} else { } else {
that.couponErrors = ['Es ist ein Fehler aufgetreten. Bitte versuchen Sie es nochmals oder kontaktieren Sie den Administrator.']; this.couponErrors = ['Es ist ein Fehler aufgetreten. Bitte versuchen Sie es nochmals oder kontaktieren Sie den Administrator.'];
} }
}) })
.finally(() => { .finally(() => {
this.loading = false; this.loading = false;
}); });
} },
});
}, },
resetForm() { };
this.coupon = '';
this.submitted = false;
this.$validator.reset();
}
},
};
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/styles/_variables.scss"; @import "~styles/helpers";
@import "@/styles/_mixins.scss";
.text-link { .text-link {
font-family: $sans-serif-font-family; font-family: $sans-serif-font-family;
color: $color-brand; color: $color-brand;
}
.actions {
&__reset {
display: inline-block;
margin-left: $large-spacing;
} }
}
.get-license { .actions {
margin-top: $large-spacing &__reset {
} display: inline-block;
margin-left: $large-spacing;
.license-links { }
&__item { }
margin-bottom: $medium-spacing;
.get-license {
margin-top: $large-spacing
}
.license-links {
&__item {
margin-bottom: $medium-spacing;
}
} }
}
</style> </style>

View File

@ -12,17 +12,29 @@
&--white-bg { &--white-bg {
background-color: $color-white; background-color: $color-white;
} }
@mixin disabled { @mixin disabled {
cursor: default; cursor: default;
} }
&--disabled { @mixin disabled-default {
@include disabled; cursor: default;
background-color: $color-silver-light; background-color: $color-silver-light;
border-color: $color-silver-light;
} }
&:disabled {
@include disabled-default;
}
&--disabled {
@include disabled-default;
}
&--disabled-alt { &--disabled-alt {
@include disabled; @include disabled;
opacity: 0.3; opacity: 0.3;
} }
&--big { &--big {
padding: 15px; padding: 15px;
} }