web/: add custom linter for requiring @vue/component
Also ignore coverage folder for linting. I had to add a new .stylelintignore file, because ignoreFiles property was not properly working. Change-Id: Iadd99b64eadd9c4103f750519263113ae8780ce1
This commit is contained in:
parent
ee4361fe0d
commit
e5977ec849
46
web/eslint-storj/index.js
Normal file
46
web/eslint-storj/index.js
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright (C) 2021 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
module.exports = {
|
||||
rules: {
|
||||
"vue/require-annotation": {
|
||||
meta: {
|
||||
fixable: "code",
|
||||
},
|
||||
create: function(context) {
|
||||
return {
|
||||
Decorator(node) {
|
||||
let isComponent = false;
|
||||
const expr = node.expression;
|
||||
if(expr.name === "Component"){
|
||||
isComponent = true;
|
||||
} else if (expr.callee && expr.callee.name === "Component"){
|
||||
isComponent = true;
|
||||
}
|
||||
if(!isComponent){ return; }
|
||||
|
||||
const commentsBefore = context.getCommentsBefore(node);
|
||||
const decoratorLine = node.loc.start.line;
|
||||
let annotated = false;
|
||||
commentsBefore.forEach(comment => {
|
||||
if(comment.loc.start.line === decoratorLine - 1){
|
||||
if(comment.value.trim() === "@vue/component") {
|
||||
annotated = true;
|
||||
}
|
||||
}
|
||||
})
|
||||
if(!annotated){
|
||||
context.report({
|
||||
node: node,
|
||||
message: '@Component requires // @vue/component',
|
||||
fix: function(fixer) {
|
||||
return fixer.insertTextBefore(node, "// @vue/component\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
5
web/eslint-storj/package.json
Normal file
5
web/eslint-storj/package.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "eslint-plugin-storj",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js"
|
||||
}
|
@ -14,7 +14,10 @@ module.exports = {
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020
|
||||
},
|
||||
plugins: ["storj"],
|
||||
rules: {
|
||||
"linebreak-style": ["error", "unix"],
|
||||
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
|
||||
@ -41,5 +44,7 @@ module.exports = {
|
||||
"vue/no-useless-v-bind": ["warn"],
|
||||
|
||||
'vue/no-unregistered-components': ['warn', { ignorePatterns: ['router-link', 'router-view'] }],
|
||||
|
||||
'storj/vue/require-annotation': 'warn',
|
||||
},
|
||||
}
|
11
web/multinode/.stylelintignore
Normal file
11
web/multinode/.stylelintignore
Normal file
@ -0,0 +1,11 @@
|
||||
*.*
|
||||
!*.vue
|
||||
!*.css
|
||||
!*.sss
|
||||
!*.less
|
||||
!*.scss
|
||||
!*.sass
|
||||
|
||||
dist
|
||||
node_modules
|
||||
coverage
|
@ -11,7 +11,6 @@ module.exports = {
|
||||
"stylelint-scss"
|
||||
],
|
||||
"extends": "stylelint-config-standard",
|
||||
"ignoreFiles": ["dist/**"],
|
||||
"rules": {
|
||||
"indentation": 4,
|
||||
"string-quotes": "single",
|
||||
|
14
web/multinode/package-lock.json
generated
14
web/multinode/package-lock.json
generated
@ -5,6 +5,7 @@
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "multinode",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"chart.js": "2.9.4",
|
||||
@ -33,6 +34,7 @@
|
||||
"compression-webpack-plugin": "6.0.0",
|
||||
"core-js": "3.6.5",
|
||||
"eslint": "6.7.2",
|
||||
"eslint-plugin-storj": "file:../eslint-storj",
|
||||
"eslint-plugin-vue": "7.16.0",
|
||||
"jest-fetch-mock": "3.0.0",
|
||||
"sass": "1.37.0",
|
||||
@ -49,6 +51,11 @@
|
||||
"vue-template-compiler": "2.6.11"
|
||||
}
|
||||
},
|
||||
"../eslint-storj": {
|
||||
"name": "eslint-plugin-storj",
|
||||
"version": "1.0.0",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz",
|
||||
@ -10722,6 +10729,10 @@
|
||||
"rimraf": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-storj": {
|
||||
"resolved": "../eslint-storj",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/eslint-plugin-vue": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.16.0.tgz",
|
||||
@ -36217,6 +36228,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"eslint-plugin-storj": {
|
||||
"version": "file:../eslint-storj"
|
||||
},
|
||||
"eslint-plugin-vue": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.16.0.tgz",
|
||||
|
@ -4,8 +4,8 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"lint": "vue-cli-service lint --max-warnings 0 --fix && stylelint --max-warnings 0 \"**/*.{vue,css,sss,less,scss,sass}\" --fix",
|
||||
"lint-ci": "vue-cli-service lint --max-warnings 0 --no-fix && stylelint --max-warnings 0 --no-fix \"**/*.{vue,css,sss,less,scss,sass}\"",
|
||||
"lint": "vue-cli-service lint --max-warnings 0 --fix && stylelint . --max-warnings 0 --fix",
|
||||
"lint-ci": "vue-cli-service lint --max-warnings 0 --no-fix && stylelint . --max-warnings 0 --no-fix",
|
||||
"build": "vue-cli-service build",
|
||||
"dev": "vue-cli-service build --mode development --watch",
|
||||
"test": "vue-cli-service test:unit"
|
||||
@ -38,6 +38,7 @@
|
||||
"core-js": "3.6.5",
|
||||
"eslint": "6.7.2",
|
||||
"eslint-plugin-vue": "7.16.0",
|
||||
"eslint-plugin-storj": "file:../eslint-storj",
|
||||
"jest-fetch-mock": "3.0.0",
|
||||
"sass": "1.37.0",
|
||||
"sass-loader": "8.0.0",
|
||||
|
@ -14,7 +14,10 @@ module.exports = {
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020
|
||||
},
|
||||
plugins: ["storj"],
|
||||
rules: {
|
||||
"linebreak-style": ["error", "unix"],
|
||||
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
|
||||
@ -41,5 +44,7 @@ module.exports = {
|
||||
"vue/no-useless-v-bind": ["warn"],
|
||||
|
||||
'vue/no-unregistered-components': ['warn', { ignorePatterns: ['router-link', 'router-view'] }],
|
||||
|
||||
'storj/vue/require-annotation': 'warn',
|
||||
},
|
||||
}
|
13
web/satellite/.stylelintignore
Normal file
13
web/satellite/.stylelintignore
Normal file
@ -0,0 +1,13 @@
|
||||
*.*
|
||||
!*.vue
|
||||
!*.css
|
||||
!*.sss
|
||||
!*.less
|
||||
!*.scss
|
||||
!*.sass
|
||||
|
||||
dist
|
||||
node_modules
|
||||
coverage
|
||||
Dockerfile
|
||||
entrypoint
|
@ -11,7 +11,6 @@ module.exports = {
|
||||
"stylelint-scss"
|
||||
],
|
||||
"extends": "stylelint-config-standard",
|
||||
"ignoreFiles": ["dist/**"],
|
||||
"rules": {
|
||||
"indentation": 4,
|
||||
"string-quotes": "single",
|
||||
|
13
web/satellite/package-lock.json
generated
13
web/satellite/package-lock.json
generated
@ -52,6 +52,7 @@
|
||||
"babel-eslint": "10.1.0",
|
||||
"compression-webpack-plugin": "6.0.0",
|
||||
"eslint": "6.7.2",
|
||||
"eslint-plugin-storj": "file:../eslint-storj",
|
||||
"eslint-plugin-vue": "7.16.0",
|
||||
"jest-fetch-mock": "3.0.3",
|
||||
"sass": "1.37.0",
|
||||
@ -68,6 +69,11 @@
|
||||
"worker-plugin": "5.0.0"
|
||||
}
|
||||
},
|
||||
"../eslint-storj": {
|
||||
"name": "eslint-plugin-storj",
|
||||
"version": "1.0.0",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz",
|
||||
@ -11802,6 +11808,10 @@
|
||||
"rimraf": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-storj": {
|
||||
"resolved": "../eslint-storj",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/eslint-plugin-vue": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.16.0.tgz",
|
||||
@ -39522,6 +39532,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"eslint-plugin-storj": {
|
||||
"version": "file:../eslint-storj"
|
||||
},
|
||||
"eslint-plugin-vue": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.16.0.tgz",
|
||||
|
@ -4,8 +4,8 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"lint": "vue-cli-service lint --max-warnings 0 && stylelint --max-warnings 0 \"**/*.{vue,css,sss,less,scss,sass}\" --fix",
|
||||
"lint-ci": "vue-cli-service lint --max-warnings 0 --no-fix && stylelint --max-warnings 0 --no-fix \"**/*.{vue,css,sss,less,scss,sass}\"",
|
||||
"lint": "vue-cli-service lint --max-warnings 0 --fix && stylelint . --max-warnings 0 --fix",
|
||||
"lint-ci": "vue-cli-service lint --max-warnings 0 --no-fix && stylelint . --max-warnings 0 --no-fix",
|
||||
"build": "vue-cli-service build",
|
||||
"wasm": "chmod +x ./scripts/build-wasm.sh && ./scripts/build-wasm.sh",
|
||||
"dev": "vue-cli-service build --mode development --watch",
|
||||
@ -57,6 +57,7 @@
|
||||
"compression-webpack-plugin": "6.0.0",
|
||||
"eslint": "6.7.2",
|
||||
"eslint-plugin-vue": "7.16.0",
|
||||
"eslint-plugin-storj": "file:../eslint-storj",
|
||||
"jest-fetch-mock": "3.0.3",
|
||||
"sass": "1.37.0",
|
||||
"sass-loader": "10.0.2",
|
||||
|
@ -1,358 +1,358 @@
|
||||
// Copyright (C) 2021 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="forgot-area" @keyup.enter="onSendConfigurations">
|
||||
<div class="forgot-area__logo-wrapper">
|
||||
<LogoIcon class="forgot-area__logo-wrapper__logo" @click="onLogoClick" />
|
||||
</div>
|
||||
<div class="forgot-area__content-area">
|
||||
<div class="forgot-area__content-area__container">
|
||||
<div class="forgot-area__content-area__container__title-area">
|
||||
<h1 class="forgot-area__content-area__container__title-area__title">Reset Password</h1>
|
||||
<div class="forgot-area__expand" @click.stop="toggleDropdown">
|
||||
<span class="forgot-area__expand__value">{{ satelliteName }}</span>
|
||||
<BottomArrowIcon />
|
||||
<div v-if="isDropdownShown" v-click-outside="closeDropdown" class="forgot-area__expand__dropdown">
|
||||
<div class="forgot-area__expand__dropdown__item" @click.stop="closeDropdown">
|
||||
<SelectedCheckIcon />
|
||||
<span class="forgot-area__expand__dropdown__item__name">{{ satelliteName }}</span>
|
||||
</div>
|
||||
<a v-for="sat in partneredSatellites" :key="sat.id" class="forgot-area__expand__dropdown__item" :href="sat.address + '/forgot-password'">
|
||||
{{ sat.name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="forgot-area__content-area__container__message">If you’ve forgotten your account password, you can reset it here. Make sure you’re signing in to the right satellite.</p>
|
||||
<div class="forgot-area__content-area__container__input-wrapper">
|
||||
<HeaderlessInput
|
||||
class="full-input"
|
||||
label="Email Address"
|
||||
placeholder="example@email.com"
|
||||
:error="emailError"
|
||||
width="calc(100% - 2px)"
|
||||
height="46px"
|
||||
@setData="setEmail"
|
||||
/>
|
||||
</div>
|
||||
<p class="forgot-area__content-area__container__button" @click.prevent="onSendConfigurations">Reset Password</p>
|
||||
</div>
|
||||
<div class="forgot-area__content-area__login-container">
|
||||
<router-link :to="loginPath" class="forgot-area__content-area__login-container__link">
|
||||
Back to Login
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import HeaderlessInput from '@/components/common/HeaderlessInput.vue';
|
||||
|
||||
import BottomArrowIcon from '@/../static/images/common/lightBottomArrow.svg';
|
||||
import SelectedCheckIcon from '@/../static/images/common/selectedCheck.svg';
|
||||
import LogoIcon from '@/../static/images/logo.svg';
|
||||
|
||||
import { AuthHttpApi } from '@/api/auth';
|
||||
import { RouteConfig } from '@/router';
|
||||
import { PartneredSatellite } from '@/types/common';
|
||||
import { Validator } from '@/utils/validation';
|
||||
|
||||
// @vue/component
|
||||
@Component({
|
||||
components: {
|
||||
HeaderlessInput,
|
||||
BottomArrowIcon,
|
||||
SelectedCheckIcon,
|
||||
LogoIcon,
|
||||
},
|
||||
})
|
||||
export default class ForgotPassword extends Vue {
|
||||
private email = '';
|
||||
private emailError = '';
|
||||
|
||||
private readonly auth: AuthHttpApi = new AuthHttpApi();
|
||||
|
||||
// tardigrade logic
|
||||
public isDropdownShown = false;
|
||||
|
||||
public readonly loginPath: string = RouteConfig.Login.path;
|
||||
|
||||
/**
|
||||
* Sets the email field to the given value.
|
||||
*/
|
||||
public setEmail(value: string): void {
|
||||
this.email = value;
|
||||
this.emailError = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the current satellite.
|
||||
*/
|
||||
public get satelliteName(): string {
|
||||
return this.$store.state.appStateModule.satelliteName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about partnered satellites, including name and signup link.
|
||||
*/
|
||||
public get partneredSatellites(): PartneredSatellite[] {
|
||||
return this.$store.state.appStateModule.partneredSatellites;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles satellite selection dropdown visibility (Tardigrade).
|
||||
*/
|
||||
public toggleDropdown(): void {
|
||||
this.isDropdownShown = !this.isDropdownShown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes satellite selection dropdown (Tardigrade).
|
||||
*/
|
||||
public closeDropdown(): void {
|
||||
this.isDropdownShown = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends recovery password email.
|
||||
*/
|
||||
public async onSendConfigurations(): Promise<void> {
|
||||
if (!this.validateFields()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.auth.forgotPassword(this.email);
|
||||
} catch (error) {
|
||||
await this.$notify.error(error.message);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.$notify.success('Please look for instructions at your email');
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes location to Login route.
|
||||
*/
|
||||
public onBackToLoginClick(): void {
|
||||
this.$router.push(RouteConfig.Login.path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the page.
|
||||
*/
|
||||
public onLogoClick(): void {
|
||||
location.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the email address is properly structured.
|
||||
*/
|
||||
private validateFields(): boolean {
|
||||
const isEmailValid = Validator.email(this.email.trim());
|
||||
|
||||
if (!isEmailValid) {
|
||||
this.emailError = 'Invalid Email';
|
||||
}
|
||||
|
||||
return isEmailValid;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.forgot-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
font-family: 'font_regular', sans-serif;
|
||||
background-color: #f5f6fa;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
min-height: 100%;
|
||||
overflow-y: scroll;
|
||||
|
||||
&__logo-wrapper {
|
||||
text-align: center;
|
||||
margin: 70px 0;
|
||||
|
||||
&__logo {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&__expand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
&__value {
|
||||
font-size: 16px;
|
||||
line-height: 21px;
|
||||
color: #acbace;
|
||||
margin-right: 10px;
|
||||
font-family: 'font_regular', sans-serif;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&__dropdown {
|
||||
position: absolute;
|
||||
top: 35px;
|
||||
left: 0;
|
||||
background-color: #fff;
|
||||
z-index: 1000;
|
||||
border: 1px solid #c5cbdb;
|
||||
box-shadow: 0 8px 34px rgba(161, 173, 185, 0.41);
|
||||
border-radius: 6px;
|
||||
min-width: 250px;
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 12px 25px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #7e8b9c;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
|
||||
&__name {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
margin-left: 15px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #7e8b9c;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #f2f2f6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__content-area {
|
||||
width: 100%;
|
||||
padding: 0 20px;
|
||||
margin-bottom: 50px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
|
||||
&__container {
|
||||
width: 610px;
|
||||
padding: 60px 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
border-radius: 20px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&__title-area {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
&__title {
|
||||
font-size: 24px;
|
||||
margin: 10px 0;
|
||||
letter-spacing: -0.100741px;
|
||||
color: #252525;
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-weight: 800;
|
||||
}
|
||||
}
|
||||
|
||||
&__input-wrapper {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
&__button {
|
||||
font-family: 'font_regular', sans-serif;
|
||||
font-weight: 700;
|
||||
margin-top: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #376fff;
|
||||
border-radius: 50px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
|
||||
&:hover {
|
||||
background-color: #0059d0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__login-container {
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 50px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
|
||||
&__link {
|
||||
font-family: 'font_medium', sans-serif;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
color: #376fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 750px) {
|
||||
|
||||
.forgot-area {
|
||||
|
||||
&__content-area {
|
||||
|
||||
&__container {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&__expand {
|
||||
|
||||
&__dropdown {
|
||||
left: -200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 414px) {
|
||||
|
||||
.forgot-area {
|
||||
|
||||
&__logo-wrapper {
|
||||
margin: 40px;
|
||||
}
|
||||
|
||||
&__content-area {
|
||||
padding: 0;
|
||||
|
||||
&__container {
|
||||
padding: 60px 60px;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
// Copyright (C) 2021 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="forgot-area" @keyup.enter="onSendConfigurations">
|
||||
<div class="forgot-area__logo-wrapper">
|
||||
<LogoIcon class="forgot-area__logo-wrapper__logo" @click="onLogoClick" />
|
||||
</div>
|
||||
<div class="forgot-area__content-area">
|
||||
<div class="forgot-area__content-area__container">
|
||||
<div class="forgot-area__content-area__container__title-area">
|
||||
<h1 class="forgot-area__content-area__container__title-area__title">Reset Password</h1>
|
||||
<div class="forgot-area__expand" @click.stop="toggleDropdown">
|
||||
<span class="forgot-area__expand__value">{{ satelliteName }}</span>
|
||||
<BottomArrowIcon />
|
||||
<div v-if="isDropdownShown" v-click-outside="closeDropdown" class="forgot-area__expand__dropdown">
|
||||
<div class="forgot-area__expand__dropdown__item" @click.stop="closeDropdown">
|
||||
<SelectedCheckIcon />
|
||||
<span class="forgot-area__expand__dropdown__item__name">{{ satelliteName }}</span>
|
||||
</div>
|
||||
<a v-for="sat in partneredSatellites" :key="sat.id" class="forgot-area__expand__dropdown__item" :href="sat.address + '/forgot-password'">
|
||||
{{ sat.name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="forgot-area__content-area__container__message">If you’ve forgotten your account password, you can reset it here. Make sure you’re signing in to the right satellite.</p>
|
||||
<div class="forgot-area__content-area__container__input-wrapper">
|
||||
<HeaderlessInput
|
||||
class="full-input"
|
||||
label="Email Address"
|
||||
placeholder="example@email.com"
|
||||
:error="emailError"
|
||||
width="calc(100% - 2px)"
|
||||
height="46px"
|
||||
@setData="setEmail"
|
||||
/>
|
||||
</div>
|
||||
<p class="forgot-area__content-area__container__button" @click.prevent="onSendConfigurations">Reset Password</p>
|
||||
</div>
|
||||
<div class="forgot-area__content-area__login-container">
|
||||
<router-link :to="loginPath" class="forgot-area__content-area__login-container__link">
|
||||
Back to Login
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import HeaderlessInput from '@/components/common/HeaderlessInput.vue';
|
||||
|
||||
import BottomArrowIcon from '@/../static/images/common/lightBottomArrow.svg';
|
||||
import SelectedCheckIcon from '@/../static/images/common/selectedCheck.svg';
|
||||
import LogoIcon from '@/../static/images/logo.svg';
|
||||
|
||||
import { AuthHttpApi } from '@/api/auth';
|
||||
import { RouteConfig } from '@/router';
|
||||
import { PartneredSatellite } from '@/types/common';
|
||||
import { Validator } from '@/utils/validation';
|
||||
|
||||
// @vue/component
|
||||
@Component({
|
||||
components: {
|
||||
HeaderlessInput,
|
||||
BottomArrowIcon,
|
||||
SelectedCheckIcon,
|
||||
LogoIcon,
|
||||
},
|
||||
})
|
||||
export default class ForgotPassword extends Vue {
|
||||
private email = '';
|
||||
private emailError = '';
|
||||
|
||||
private readonly auth: AuthHttpApi = new AuthHttpApi();
|
||||
|
||||
// tardigrade logic
|
||||
public isDropdownShown = false;
|
||||
|
||||
public readonly loginPath: string = RouteConfig.Login.path;
|
||||
|
||||
/**
|
||||
* Sets the email field to the given value.
|
||||
*/
|
||||
public setEmail(value: string): void {
|
||||
this.email = value;
|
||||
this.emailError = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the current satellite.
|
||||
*/
|
||||
public get satelliteName(): string {
|
||||
return this.$store.state.appStateModule.satelliteName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about partnered satellites, including name and signup link.
|
||||
*/
|
||||
public get partneredSatellites(): PartneredSatellite[] {
|
||||
return this.$store.state.appStateModule.partneredSatellites;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles satellite selection dropdown visibility (Tardigrade).
|
||||
*/
|
||||
public toggleDropdown(): void {
|
||||
this.isDropdownShown = !this.isDropdownShown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes satellite selection dropdown (Tardigrade).
|
||||
*/
|
||||
public closeDropdown(): void {
|
||||
this.isDropdownShown = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends recovery password email.
|
||||
*/
|
||||
public async onSendConfigurations(): Promise<void> {
|
||||
if (!this.validateFields()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.auth.forgotPassword(this.email);
|
||||
} catch (error) {
|
||||
await this.$notify.error(error.message);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.$notify.success('Please look for instructions at your email');
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes location to Login route.
|
||||
*/
|
||||
public onBackToLoginClick(): void {
|
||||
this.$router.push(RouteConfig.Login.path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the page.
|
||||
*/
|
||||
public onLogoClick(): void {
|
||||
location.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the email address is properly structured.
|
||||
*/
|
||||
private validateFields(): boolean {
|
||||
const isEmailValid = Validator.email(this.email.trim());
|
||||
|
||||
if (!isEmailValid) {
|
||||
this.emailError = 'Invalid Email';
|
||||
}
|
||||
|
||||
return isEmailValid;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.forgot-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
font-family: 'font_regular', sans-serif;
|
||||
background-color: #f5f6fa;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
min-height: 100%;
|
||||
overflow-y: scroll;
|
||||
|
||||
&__logo-wrapper {
|
||||
text-align: center;
|
||||
margin: 70px 0;
|
||||
|
||||
&__logo {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&__expand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
&__value {
|
||||
font-size: 16px;
|
||||
line-height: 21px;
|
||||
color: #acbace;
|
||||
margin-right: 10px;
|
||||
font-family: 'font_regular', sans-serif;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&__dropdown {
|
||||
position: absolute;
|
||||
top: 35px;
|
||||
left: 0;
|
||||
background-color: #fff;
|
||||
z-index: 1000;
|
||||
border: 1px solid #c5cbdb;
|
||||
box-shadow: 0 8px 34px rgba(161, 173, 185, 0.41);
|
||||
border-radius: 6px;
|
||||
min-width: 250px;
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 12px 25px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #7e8b9c;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
|
||||
&__name {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
margin-left: 15px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #7e8b9c;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #f2f2f6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__content-area {
|
||||
width: 100%;
|
||||
padding: 0 20px;
|
||||
margin-bottom: 50px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
|
||||
&__container {
|
||||
width: 610px;
|
||||
padding: 60px 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
border-radius: 20px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&__title-area {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
&__title {
|
||||
font-size: 24px;
|
||||
margin: 10px 0;
|
||||
letter-spacing: -0.100741px;
|
||||
color: #252525;
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-weight: 800;
|
||||
}
|
||||
}
|
||||
|
||||
&__input-wrapper {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
&__button {
|
||||
font-family: 'font_regular', sans-serif;
|
||||
font-weight: 700;
|
||||
margin-top: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #376fff;
|
||||
border-radius: 50px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
|
||||
&:hover {
|
||||
background-color: #0059d0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__login-container {
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 50px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
|
||||
&__link {
|
||||
font-family: 'font_medium', sans-serif;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
color: #376fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 750px) {
|
||||
|
||||
.forgot-area {
|
||||
|
||||
&__content-area {
|
||||
|
||||
&__container {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&__expand {
|
||||
|
||||
&__dropdown {
|
||||
left: -200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 414px) {
|
||||
|
||||
.forgot-area {
|
||||
|
||||
&__logo-wrapper {
|
||||
margin: 40px;
|
||||
}
|
||||
|
||||
&__content-area {
|
||||
padding: 0;
|
||||
|
||||
&__container {
|
||||
padding: 60px 60px;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,344 +1,344 @@
|
||||
// Copyright (C) 2021 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="reset-area" @keyup.enter="onResetClick">
|
||||
<div class="reset-area__logo-wrapper">
|
||||
<LogoIcon class="reset-area__logo-wrapper_logo" @click="onLogoClick" />
|
||||
</div>
|
||||
<div class="reset-area__content-area">
|
||||
<div class="reset-area__content-area__container" :class="{'success': isSuccessfulPasswordResetShown}">
|
||||
<template v-if="!isSuccessfulPasswordResetShown">
|
||||
<h1 class="reset-area__content-area__container__title">Reset Password</h1>
|
||||
<p class="reset-area__content-area__container__message">Please enter your new password.</p>
|
||||
<div class="reset-area__content-area__container__input-wrapper password">
|
||||
<HeaderlessInput
|
||||
label="Password"
|
||||
placeholder="Enter Password"
|
||||
:error="passwordError"
|
||||
width="100%"
|
||||
height="46px"
|
||||
is-password="true"
|
||||
@setData="setPassword"
|
||||
@showPasswordStrength="showPasswordStrength"
|
||||
@hidePasswordStrength="hidePasswordStrength"
|
||||
/>
|
||||
<PasswordStrength
|
||||
:password-string="password"
|
||||
:is-shown="isPasswordStrengthShown"
|
||||
/>
|
||||
</div>
|
||||
<div class="reset-area__content-area__container__input-wrapper">
|
||||
<HeaderlessInput
|
||||
label="Retype Password"
|
||||
placeholder="Retype Password"
|
||||
:error="repeatedPasswordError"
|
||||
width="100%"
|
||||
height="46px"
|
||||
is-password="true"
|
||||
@setData="setRepeatedPassword"
|
||||
/>
|
||||
</div>
|
||||
<p class="reset-area__content-area__container__button" @click.prevent="onResetClick">Reset Password</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<KeyIcon />
|
||||
<h2 class="reset-area__content-area__container__title success">Success!</h2>
|
||||
<p class="reset-area__content-area__container__sub-title">
|
||||
You have successfully changed your password.
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
<router-link :to="loginPath" class="reset-area__content-area__login-link">
|
||||
Back to Login
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import HeaderlessInput from '@/components/common/HeaderlessInput.vue';
|
||||
import PasswordStrength from '@/components/common/PasswordStrength.vue';
|
||||
|
||||
import LogoIcon from '@/../static/images/logo.svg';
|
||||
import KeyIcon from '@/../static/images/resetPassword/success.svg';
|
||||
|
||||
import { AuthHttpApi } from '@/api/auth';
|
||||
import { RouteConfig } from '@/router';
|
||||
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
|
||||
import { Validator } from '@/utils/validation';
|
||||
|
||||
// @vue/component
|
||||
@Component({
|
||||
components: {
|
||||
LogoIcon,
|
||||
HeaderlessInput,
|
||||
PasswordStrength,
|
||||
KeyIcon,
|
||||
},
|
||||
})
|
||||
|
||||
export default class ResetPassword extends Vue {
|
||||
private token = '';
|
||||
private password = '';
|
||||
private repeatedPassword = '';
|
||||
|
||||
private passwordError = '';
|
||||
private repeatedPasswordError = '';
|
||||
private isLoading = false;
|
||||
|
||||
private readonly auth: AuthHttpApi = new AuthHttpApi();
|
||||
|
||||
public isPasswordStrengthShown = false;
|
||||
|
||||
public readonly loginPath: string = RouteConfig.Login.path;
|
||||
|
||||
/**
|
||||
* Lifecycle hook on component destroy.
|
||||
* Sets view to default state.
|
||||
*/
|
||||
public beforeDestroy(): void {
|
||||
if (this.isSuccessfulPasswordResetShown) {
|
||||
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_SUCCESSFUL_PASSWORD_RESET);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle hook after initial render.
|
||||
* Initializes recovery token from route param
|
||||
* and redirects to login if token doesn't exist.
|
||||
*/
|
||||
public mounted(): void {
|
||||
if (this.$route.query.token) {
|
||||
this.token = this.$route.query.token.toString();
|
||||
} else {
|
||||
this.$router.push(RouteConfig.Login.path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the successful password reset area is shown.
|
||||
*/
|
||||
public get isSuccessfulPasswordResetShown() : boolean {
|
||||
return this.$store.state.appStateModule.appState.isSuccessfulPasswordResetShown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates input fields and requests password reset.
|
||||
*/
|
||||
public async onResetClick(): Promise<void> {
|
||||
if (this.isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
if (!this.validateFields()) {
|
||||
this.isLoading = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.auth.resetPassword(this.token, this.password);
|
||||
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_SUCCESSFUL_PASSWORD_RESET);
|
||||
} catch (error) {
|
||||
await this.$notify.error(error.message);
|
||||
}
|
||||
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates input values to satisfy expected rules.
|
||||
*/
|
||||
private validateFields(): boolean {
|
||||
let isNoErrors = true;
|
||||
|
||||
if (!Validator.password(this.password)) {
|
||||
this.passwordError = 'Invalid password';
|
||||
isNoErrors = false;
|
||||
}
|
||||
|
||||
if (this.repeatedPassword !== this.password) {
|
||||
this.repeatedPasswordError = 'Password doesn\'t match';
|
||||
isNoErrors = false;
|
||||
}
|
||||
|
||||
return isNoErrors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes password strength container visible.
|
||||
*/
|
||||
public showPasswordStrength(): void {
|
||||
this.isPasswordStrengthShown = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides password strength container.
|
||||
*/
|
||||
public hidePasswordStrength(): void {
|
||||
this.isPasswordStrengthShown = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the page.
|
||||
*/
|
||||
public onLogoClick(): void {
|
||||
location.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets user's password field from value string.
|
||||
*/
|
||||
public setPassword(value: string): void {
|
||||
this.password = value.trim();
|
||||
this.passwordError = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets user's repeat password field from value string.
|
||||
*/
|
||||
public setRepeatedPassword(value: string): void {
|
||||
this.repeatedPassword = value.trim();
|
||||
this.repeatedPasswordError = '';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.reset-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
font-family: 'font_regular', sans-serif;
|
||||
background-color: #f5f6fa;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
min-height: 100%;
|
||||
overflow-y: scroll;
|
||||
|
||||
&__logo-wrapper {
|
||||
text-align: center;
|
||||
margin: 70px 0;
|
||||
|
||||
&__logo {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&__content-area {
|
||||
width: 100%;
|
||||
padding: 0 20px;
|
||||
margin-bottom: 50px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
|
||||
&__container {
|
||||
width: 610px;
|
||||
padding: 60px 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
border-radius: 20px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.success {
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__input-wrapper {
|
||||
margin-top: 20px;
|
||||
|
||||
&.password {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 24px;
|
||||
margin: 10px 0;
|
||||
letter-spacing: -0.100741px;
|
||||
color: #252525;
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-weight: 800;
|
||||
|
||||
&.success {
|
||||
font-size: 40px;
|
||||
margin: 25px 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__button {
|
||||
font-family: 'font_regular', sans-serif;
|
||||
font-weight: 700;
|
||||
margin-top: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #376fff;
|
||||
border-radius: 50px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
|
||||
&:hover {
|
||||
background-color: #0059d0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__login-link {
|
||||
font-family: 'font_medium', sans-serif;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
color: #376fff;
|
||||
margin-top: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 750px) {
|
||||
|
||||
.reset-area {
|
||||
|
||||
&__content-area {
|
||||
|
||||
&__container {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 414px) {
|
||||
|
||||
.reset-area {
|
||||
|
||||
&__logo-wrapper {
|
||||
margin: 40px;
|
||||
}
|
||||
|
||||
&__content-area {
|
||||
padding: 0;
|
||||
|
||||
&__container {
|
||||
padding: 60px 60px;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
// Copyright (C) 2021 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="reset-area" @keyup.enter="onResetClick">
|
||||
<div class="reset-area__logo-wrapper">
|
||||
<LogoIcon class="reset-area__logo-wrapper_logo" @click="onLogoClick" />
|
||||
</div>
|
||||
<div class="reset-area__content-area">
|
||||
<div class="reset-area__content-area__container" :class="{'success': isSuccessfulPasswordResetShown}">
|
||||
<template v-if="!isSuccessfulPasswordResetShown">
|
||||
<h1 class="reset-area__content-area__container__title">Reset Password</h1>
|
||||
<p class="reset-area__content-area__container__message">Please enter your new password.</p>
|
||||
<div class="reset-area__content-area__container__input-wrapper password">
|
||||
<HeaderlessInput
|
||||
label="Password"
|
||||
placeholder="Enter Password"
|
||||
:error="passwordError"
|
||||
width="100%"
|
||||
height="46px"
|
||||
is-password="true"
|
||||
@setData="setPassword"
|
||||
@showPasswordStrength="showPasswordStrength"
|
||||
@hidePasswordStrength="hidePasswordStrength"
|
||||
/>
|
||||
<PasswordStrength
|
||||
:password-string="password"
|
||||
:is-shown="isPasswordStrengthShown"
|
||||
/>
|
||||
</div>
|
||||
<div class="reset-area__content-area__container__input-wrapper">
|
||||
<HeaderlessInput
|
||||
label="Retype Password"
|
||||
placeholder="Retype Password"
|
||||
:error="repeatedPasswordError"
|
||||
width="100%"
|
||||
height="46px"
|
||||
is-password="true"
|
||||
@setData="setRepeatedPassword"
|
||||
/>
|
||||
</div>
|
||||
<p class="reset-area__content-area__container__button" @click.prevent="onResetClick">Reset Password</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<KeyIcon />
|
||||
<h2 class="reset-area__content-area__container__title success">Success!</h2>
|
||||
<p class="reset-area__content-area__container__sub-title">
|
||||
You have successfully changed your password.
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
<router-link :to="loginPath" class="reset-area__content-area__login-link">
|
||||
Back to Login
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import HeaderlessInput from '@/components/common/HeaderlessInput.vue';
|
||||
import PasswordStrength from '@/components/common/PasswordStrength.vue';
|
||||
|
||||
import LogoIcon from '@/../static/images/logo.svg';
|
||||
import KeyIcon from '@/../static/images/resetPassword/success.svg';
|
||||
|
||||
import { AuthHttpApi } from '@/api/auth';
|
||||
import { RouteConfig } from '@/router';
|
||||
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
|
||||
import { Validator } from '@/utils/validation';
|
||||
|
||||
// @vue/component
|
||||
@Component({
|
||||
components: {
|
||||
LogoIcon,
|
||||
HeaderlessInput,
|
||||
PasswordStrength,
|
||||
KeyIcon,
|
||||
},
|
||||
})
|
||||
|
||||
export default class ResetPassword extends Vue {
|
||||
private token = '';
|
||||
private password = '';
|
||||
private repeatedPassword = '';
|
||||
|
||||
private passwordError = '';
|
||||
private repeatedPasswordError = '';
|
||||
private isLoading = false;
|
||||
|
||||
private readonly auth: AuthHttpApi = new AuthHttpApi();
|
||||
|
||||
public isPasswordStrengthShown = false;
|
||||
|
||||
public readonly loginPath: string = RouteConfig.Login.path;
|
||||
|
||||
/**
|
||||
* Lifecycle hook on component destroy.
|
||||
* Sets view to default state.
|
||||
*/
|
||||
public beforeDestroy(): void {
|
||||
if (this.isSuccessfulPasswordResetShown) {
|
||||
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_SUCCESSFUL_PASSWORD_RESET);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle hook after initial render.
|
||||
* Initializes recovery token from route param
|
||||
* and redirects to login if token doesn't exist.
|
||||
*/
|
||||
public mounted(): void {
|
||||
if (this.$route.query.token) {
|
||||
this.token = this.$route.query.token.toString();
|
||||
} else {
|
||||
this.$router.push(RouteConfig.Login.path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the successful password reset area is shown.
|
||||
*/
|
||||
public get isSuccessfulPasswordResetShown() : boolean {
|
||||
return this.$store.state.appStateModule.appState.isSuccessfulPasswordResetShown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates input fields and requests password reset.
|
||||
*/
|
||||
public async onResetClick(): Promise<void> {
|
||||
if (this.isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
if (!this.validateFields()) {
|
||||
this.isLoading = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.auth.resetPassword(this.token, this.password);
|
||||
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_SUCCESSFUL_PASSWORD_RESET);
|
||||
} catch (error) {
|
||||
await this.$notify.error(error.message);
|
||||
}
|
||||
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates input values to satisfy expected rules.
|
||||
*/
|
||||
private validateFields(): boolean {
|
||||
let isNoErrors = true;
|
||||
|
||||
if (!Validator.password(this.password)) {
|
||||
this.passwordError = 'Invalid password';
|
||||
isNoErrors = false;
|
||||
}
|
||||
|
||||
if (this.repeatedPassword !== this.password) {
|
||||
this.repeatedPasswordError = 'Password doesn\'t match';
|
||||
isNoErrors = false;
|
||||
}
|
||||
|
||||
return isNoErrors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes password strength container visible.
|
||||
*/
|
||||
public showPasswordStrength(): void {
|
||||
this.isPasswordStrengthShown = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides password strength container.
|
||||
*/
|
||||
public hidePasswordStrength(): void {
|
||||
this.isPasswordStrengthShown = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the page.
|
||||
*/
|
||||
public onLogoClick(): void {
|
||||
location.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets user's password field from value string.
|
||||
*/
|
||||
public setPassword(value: string): void {
|
||||
this.password = value.trim();
|
||||
this.passwordError = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets user's repeat password field from value string.
|
||||
*/
|
||||
public setRepeatedPassword(value: string): void {
|
||||
this.repeatedPassword = value.trim();
|
||||
this.repeatedPasswordError = '';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.reset-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
font-family: 'font_regular', sans-serif;
|
||||
background-color: #f5f6fa;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
min-height: 100%;
|
||||
overflow-y: scroll;
|
||||
|
||||
&__logo-wrapper {
|
||||
text-align: center;
|
||||
margin: 70px 0;
|
||||
|
||||
&__logo {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&__content-area {
|
||||
width: 100%;
|
||||
padding: 0 20px;
|
||||
margin-bottom: 50px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
|
||||
&__container {
|
||||
width: 610px;
|
||||
padding: 60px 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
border-radius: 20px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.success {
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__input-wrapper {
|
||||
margin-top: 20px;
|
||||
|
||||
&.password {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 24px;
|
||||
margin: 10px 0;
|
||||
letter-spacing: -0.100741px;
|
||||
color: #252525;
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-weight: 800;
|
||||
|
||||
&.success {
|
||||
font-size: 40px;
|
||||
margin: 25px 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__button {
|
||||
font-family: 'font_regular', sans-serif;
|
||||
font-weight: 700;
|
||||
margin-top: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #376fff;
|
||||
border-radius: 50px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
|
||||
&:hover {
|
||||
background-color: #0059d0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__login-link {
|
||||
font-family: 'font_medium', sans-serif;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
color: #376fff;
|
||||
margin-top: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 750px) {
|
||||
|
||||
.reset-area {
|
||||
|
||||
&__content-area {
|
||||
|
||||
&__container {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 414px) {
|
||||
|
||||
.reset-area {
|
||||
|
||||
&__logo-wrapper {
|
||||
margin: 40px;
|
||||
}
|
||||
|
||||
&__content-area {
|
||||
padding: 0;
|
||||
|
||||
&__container {
|
||||
padding: 60px 60px;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -14,7 +14,10 @@ module.exports = {
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020
|
||||
},
|
||||
plugins: ["storj"],
|
||||
rules: {
|
||||
"linebreak-style": ["error", "unix"],
|
||||
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
|
||||
@ -43,5 +46,7 @@ module.exports = {
|
||||
"vue/no-useless-v-bind": ["warn"],
|
||||
|
||||
'vue/no-unregistered-components': ['warn', { ignorePatterns: ['router-link', 'router-view'] }],
|
||||
|
||||
'storj/vue/require-annotation': 'warn',
|
||||
},
|
||||
}
|
11
web/storagenode/.stylelintignore
Normal file
11
web/storagenode/.stylelintignore
Normal file
@ -0,0 +1,11 @@
|
||||
*.*
|
||||
!*.vue
|
||||
!*.css
|
||||
!*.sss
|
||||
!*.less
|
||||
!*.scss
|
||||
!*.sass
|
||||
|
||||
dist
|
||||
node_modules
|
||||
coverage
|
@ -11,7 +11,6 @@ module.exports = {
|
||||
"stylelint-scss"
|
||||
],
|
||||
"extends": "stylelint-config-standard",
|
||||
"ignoreFiles": ["dist/**"],
|
||||
"rules": {
|
||||
"indentation": 4,
|
||||
"string-quotes": "single",
|
||||
|
13
web/storagenode/package-lock.json
generated
13
web/storagenode/package-lock.json
generated
@ -35,6 +35,7 @@
|
||||
"compression-webpack-plugin": "6.0.0",
|
||||
"core-js": "3.6.5",
|
||||
"eslint": "6.7.2",
|
||||
"eslint-plugin-storj": "file:../eslint-storj",
|
||||
"eslint-plugin-vue": "7.16.0",
|
||||
"jest-fetch-mock": "3.0.0",
|
||||
"sass": "1.37.0",
|
||||
@ -51,6 +52,11 @@
|
||||
"vue-template-compiler": "2.6.11"
|
||||
}
|
||||
},
|
||||
"../eslint-storj": {
|
||||
"name": "eslint-plugin-storj",
|
||||
"version": "1.0.0",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz",
|
||||
@ -10730,6 +10736,10 @@
|
||||
"rimraf": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-storj": {
|
||||
"resolved": "../eslint-storj",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/eslint-plugin-vue": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.16.0.tgz",
|
||||
@ -36231,6 +36241,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"eslint-plugin-storj": {
|
||||
"version": "file:../eslint-storj"
|
||||
},
|
||||
"eslint-plugin-vue": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.16.0.tgz",
|
||||
|
@ -4,8 +4,8 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"lint": "vue-cli-service lint --max-warnings 0 --fix && stylelint --max-warnings 0 \"**/*.{vue,css,sss,less,scss,sass}\" --fix",
|
||||
"lint-ci": "vue-cli-service lint --max-warnings 0 --no-fix && stylelint --max-warnings 0 --no-fix \"**/*.{vue,css,sss,less,scss,sass}\"",
|
||||
"lint": "vue-cli-service lint --max-warnings 0 --fix && stylelint . --max-warnings 0 --fix",
|
||||
"lint-ci": "vue-cli-service lint --max-warnings 0 --no-fix && stylelint . --max-warnings 0 --no-fix",
|
||||
"build": "vue-cli-service build",
|
||||
"dev": "vue-cli-service build --mode development --watch",
|
||||
"test": "vue-cli-service test:unit"
|
||||
@ -39,6 +39,7 @@
|
||||
"core-js": "3.6.5",
|
||||
"eslint": "6.7.2",
|
||||
"eslint-plugin-vue": "7.16.0",
|
||||
"eslint-plugin-storj": "file:../eslint-storj",
|
||||
"jest-fetch-mock": "3.0.0",
|
||||
"sass": "1.37.0",
|
||||
"sass-loader": "8.0.0",
|
||||
|
Loading…
Reference in New Issue
Block a user