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
```
# 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": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-1.2.2.tgz",
@ -19743,9 +19758,9 @@
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
},
"vee-validate": {
"version": "2.2.15",
"resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-2.2.15.tgz",
"integrity": "sha512-4TOsI8XwVkKVLkg8Nhmy+jyoJrR6XcTRDyxBarzcCvYzU61zamipS1WsB6FlDze8eJQpgglS4NXAS6o4NDPs1g=="
"version": "3.4.14",
"resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-3.4.14.tgz",
"integrity": "sha512-Hqqic8G9WcRSIzCxiCPqMZv4qB8JE1lIQqIOLDm2K5BXUiL8d4a2+kqkanv8gQSGDzYpnCQZ7BO/T99Aj05T1Q=="
},
"vendors": {
"version": "1.0.4",

View File

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

View File

@ -1,6 +1,6 @@
<template>
<button
:disabled="loading"
:disabled="loading || disabled"
class="loading-button button button--primary button--big">
<template v-if="!loading">{{ label }}</template>
<loading-icon
@ -18,6 +18,10 @@
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
label: {
type: String,
default: ''
@ -30,7 +34,7 @@
</script>
<style scoped lang="scss">
@import "@/styles/_helpers.scss";
@import "~styles/helpers";
.loading-button {
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 store from '@/store/index';
import VueScrollTo from 'vue-scrollto';
import {Validator} from 'vee-validate/dist/vee-validate.minimal.esm.js';
import VeeValidate from 'vee-validate';
import {required, min, decimal, confirmed} from 'vee-validate/dist/rules.esm.js';
import veeDe from 'vee-validate/dist/locale/de';
import {extend, localize} from 'vee-validate';
import {required, min, double, confirmed} from 'vee-validate/dist/rules';
import {dateFilter, dateTimeFilter} from './filters/date-filter';
import autoGrow from '@/directives/auto-grow';
import clickOutside from '@/directives/click-outside';
@ -20,6 +18,7 @@ import VueRemoveEdges from '@/plugins/edges';
import VueMatomo from 'vue-matomo';
import VueToast from 'vue-toast-notification';
import VueLogger from 'vuejs-logger';
import de from 'vee-validate/dist/locale/de.json';
Vue.config.productionTip = false;
const isProduction = process.env.NODE_ENV === 'production';
@ -63,28 +62,36 @@ const apolloProvider = new VueApollo({
defaultClient: privateApolloClient
});
Validator.extend('required', required);
Validator.extend('min', min);
Validator.extend('decimal', decimal);
Validator.extend('confirmed', confirmed);
extend('required', required);
extend('min', min);
extend('decimal', double);
extend('confirmed', confirmed);
const dict = {
custom: {
oldPassword: {
required: 'Dein aktuelles Passwort fehlt'
},
newPassword: {
required: 'Dein neues Passwort fehlt',
min: 'Das neue Passwort muss mindestens 8 Zeichen lang sein'
// const dict = {
// custom: {
// oldPassword: {
// required: 'Dein aktuelles Passwort fehlt'
// },
// newPassword: {
// required: 'Dein neues Passwort fehlt',
// 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
Validator.extend('strongPassword', {
extend('strongPassword', {
getMessage: field => 'Das Passwort muss Grossbuchstaben, Zahlen und Sonderzeichen beinhalten und mindestens 8 Zeichen lang sein',
validate: value => {
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',
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])?)+$/;
@ -100,10 +107,6 @@ Validator.extend('email', {
}
});
Vue.use(VeeValidate, {
locale: 'de'
});
Vue.filter('date', dateFilter);
Vue.filter('datetime', dateTimeFilter);

View File

@ -1,175 +1,137 @@
<template>
<div class="login public-page">
<h1 class="login__title public-page__title">Melden Sie sich jetzt an</h1>
<form
class="login__form login-form"
novalidate
@submit.prevent="validateBeforeSubmit">
<div class="login-form__field skillboxform-input">
<label
for="email"
class="skillboxform-input__label">E-Mail</label>
<input
<ValidationObserver v-slot="{invalid, handleSubmit}">
<form
class="login__form login-form"
novalidate
@submit.prevent="handleSubmit(validateBeforeSubmit)">
<validated-input
v-model="email"
v-validate="'required'"
:class="{ 'skillboxform-input__input--error': errors.has('email') }"
:remote-errors="emailErrors"
rules="required"
name="email"
type="text"
data-vv-as="E-Mail"
class="change-form__email skillbox-input skillboxform-input__input"
autocomplete="off"
label="E-Mail"
data-cy="email-input"
id="email"
>
<small
class="skillboxform-input__error"
data-cy="email-local-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
/>
<validated-input
v-slot="{errors}"
v-model="password"
v-validate="'required'"
:class="{ 'skillboxform-input__input--error': errors.has('password') }"
:remote-errors="passwordErrors"
label="Passwort"
rules="required"
name="password"
type="password"
data-vv-as="Passwort"
class="change-form__new skillbox-input skillboxform-input__input"
autocomplete="off"
data-cy="password-input"
id="pw"
>
<small
class="skillboxform-input__error"
data-cy="password-local-errors"
v-if="errors.has('password') && submitted"
>{{ errors.first('password') }}</small>
<small
:key="error"
class="skillboxform-input__error"
data-cy="password-remote-errors"
v-for="error in passwordErrors"
>{{ error }}</small>
</div>
<div class="skillboxform-input">
<small
class="skillboxform-input__error"
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 class="skillboxform-input">
<small
class="skillboxform-input__error"
data-cy="login-error"
v-if="loginError">{{ loginError }}</small>
</div>
<div class="actions">
<button
:disabled="invalid"
class="button button--primary button--big actions__submit"
data-cy="login-button">Anmelden
</button>
</div>
</form>
</ValidationObserver>
</div>
</template>
<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 {
components: {},
data() {
return {
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;
}
});
}
});
export default {
components: {
ValidatedInput,
ValidationProvider,
ValidationObserver,
},
resetForm() {
this.email = '';
this.password = '';
this.submitted = false;
this.$validator.reset();
}
},
};
data() {
return {
email: '',
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>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
@import "~styles/helpers";
.text-link {
font-family: $sans-serif-font-family;
color: $color-brand;
}
.actions {
display: flex;
justify-content: space-between;
&__reset {
display: inline-block;
margin-left: $large-spacing;
padding: 15px;
line-height: 19px;
.text-link {
font-family: $sans-serif-font-family;
color: $color-brand;
}
.actions {
display: flex;
justify-content: space-between;
&__reset {
display: inline-block;
margin-left: $large-spacing;
padding: 15px;
line-height: 19px;
}
}
}
</style>

View File

@ -1,57 +1,44 @@
<template>
<div class="license-activation public-page">
<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>
<section class="coupon">
<form
class="license-activation__form license-activation-form"
novalidate
@submit.prevent="validateBeforeSubmit">
<h2>Geben Sie einen Coupon-Code ein</h2>
<div class="change-form__field skillboxform-input">
<label
for="coupon"
class="skillboxform-input__label">Coupon-Code</label>
<input
<ValidationObserver
v-slot="{handleSubmit, invalid}"
>
<form
class="license-activation__form license-activation-form"
novalidate
@submit.prevent="handleSubmit(validateBeforeSubmit)">
<h2>Geben Sie einen Coupon-Code ein</h2>
<validated-input
v-model="coupon"
v-validate="'required'"
:class="{ 'skillboxform-input__input--error': errors.has('coupon') }"
:remote-errors="couponErrors"
rules="required"
name="coupon"
type="coupon"
data-vv-as="Coupon"
class="change-form__new skillbox-input skillboxform-input__input"
autocomplete="off"
label="Coupon-Code"
data-cy="coupon-input"
class="login-form__field"
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
class="button button--big"
data-cy="license-activation-cancel"
@click="logout">Abmelden
</a>
</div>
</form>
<div class="actions">
<loading-button
:loading="loading"
:disabled="invalid"
class="actions__submit"
data-cy="coupon-button"
label="Coupon abschicken"
/>
<a
class="button button--big"
data-cy="license-activation-cancel"
@click="logout">Abmelden
</a>
</div>
</form>
</ValidationObserver>
</section>
<section class="get-license">
<h2>Oder, kaufen Sie eine Lizenz</h2>
@ -69,108 +56,103 @@
<script>
import REDEEM_COUPON from '@/graphql/gql/mutations/redeemCoupon.gql';
import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
import LoadingButton from '@/components/LoadingButton';
import REDEEM_COUPON from '@/graphql/gql/mutations/redeemCoupon.gql';
import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
import LoadingButton from '@/components/LoadingButton';
import ValidatedInput from '@/components/validation/ValidatedInput';
import {ValidationObserver} from 'vee-validate';
import me from '@/mixins/me';
import logout from '@/mixins/logout';
import pageTitleMixin from '@/mixins/page-title';
import me from '@/mixins/me';
import logout from '@/mixins/logout';
import pageTitleMixin from '@/mixins/page-title';
export default {
mixins: [me, logout, pageTitleMixin],
components: {LoadingButton},
export default {
mixins: [me, logout, pageTitleMixin],
components: {
LoadingButton,
ValidationObserver,
ValidatedInput,
},
data() {
return {
coupon: '',
couponErrors: [],
loginError: '',
submitted: false,
me: {
email: ''
},
teacherEditionUrl: `${process.env.HEP_URL}/myskillbox-lehrpersonen`,
studentEditionUrl: `${process.env.HEP_URL}/myskillbox-fur-lernende`,
loading: false
};
},
methods: {
validateBeforeSubmit() {
this.$validator.validate().then(result => {
data() {
return {
coupon: '',
couponErrors: [],
loginError: '',
submitted: false,
me: {
email: '',
},
teacherEditionUrl: `${process.env.HEP_URL}/myskillbox-lehrpersonen`,
studentEditionUrl: `${process.env.HEP_URL}/myskillbox-fur-lernende`,
loading: false,
};
},
methods: {
validateBeforeSubmit() {
this.submitted = true;
let that = this;
if (result) {
this.loading = true;
this.$apollo.mutate({
mutation: REDEEM_COUPON,
variables: {
input: {
couponCode: this.coupon
}
this.loading = true;
this.$apollo.mutate({
mutation: REDEEM_COUPON,
variables: {
input: {
couponCode: this.coupon,
},
update(
store,
{
data: {coupon}
}
) {
if (coupon.success) {
that.loading = false;
that.couponErrors = [];
that.$apollo.query({
query: ME_QUERY,
fetchPolicy: 'network-only',
}).then(() => that.$router.push('/'));
}
},
update(
store,
{
data: {coupon},
},
) {
if (coupon.success) {
this.loading = false;
this.couponErrors = [];
this.$apollo.query({
query: ME_QUERY,
fetchPolicy: 'network-only',
}).then(() => this.$router.push('/'));
}
}).catch(({message}) => {
},
}).catch(({message}) => {
this.loading = false;
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 {
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(() => {
this.loading = false;
});
}
});
},
},
resetForm() {
this.coupon = '';
this.submitted = false;
this.$validator.reset();
}
},
};
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
@import "~styles/helpers";
.text-link {
font-family: $sans-serif-font-family;
color: $color-brand;
}
.actions {
&__reset {
display: inline-block;
margin-left: $large-spacing;
.text-link {
font-family: $sans-serif-font-family;
color: $color-brand;
}
}
.get-license {
margin-top: $large-spacing
}
.license-links {
&__item {
margin-bottom: $medium-spacing;
.actions {
&__reset {
display: inline-block;
margin-left: $large-spacing;
}
}
.get-license {
margin-top: $large-spacing
}
.license-links {
&__item {
margin-bottom: $medium-spacing;
}
}
}
</style>

View File

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