first commit
This commit is contained in:
76
resources/frontend/core/views/Auth/AuthInput.vue
Normal file
76
resources/frontend/core/views/Auth/AuthInput.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div>
|
||||
<validation-provider v-slot="{ errors }" rules="required|email" mode="passive" name="E-mail">
|
||||
<div class="input-group">
|
||||
<small>E-Mail</small>
|
||||
<at-input
|
||||
v-model="user.email"
|
||||
name="login"
|
||||
:status="errors.length > 0 ? 'error' : ''"
|
||||
placeholder="E-Mail"
|
||||
icon="mail"
|
||||
type="text"
|
||||
required
|
||||
@keydown.native.enter.prevent="submit"
|
||||
></at-input>
|
||||
<small>{{ errors[0] }}</small>
|
||||
</div>
|
||||
<!-- /.input-group -->
|
||||
</validation-provider>
|
||||
<validation-provider v-slot="{ errors }" rules="required" mode="passive" :name="$t('field.password')">
|
||||
<div class="input-group">
|
||||
<small>{{ $t('field.password') }}</small>
|
||||
<at-input
|
||||
v-model="user.password"
|
||||
name="password"
|
||||
:status="errors.length > 0 ? 'error' : ''"
|
||||
:placeholder="$t('field.password')"
|
||||
type="password"
|
||||
icon="lock"
|
||||
required
|
||||
@keydown.native.enter.prevent="submit"
|
||||
></at-input>
|
||||
<small>{{ errors[0] }}</small>
|
||||
</div>
|
||||
<!-- /.input-group -->
|
||||
</validation-provider>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ValidationProvider } from 'vee-validate';
|
||||
|
||||
export default {
|
||||
name: 'AuthInput',
|
||||
|
||||
components: {
|
||||
ValidationProvider,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
user: {
|
||||
email: null,
|
||||
password: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
submit() {
|
||||
this.$emit('submit');
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
'user.email'(value) {
|
||||
// Trim space
|
||||
this.user.email = value.replace(/\s/, '');
|
||||
this.$emit('change', this.user);
|
||||
},
|
||||
'user.password'() {
|
||||
this.$emit('change', this.user);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
178
resources/frontend/core/views/Auth/Desktop.vue
Normal file
178
resources/frontend/core/views/Auth/Desktop.vue
Normal file
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<div class="login row at-row no-gutter">
|
||||
<div class="login__wrap">
|
||||
<div class="login__form">
|
||||
<div class="box">
|
||||
<div class="top">
|
||||
<div class="static-message">
|
||||
<div class="logo" />
|
||||
</div>
|
||||
<h1 class="login__title">Cattr</h1>
|
||||
</div>
|
||||
<template v-if="error">
|
||||
<div>
|
||||
<at-alert :message="$t('auth.desktop_error')" class="login__error" type="error" />
|
||||
</div>
|
||||
<at-button class="login__btn" type="primary" @click="commonLogin"
|
||||
>{{ $t('auth.switch_to_common') }}
|
||||
</at-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<at-alert :message="$t('auth.desktop_working')" class="login__error" type="info" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<a class="login__slogan" href="https://cattr.app" v-html="slogan" />
|
||||
</div>
|
||||
<div class="hero col-16" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login {
|
||||
flex-wrap: nowrap;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
max-height: 100vh;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
&__wrap {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__form {
|
||||
flex: 8;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__slogan {
|
||||
align-content: flex-start;
|
||||
color: $gray-3;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__title {
|
||||
color: $black-900;
|
||||
font-size: 1.8rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__btn {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
&__error {
|
||||
margin-bottom: 1rem;
|
||||
overflow: initial;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
padding: 0 $spacing-08;
|
||||
width: 100%;
|
||||
|
||||
.top {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
margin-bottom: $layout-01;
|
||||
|
||||
.static-message {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
|
||||
.logo {
|
||||
align-items: center;
|
||||
background: url('../../assets/logo.svg');
|
||||
background-size: cover;
|
||||
border-radius: 10px;
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
font-size: 1.8rem;
|
||||
font-weight: bold;
|
||||
height: 60px;
|
||||
justify-content: center;
|
||||
text-transform: uppercase;
|
||||
width: 60px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.link {
|
||||
color: $blue-1;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
::v-deep .input-group {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.hero {
|
||||
background: #6159e6 url('../../assets/login.svg') no-repeat;
|
||||
background-size: 100%;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import sloganGenerator from '@/helpers/sloganGenerator';
|
||||
|
||||
export default {
|
||||
name: 'desktop-login',
|
||||
computed: {
|
||||
slogan() {
|
||||
return sloganGenerator();
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
error: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
commonLogin() {
|
||||
this.$router.replace({ name: 'auth.login' });
|
||||
},
|
||||
finish(error = true) {
|
||||
this.error = error;
|
||||
this.$Loading.error();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$Loading.start();
|
||||
|
||||
if (location.search.length === 0) {
|
||||
this.finish();
|
||||
} else {
|
||||
const query = location.search.substr(1).split('=');
|
||||
|
||||
if (query[0] !== 'token' && query.length !== 2) {
|
||||
this.finish();
|
||||
} else {
|
||||
const apiService = this.$store.getters['user/apiService'];
|
||||
apiService
|
||||
.attemptDesktopLogin(query[1])
|
||||
.then(() => apiService.getCompanyData())
|
||||
.then(() => this.finish(false))
|
||||
.catch(() => this.finish());
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
270
resources/frontend/core/views/Auth/Login.vue
Normal file
270
resources/frontend/core/views/Auth/Login.vue
Normal file
@@ -0,0 +1,270 @@
|
||||
<template>
|
||||
<div class="login row at-row no-gutter">
|
||||
<div class="login__wrap">
|
||||
<div class="login__form">
|
||||
<validation-observer ref="observer" class="box" tag="div" @submit.prevent="submit">
|
||||
<div class="top">
|
||||
<div class="static-message">
|
||||
<div class="logo"></div>
|
||||
</div>
|
||||
<h1 class="login__title">Cattr</h1>
|
||||
</div>
|
||||
<div>
|
||||
<at-alert
|
||||
v-if="error"
|
||||
type="error"
|
||||
class="login__error"
|
||||
closable
|
||||
:message="error"
|
||||
@on-close="error = null"
|
||||
/>
|
||||
|
||||
<component :is="config.authInput" @change="change" @submit="submit" />
|
||||
|
||||
<vue-recaptcha
|
||||
v-if="recaptchaKey"
|
||||
ref="recaptcha"
|
||||
:loadRecaptchaScript="true"
|
||||
:sitekey="recaptchaKey"
|
||||
class="recaptcha"
|
||||
@verify="onCaptchaVerify"
|
||||
@expired="onCaptchaExpired"
|
||||
></vue-recaptcha>
|
||||
</div>
|
||||
<at-button
|
||||
class="login__btn"
|
||||
native-type="submit"
|
||||
type="primary"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
@click="submit"
|
||||
>{{ $t('auth.submit') }}</at-button
|
||||
>
|
||||
<router-link class="link" to="/auth/password/reset">{{ $t('auth.forgot_password') }}</router-link>
|
||||
</validation-observer>
|
||||
</div>
|
||||
<a class="login__slogan" href="https://cattr.app" v-html="slogan" />
|
||||
</div>
|
||||
<div class="hero col-lg-16 col-md-14 col-sm-12"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ValidationObserver } from 'vee-validate';
|
||||
import { VueRecaptcha } from 'vue-recaptcha';
|
||||
import AuthInput from './AuthInput';
|
||||
import sloganGenerator from '@/helpers/sloganGenerator';
|
||||
import has from 'lodash/has';
|
||||
|
||||
export const config = { authInput: AuthInput };
|
||||
|
||||
export default {
|
||||
name: 'Login',
|
||||
|
||||
components: {
|
||||
ValidationObserver,
|
||||
VueRecaptcha,
|
||||
AuthInput,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
user: {
|
||||
email: null,
|
||||
password: null,
|
||||
recaptcha: null,
|
||||
},
|
||||
recaptchaKey: null,
|
||||
error: null,
|
||||
isLoading: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
config() {
|
||||
return config;
|
||||
},
|
||||
slogan() {
|
||||
return sloganGenerator();
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.$store.getters['user/isLoggedIn']) {
|
||||
this.$router.push({ name: 'dashboard' });
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getRandomIntInclusive(min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
},
|
||||
|
||||
onCaptchaVerify(response) {
|
||||
this.user.recaptcha = response;
|
||||
},
|
||||
|
||||
onCaptchaExpired() {
|
||||
this.$refs.recaptcha.reset();
|
||||
},
|
||||
|
||||
change(user) {
|
||||
this.user = { ...this.user, ...user };
|
||||
},
|
||||
|
||||
async submit() {
|
||||
const valid = await this.$refs.observer.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$Loading.start();
|
||||
this.isLoading = true;
|
||||
const apiService = this.$store.getters['user/apiService'];
|
||||
|
||||
try {
|
||||
if ('grecaptcha' in window) {
|
||||
this.$refs.recaptcha.reset();
|
||||
}
|
||||
|
||||
await apiService.attemptLogin(this.user);
|
||||
await apiService.getCompanyData();
|
||||
|
||||
this.error = null;
|
||||
this.$Loading.finish();
|
||||
} catch (e) {
|
||||
this.$Loading.error();
|
||||
|
||||
if (has(e, 'response.status')) {
|
||||
if (e.response.status === 429 && this.recaptchaKey === null) {
|
||||
this.recaptchaKey = e.response.data.info.site_key;
|
||||
}
|
||||
|
||||
let message;
|
||||
|
||||
if (e.response.status === 401) {
|
||||
message = this.$t('auth.message.user_not_found');
|
||||
} else if (e.response.status === 429) {
|
||||
message = this.$t('auth.message.solve_captcha');
|
||||
} else if (e.response.status === 503) {
|
||||
message = this.$t('auth.message.data_reset');
|
||||
} else {
|
||||
message = this.$t('auth.message.auth_error');
|
||||
}
|
||||
|
||||
this.error = message;
|
||||
}
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login {
|
||||
flex-wrap: nowrap;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
max-height: 100vh;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
&__wrap {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
&__form {
|
||||
width: 100%;
|
||||
flex: 8;
|
||||
}
|
||||
|
||||
&__slogan {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: flex-start;
|
||||
color: $gray-3;
|
||||
}
|
||||
|
||||
&__title {
|
||||
color: $black-900;
|
||||
font-size: 1.8rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__btn {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
&__error {
|
||||
margin-bottom: 1rem;
|
||||
overflow: initial;
|
||||
}
|
||||
|
||||
.box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
padding: 0 $spacing-08;
|
||||
width: 100%;
|
||||
|
||||
.top {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
margin-bottom: $layout-01;
|
||||
|
||||
.static-message {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
|
||||
.logo {
|
||||
align-items: center;
|
||||
background: url('../../assets/logo.svg');
|
||||
background-size: cover;
|
||||
border-radius: 10px;
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
font-size: 1.8rem;
|
||||
font-weight: bold;
|
||||
height: 60px;
|
||||
justify-content: center;
|
||||
text-transform: uppercase;
|
||||
width: 60px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.recaptcha {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.link {
|
||||
color: $blue-1;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
::v-deep .input-group {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.hero {
|
||||
background: url('../../assets/login.svg') #6159e6;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100%;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
232
resources/frontend/core/views/Auth/Register.vue
Normal file
232
resources/frontend/core/views/Auth/Register.vue
Normal file
@@ -0,0 +1,232 @@
|
||||
<template>
|
||||
<div class="content-wrapper">
|
||||
<div v-if="isTokenValid" class="container">
|
||||
<div class="at-container">
|
||||
<div class="at-container__inner">
|
||||
<div v-if="isRegisterSuccess">
|
||||
<div class="header-text">
|
||||
<i class="icon icon-check"></i>
|
||||
<h2 class="header-text__title">
|
||||
{{ $t('register.success_title') }}
|
||||
</h2>
|
||||
<p class="header-text__subtitle">{{ $t('register.success_subtitle') }}</p>
|
||||
<router-link to="/auth/login">{{ $t('reset.go_away') }}</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="row">
|
||||
<div class="col-6 col-offset-9">
|
||||
<div class="header-text">
|
||||
<h2 class="header-text__title">
|
||||
{{ $t('register.title') }}
|
||||
</h2>
|
||||
<p class="header-text__subtitle">
|
||||
{{ $t('register.subtitle') }}
|
||||
</p>
|
||||
</div>
|
||||
<at-alert
|
||||
v-if="errorMessage"
|
||||
type="error"
|
||||
class="alert"
|
||||
closable
|
||||
:message="errorMessage"
|
||||
@on-close="errorMessage = null"
|
||||
/>
|
||||
<validation-observer v-slot="{ invalid }">
|
||||
<validation-provider v-slot="{ errors }" rules="required|email" name="E-mail">
|
||||
<div class="input-group">
|
||||
<small>E-Mail</small>
|
||||
<at-input
|
||||
v-model="email"
|
||||
name="login"
|
||||
:status="errors.length > 0 ? 'error' : ''"
|
||||
placeholder="E-Mail"
|
||||
icon="mail"
|
||||
type="text"
|
||||
disabled="true"
|
||||
>
|
||||
</at-input>
|
||||
<p class="error-message">
|
||||
<small>{{ errors[0] }}</small>
|
||||
</p>
|
||||
</div>
|
||||
</validation-provider>
|
||||
<validation-provider v-slot="{ errors }" rules="required" :name="$t('field.full_name')">
|
||||
<div class="input-group">
|
||||
<small>{{ $t('field.full_name') }}</small>
|
||||
<at-input
|
||||
v-model="fullName"
|
||||
name="full_name"
|
||||
:status="errors.length > 0 ? 'error' : ''"
|
||||
:placeholder="$t('field.full_name')"
|
||||
icon="user"
|
||||
type="text"
|
||||
>
|
||||
</at-input>
|
||||
<p class="error-message">
|
||||
<small>{{ errors[0] }}</small>
|
||||
</p>
|
||||
</div>
|
||||
</validation-provider>
|
||||
<validation-provider
|
||||
v-slot="{ errors }"
|
||||
rules="required|min:6"
|
||||
vid="password"
|
||||
:name="$t('field.password')"
|
||||
>
|
||||
<div class="input-group">
|
||||
<small>{{ $t('field.password') }}</small>
|
||||
<at-input
|
||||
v-model="password"
|
||||
name="password"
|
||||
:status="errors.length > 0 ? 'error' : ''"
|
||||
:placeholder="$t('field.full_name')"
|
||||
icon="lock"
|
||||
type="password"
|
||||
>
|
||||
</at-input>
|
||||
<p class="error-message">
|
||||
<small>{{ errors[0] }}</small>
|
||||
</p>
|
||||
</div>
|
||||
</validation-provider>
|
||||
<validation-provider
|
||||
v-slot="{ errors }"
|
||||
rules="required|min:6|confirmed:password"
|
||||
:name="$t('reset.confirm_password')"
|
||||
>
|
||||
<div class="input-group">
|
||||
<small>{{ $t('reset.confirm_password') }}</small>
|
||||
<at-input
|
||||
v-model="passwordConfirmation"
|
||||
name="passwordConfirmation"
|
||||
:status="errors.length > 0 ? 'error' : ''"
|
||||
:placeholder="$t('reset.confirm_password')"
|
||||
icon="lock"
|
||||
type="password"
|
||||
>
|
||||
</at-input>
|
||||
<p class="error-message">
|
||||
<small>{{ errors[0] }}</small>
|
||||
</p>
|
||||
</div>
|
||||
</validation-provider>
|
||||
<at-button
|
||||
class="btn"
|
||||
native-type="submit"
|
||||
type="primary"
|
||||
:disabled="invalid || isLoading"
|
||||
:loading="isLoading"
|
||||
@click="register"
|
||||
>{{ $t('register.register_btn') }}</at-button
|
||||
>
|
||||
</validation-observer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.content-wrapper -->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ValidationObserver, ValidationProvider } from 'vee-validate';
|
||||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
name: 'ResetPassword',
|
||||
components: {
|
||||
ValidationProvider,
|
||||
ValidationObserver,
|
||||
},
|
||||
created() {
|
||||
if (this.$route.query.token) {
|
||||
this.token = this.$route.query.token;
|
||||
this.validateToken();
|
||||
} else {
|
||||
this.$router.push({ name: 'not-found' });
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
email: null,
|
||||
password: null,
|
||||
passwordConfirmation: null,
|
||||
fullName: null,
|
||||
token: null,
|
||||
isTokenValid: false,
|
||||
isLoading: false,
|
||||
isRegisterSuccess: false,
|
||||
errorMessage: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async validateToken() {
|
||||
try {
|
||||
const { data } = await axios.get(`/auth/register/${this.token}`);
|
||||
this.email = data.email;
|
||||
this.isTokenValid = true;
|
||||
} catch ({ response }) {
|
||||
if (response.status === 404) {
|
||||
this.$router.replace({ name: 'not-found' });
|
||||
}
|
||||
}
|
||||
},
|
||||
async register() {
|
||||
this.isLoading = true;
|
||||
|
||||
const data = {
|
||||
email: this.email,
|
||||
full_name: this.fullName,
|
||||
password: this.password,
|
||||
password_confirmation: this.passwordConfirmation,
|
||||
};
|
||||
|
||||
try {
|
||||
await axios.post(`auth/register/${this.token}`, data);
|
||||
|
||||
this.isRegisterSuccess = true;
|
||||
} catch ({ response }) {
|
||||
this.errorMessage = response.data.error;
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.header-text {
|
||||
text-align: center;
|
||||
|
||||
&__title {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: $layout-01;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 92px;
|
||||
|
||||
&-check {
|
||||
color: $green-1;
|
||||
}
|
||||
}
|
||||
|
||||
.alert {
|
||||
margin-bottom: $layout-01;
|
||||
}
|
||||
</style>
|
||||
281
resources/frontend/core/views/Auth/ResetPassword.vue
Normal file
281
resources/frontend/core/views/Auth/ResetPassword.vue
Normal file
@@ -0,0 +1,281 @@
|
||||
<template>
|
||||
<div class="content-wrapper">
|
||||
<div class="container">
|
||||
<div class="at-container crud__content">
|
||||
<at-steps :current="currentStep" class="steps col-lg-offset-4">
|
||||
<at-step
|
||||
:title="$t('reset.step', { n: 1 })"
|
||||
:description="$t('reset.step_description.step_1')"
|
||||
></at-step>
|
||||
<at-step
|
||||
:title="$t('reset.step', { n: 2 })"
|
||||
:description="$t('reset.step_description.step_2')"
|
||||
></at-step>
|
||||
<at-step
|
||||
:title="$t('reset.step', { n: 3 })"
|
||||
:description="$t('reset.step_description.step_3')"
|
||||
></at-step>
|
||||
<at-step
|
||||
:title="$t('reset.step', { n: 4 })"
|
||||
:description="$t('reset.step_description.step_4')"
|
||||
></at-step>
|
||||
</at-steps>
|
||||
|
||||
<div class="row">
|
||||
<div v-if="currentStep == 0" class="col-6 col-offset-9">
|
||||
<validation-observer v-slot="{ invalid }">
|
||||
<div class="header-text">
|
||||
<h2 class="header-text__title">
|
||||
{{ $t('reset.tabs.enter_email.title') }}
|
||||
</h2>
|
||||
<p class="header-text__subtitle">
|
||||
{{ $t('reset.tabs.enter_email.subtitle') }}
|
||||
</p>
|
||||
</div>
|
||||
<validation-provider v-slot="{ errors }" rules="required|email">
|
||||
<small>E-Mail</small>
|
||||
<at-input
|
||||
v-model="email"
|
||||
name="login"
|
||||
:status="errors.length > 0 ? 'error' : ''"
|
||||
placeholder="E-Mail"
|
||||
icon="mail"
|
||||
type="text"
|
||||
:disabled="disabledForm"
|
||||
>
|
||||
</at-input>
|
||||
</validation-provider>
|
||||
<at-button
|
||||
class="btn"
|
||||
native-type="submit"
|
||||
type="primary"
|
||||
:disabled="invalid || disabledForm"
|
||||
@click="resetPassword"
|
||||
>{{ $t('reset.reset_password') }}</at-button
|
||||
>
|
||||
</validation-observer>
|
||||
</div>
|
||||
|
||||
<div v-if="currentStep == 1" class="col-6 col-offset-9">
|
||||
<div class="header-text">
|
||||
<i class="icon icon-mail"></i>
|
||||
<h2 class="header-text__title">
|
||||
{{ $t('reset.tabs.check_email.title') }}
|
||||
</h2>
|
||||
<p class="header-text__subtitle">
|
||||
{{ $t('reset.tabs.check_email.subtitle') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="currentStep == 2" class="col-6 col-offset-9">
|
||||
<validation-observer v-if="isValidToken" ref="form" v-slot="{ invalid }">
|
||||
<div class="header-text">
|
||||
<h2 class="header-text__title">
|
||||
{{ $t('reset.tabs.new_password.title') }}
|
||||
</h2>
|
||||
<p class="header-text__subtitle">
|
||||
{{ $t('reset.tabs.new_password.subtitle') }}
|
||||
</p>
|
||||
</div>
|
||||
<validation-provider
|
||||
v-slot="{ errors }"
|
||||
rules="required|min:6"
|
||||
:name="$t('field.password')"
|
||||
vid="password"
|
||||
>
|
||||
<small>{{ $t('field.password') }}</small>
|
||||
<at-input
|
||||
v-model="password"
|
||||
:name="$t('field.password')"
|
||||
:status="errors.length > 0 ? 'error' : ''"
|
||||
:placeholder="$t('field.password')"
|
||||
icon="lock"
|
||||
type="password"
|
||||
:disabled="disabledForm"
|
||||
>
|
||||
</at-input>
|
||||
<p class="error-message">
|
||||
<small>{{ errors[0] }}</small>
|
||||
</p>
|
||||
</validation-provider>
|
||||
<validation-provider
|
||||
v-slot="{ errors }"
|
||||
rules="required|min:6|confirmed:password"
|
||||
:name="$t('reset.confirm_password')"
|
||||
vid="passwordConfirmation"
|
||||
>
|
||||
<small>{{ $t('reset.confirm_password') }}</small>
|
||||
<at-input
|
||||
v-model="passwordConfirmation"
|
||||
name="passwordConfirmation"
|
||||
:status="errors.length > 0 ? 'error' : ''"
|
||||
:placeholder="$t('reset.confirm_password')"
|
||||
icon="lock"
|
||||
type="password"
|
||||
:disabled="disabledForm"
|
||||
>
|
||||
</at-input>
|
||||
<p class="error-message">
|
||||
<small>{{ errors[0] }}</small>
|
||||
</p>
|
||||
</validation-provider>
|
||||
<at-button
|
||||
class="btn"
|
||||
native-type="submit"
|
||||
type="primary"
|
||||
:disabled="invalid || disabledForm"
|
||||
@click="submitNewPassword"
|
||||
>{{ $t('control.submit') }}</at-button
|
||||
>
|
||||
</validation-observer>
|
||||
<div v-else>
|
||||
<div class="header-text">
|
||||
<h2 class="header-text__title">
|
||||
{{ $t('reset.page_is_not_available') }}
|
||||
</h2>
|
||||
<router-link to="/auth/login">{{ $t('reset.go_away') }}</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="currentStep == 3" class="col-6 col-offset-9">
|
||||
<div class="header-text">
|
||||
<i class="icon icon-check"></i>
|
||||
<h2 class="header-text__title">
|
||||
{{ $t('reset.tabs.success.title') }}
|
||||
</h2>
|
||||
<p class="header-text__subtitle"></p>
|
||||
<router-link to="/auth/login">{{ $t('reset.go_away') }}</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.content-wrapper -->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ValidationObserver, ValidationProvider } from 'vee-validate';
|
||||
import AuthService from '@/services/auth.service';
|
||||
|
||||
export default {
|
||||
name: 'ResetPassword',
|
||||
components: {
|
||||
ValidationProvider,
|
||||
ValidationObserver,
|
||||
},
|
||||
created() {
|
||||
if (this.$route.query.token && this.$route.query.email) {
|
||||
this.currentStep = 2;
|
||||
this.validateToken();
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
email: null,
|
||||
password: null,
|
||||
passwordConfirmation: null,
|
||||
currentStep: 0,
|
||||
disabledForm: false,
|
||||
isValidToken: true,
|
||||
authService: new AuthService(),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async resetPassword() {
|
||||
this.disabledForm = true;
|
||||
|
||||
const payload = {
|
||||
email: this.email,
|
||||
};
|
||||
|
||||
try {
|
||||
await this.authService.resetPasswordRequest(payload);
|
||||
this.currentStep = 1;
|
||||
} catch (e) {
|
||||
//
|
||||
} finally {
|
||||
this.disabledForm = false;
|
||||
}
|
||||
},
|
||||
async validateToken() {
|
||||
const payload = {
|
||||
email: this.$route.query.email,
|
||||
token: this.$route.query.token,
|
||||
};
|
||||
|
||||
try {
|
||||
await this.authService.resetPasswordValidateToken(payload);
|
||||
this.isValidToken = true;
|
||||
} catch (e) {
|
||||
this.isValidToken = false;
|
||||
}
|
||||
},
|
||||
async submitNewPassword() {
|
||||
this.disabledForm = true;
|
||||
|
||||
const payload = {
|
||||
email: this.$route.query.email,
|
||||
token: this.$route.query.token,
|
||||
password: this.password,
|
||||
password_confirmation: this.passwordConfirmation,
|
||||
};
|
||||
|
||||
try {
|
||||
await this.authService.resetPasswordProcess(payload);
|
||||
this.currentStep = 3;
|
||||
this.disabledForm = false;
|
||||
} catch (e) {
|
||||
//
|
||||
} finally {
|
||||
this.disabledForm = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.steps {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.header-text {
|
||||
text-align: center;
|
||||
|
||||
&__title {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 92px;
|
||||
|
||||
&-mail {
|
||||
color: $blue-2;
|
||||
}
|
||||
|
||||
&-check {
|
||||
color: $green-1;
|
||||
}
|
||||
}
|
||||
|
||||
.error-message {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.at-input {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user