web/satellite notifications added, users and projects api updated (#934)

* web/satellite notifications added, users and projects api updated

* fix users api
This commit is contained in:
Yehor Butko 2018-12-24 14:52:52 +02:00 committed by GitHub
parent 7569b7d71b
commit 5ef427265e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 342 additions and 439 deletions

View File

@ -8,17 +8,16 @@ import (
"github.com/skyrings/skyring-common/tools/uuid"
"storj.io/storj/pkg/satellite"
"storj.io/storj/pkg/utils"
)
const (
// Mutation is graphql request that modifies data
Mutation = "mutation"
createUserMutation = "createUser"
updateUserMutation = "updateUser"
deleteUserMutation = "deleteUser"
changeUserPasswordMutation = "changeUserPassword"
createUserMutation = "createUser"
updateAccountMutation = "updateAccount"
deleteAccountMutation = "deleteAccount"
changePasswordMutation = "changePassword"
createProjectMutation = "createProject"
deleteProjectMutation = "deleteProject"
@ -59,46 +58,34 @@ func rootMutation(service *satellite.Service, types Types) *graphql.Object {
return user.ID.String(), nil
},
},
updateUserMutation: &graphql.Field{
updateAccountMutation: &graphql.Field{
Type: types.User(),
Args: graphql.FieldConfigArgument{
fieldID: &graphql.ArgumentConfig{
Type: graphql.String,
},
input: &graphql.ArgumentConfig{
Type: graphql.NewNonNull(types.UserInput()),
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
id, err := uuidIDAuthFallback(p, fieldID)
if err != nil {
return nil, err
}
input, _ := p.Args[input].(map[string]interface{})
user, err := service.GetUser(p.Context, *id)
auth, err := satellite.GetAuth(p.Context)
if err != nil {
return nil, err
}
updatedUser := *user
info := fillUserInfo(&updatedUser, input)
info := fillUserInfo(&auth.User, input)
err = service.UpdateUser(p.Context, *id, info)
err = service.UpdateAccount(p.Context, info)
if err != nil {
return user, err
return nil, err
}
return &updatedUser, nil
return auth.User, nil
},
},
changeUserPasswordMutation: &graphql.Field{
changePasswordMutation: &graphql.Field{
Type: types.User(),
Args: graphql.FieldConfigArgument{
fieldID: &graphql.ArgumentConfig{
Type: graphql.String,
},
fieldPassword: &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
@ -107,44 +94,43 @@ func rootMutation(service *satellite.Service, types Types) *graphql.Object {
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
id, err := uuidIDAuthFallback(p, fieldID)
pass, _ := p.Args[fieldPassword].(string)
newPass, _ := p.Args[fieldNewPassword].(string)
auth, err := satellite.GetAuth(p.Context)
if err != nil {
return nil, err
}
pass, _ := p.Args[fieldPassword].(string)
newPass, _ := p.Args[fieldNewPassword].(string)
err = service.ChangePassword(p.Context, pass, newPass)
if err != nil {
return nil, err
}
err = service.ChangeUserPassword(p.Context, *id, pass, newPass)
user, getErr := service.GetUser(p.Context, *id)
return user, utils.CombineErrors(err, getErr)
return auth.User, nil
},
},
deleteUserMutation: &graphql.Field{
deleteAccountMutation: &graphql.Field{
Type: types.User(),
Args: graphql.FieldConfigArgument{
fieldID: &graphql.ArgumentConfig{
Type: graphql.String,
},
fieldPassword: &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
id, err := uuidIDAuthFallback(p, fieldID)
if err != nil {
return nil, err
}
password, _ := p.Args[fieldPassword].(string)
user, err := service.GetUser(p.Context, *id)
auth, err := satellite.GetAuth(p.Context)
if err != nil {
return nil, err
}
err = service.DeleteUser(p.Context, *id, password)
return user, err
err = service.DeleteAccount(p.Context, password)
if err != nil {
return nil, err
}
return auth.User, nil
},
},
// creates project from input params

View File

@ -112,10 +112,11 @@ func (s *Service) GetUser(ctx context.Context, id uuid.UUID) (u *User, err error
return s.store.Users().Get(ctx, id)
}
// UpdateUser updates User with given id
func (s *Service) UpdateUser(ctx context.Context, id uuid.UUID, info UserInfo) (err error) {
// UpdateAccount updates User
func (s *Service) UpdateAccount(ctx context.Context, info UserInfo) (err error) {
defer mon.Task()(&ctx)(&err)
_, err = GetAuth(ctx)
auth, err := GetAuth(ctx)
if err != nil {
return err
}
@ -125,7 +126,7 @@ func (s *Service) UpdateUser(ctx context.Context, id uuid.UUID, info UserInfo) (
}
return s.store.Users().Update(ctx, &User{
ID: id,
ID: auth.User.ID,
FirstName: info.FirstName,
LastName: info.LastName,
Email: info.Email,
@ -133,20 +134,15 @@ func (s *Service) UpdateUser(ctx context.Context, id uuid.UUID, info UserInfo) (
})
}
// ChangeUserPassword updates password for a given user
func (s *Service) ChangeUserPassword(ctx context.Context, id uuid.UUID, pass, newPass string) (err error) {
// ChangePassword updates password for a given user
func (s *Service) ChangePassword(ctx context.Context, pass, newPass string) (err error) {
defer mon.Task()(&ctx)(&err)
_, err = GetAuth(ctx)
auth, err := GetAuth(ctx)
if err != nil {
return err
}
user, err := s.store.Users().Get(ctx, id)
if err != nil {
return err
}
err = bcrypt.CompareHashAndPassword(user.PasswordHash, []byte(pass))
err = bcrypt.CompareHashAndPassword(auth.User.PasswordHash, []byte(pass))
if err != nil {
return ErrUnauthorized.New("origin password is incorrect: %s", err.Error())
}
@ -160,28 +156,24 @@ func (s *Service) ChangeUserPassword(ctx context.Context, id uuid.UUID, pass, ne
return err
}
user.PasswordHash = hash
return s.store.Users().Update(ctx, user)
auth.User.PasswordHash = hash
return s.store.Users().Update(ctx, &auth.User)
}
// DeleteUser deletes User by id
func (s *Service) DeleteUser(ctx context.Context, id uuid.UUID, password string) (err error) {
// DeleteAccount deletes User
func (s *Service) DeleteAccount(ctx context.Context, password string) (err error) {
defer mon.Task()(&ctx)(&err)
auth, err := GetAuth(ctx)
if err != nil {
return err
}
if !uuid.Equal(auth.User.ID, id) {
return ErrUnauthorized.New("user has no rights")
}
err = bcrypt.CompareHashAndPassword(auth.User.PasswordHash, []byte(password))
if err != nil {
return ErrUnauthorized.New("origin password is incorrect")
}
return s.store.Users().Delete(ctx, id)
return s.store.Users().Delete(ctx, auth.User.ID)
}
// GetProject is a method for querying project by id

View File

@ -4,13 +4,16 @@
import apollo from '@/utils/apolloManager';
import gql from 'graphql-tag';
// Performs graqhQL request.
// Throws an exception if error occurs
export async function createProject(project: Project): Promise<any> {
let response: any = null;
// Performs graqhQL request for project creation.
export async function createProject(project: Project): Promise<RequestResponse<Project>> {
let result: RequestResponse<Project> = {
errorMessage: '',
isSuccess: false,
data: project
};
try {
response = await apollo.mutate(
let response: any = await apollo.mutate(
{
mutation: gql(`
mutation {
@ -26,19 +29,30 @@ export async function createProject(project: Project): Promise<any> {
fetchPolicy: 'no-cache',
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data.id = response.data.createProject.id;
}
} catch (e) {
console.error(e);
result.errorMessage = e.message;
}
return response;
return result;
}
// Performs graqhQL request for fetching all projects of current user.
export async function fetchProjects(): Promise<any> {
let response: any = null;
export async function fetchProjects(): Promise<RequestResponse<Project[]>> {
let result: RequestResponse<Project[]> = {
errorMessage: '',
isSuccess: false,
data: []
};
try {
response = await apollo.query(
try {
let response: any = await apollo.query(
{
query: gql(`
query {
@ -46,8 +60,7 @@ export async function fetchProjects(): Promise<any> {
name
id
description
createdAt
isTermsAccepted
createdAt
}
}`
),
@ -55,19 +68,29 @@ export async function fetchProjects(): Promise<any> {
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data = response.data.myProjects;
}
} catch (e) {
console.error(e);
result.errorMessage = e.message;
}
return response;
return result;
}
// Performs graqhQL request for updating selected project description
export async function updateProject(projectID: string, description: string): Promise<any> {
let response: any = null;
export async function updateProject(projectID: string, description: string): Promise<RequestResponse<null>> {
let result: RequestResponse<null> = {
errorMessage: '',
isSuccess: false,
data: null
};
try {
response = await apollo.mutate(
let response: any = await apollo.mutate(
{
mutation: gql(`
mutation {
@ -80,19 +103,29 @@ export async function updateProject(projectID: string, description: string): Pro
fetchPolicy: 'no-cache',
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
}
} catch (e) {
console.error(e);
result.errorMessage = e.message;
}
return response;
return result;
}
// Performs graqhQL request for deleting selected project
export async function deleteProject(projectID: string): Promise<any> {
let response: any = null;
export async function deleteProject(projectID: string): Promise<RequestResponse<null>> {
let result: RequestResponse<null> = {
errorMessage: '',
isSuccess: false,
data: null
};
try {
response = await apollo.mutate(
let response = await apollo.mutate(
{
mutation: gql(`
mutation {
@ -104,9 +137,15 @@ export async function deleteProject(projectID: string): Promise<any> {
fetchPolicy: 'no-cache',
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
}
} catch (e) {
console.error(e);
result.errorMessage = e.message;
}
return response;
return result;
}

View File

@ -6,16 +6,23 @@ import gql from 'graphql-tag';
// Performs update user info graphQL mutation request.
// Returns User object if succeed, null otherwise
export async function updateBasicUserInfoRequest(user: User) {
let response: any = null;
export async function updateAccountRequest(user: User): Promise<RequestResponse<User>> {
let result: RequestResponse<User> = {
errorMessage: '',
isSuccess: false,
data: {
firstName: '',
lastName: '',
email: ''
}
};
try {
response = await apolloManager.mutate(
let response: any = await apolloManager.mutate(
{
mutation: gql(`
mutation {
updateUser (
id: "${user.id}",
updateAccount (
input: {
email: "${user.email}",
firstName: "${user.firstName}",
@ -31,53 +38,70 @@ export async function updateBasicUserInfoRequest(user: User) {
fetchPolicy: 'no-cache',
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data = response.data.updateAccount;
}
} catch (e) {
console.error(e);
result.errorMessage = e.message;
}
return response;
return result;
}
// Performs change password graphQL mutation
// Returns base user fields
export async function updatePasswordRequest(userId: string, password: string) {
let response: any = null;
export async function changePasswordRequest(password: string, newPassword: string): Promise<RequestResponse<null>> {
let result: RequestResponse<null> = {
errorMessage: '',
isSuccess: false,
data: null
};
try {
response = await apolloManager.mutate(
let response: any = await apolloManager.mutate(
{
mutation: gql(`
mutation {
updateUser (
id: "${userId}",
input: {
password: "${password}"
}
changePassword (
password: "${password}",
newPassword: "${newPassword}"
) {
email,
firstName,
lastName
email
}
}`
),
fetchPolicy: 'no-cache',
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
}
} catch (e) {
console.error(e);
result.errorMessage = e.message;
}
return response;
return result;
}
// Performs Create user graqhQL request.
// Throws an exception if error occurs
// Returns object with newly created user
export async function createUserRequest(user: User, password: string): Promise<any> {
let response: any = null;
export async function createUserRequest(user: User, password: string): Promise<RequestResponse<null>> {
let result: RequestResponse<null> = {
errorMessage: '',
isSuccess: false,
data: null
};
try {
response = await apolloManager.mutate(
let response = await apolloManager.mutate(
{
mutation: gql(`
mutation {
@ -94,21 +118,31 @@ export async function createUserRequest(user: User, password: string): Promise<a
fetchPolicy: 'no-cache',
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
}
} catch (e) {
console.error(e);
result.errorMessage = e.message;
}
return response;
return result;
}
// Performs graqhQL request.
// Returns Token, User objects.
// Throws an exception if error occurs
export async function getTokenRequest(email: string, password: string): Promise<any> {
let response: any = null;
export async function getTokenRequest(email: string, password: string): Promise<RequestResponse<string>> {
let result: RequestResponse<string> = {
errorMessage: '',
isSuccess: false,
data: ''
};
try {
response = await apolloManager.query(
let response: any = await apolloManager.query(
{
query: gql(`
query {
@ -121,21 +155,36 @@ export async function getTokenRequest(email: string, password: string): Promise<
fetchPolicy: 'no-cache',
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data = response.data.token.token;
}
} catch (e) {
console.error(e);
result.errorMessage = e.message;
}
return response;
return result;
}
// Performs graqhQL request.
// Returns Token, User objects.
// Throws an exception if error occurs
export async function getUserRequest(): Promise<any> {
let response: any = null;
export async function getUserRequest(): Promise<RequestResponse<User>> {
let result: RequestResponse<User> = {
errorMessage: '',
isSuccess: false,
data: {
firstName: '',
lastName: '',
email: ''
}
};
try {
response = await apolloManager.query(
let response: any = await apolloManager.query(
{
query: gql(`
query {
@ -149,35 +198,52 @@ export async function getUserRequest(): Promise<any> {
fetchPolicy: 'no-cache',
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data = response.data.user;
}
} catch (e) {
console.error(e);
result.errorMessage = e.message;
}
return response;
return result;
}
// Performs graqhQL request.
// User object.
// Throws an exception if error occurs
export async function deleteUserAccountRequest(password: string): Promise<any> {
let response: any = null;
export async function deleteAccountRequest(password: string): Promise<RequestResponse<null>> {
let result: RequestResponse<null> = {
errorMessage: '',
isSuccess: false,
data: null
};
try {
response = await apolloManager.mutate(
let response = await apolloManager.mutate(
{
mutation: gql(`
mutation {
deleteUser(password: "${password}") {
id
deleteAccount(password: "${password}") {
email
}
}`
),
fetchPolicy: 'no-cache'
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
}
} catch (e) {
console.error(e);
result.errorMessage = e.message;
}
return response;
return result;
}

View File

@ -14,15 +14,15 @@
width="100%"
ref="firstNameInput"
:error="firstNameError"
:init-value="originalFirstName"
:init-value="user.firstName"
@setData="setFirstName" />
<HeaderedInput
label="Last Name"
placeholder="LastNameEdit"
placeholder="Enter Last Name"
width="100%"
ref="lastNameInput"
:error="lastNameError"
:initValue="originalLastName"
:initValue="user.lastName"
@setData="setLastName"/>
</div>
<div class="account-area-row-container">
@ -33,7 +33,7 @@
width="100%"
ref="emailInput"
:error="emailError"
:initValue="originalEmail"
:initValue="user.email"
@setData="setEmail" />
</div>
<div v-if="isAccountSettingsEditing" class="account-area-save-button-area" >
@ -212,18 +212,20 @@ import DeleteAccountPopup from '@/components/dashboard/account/DeleteAccountPopu
}
let user = {
id: this.$store.getters.user.id,
email: this.$data.email,
firstName: this.$data.firstName,
lastName: this.$data.lastName,
};
let isSuccess = await this.$store.dispatch('updateBasicUserInfo', user);
if (!isSuccess) {
// TODO Change to popup
console.error('error while changing basic user info');
let response = await this.$store.dispatch('updateAccount', user);
if (!response.isSuccess) {
this.$store.dispatch('error', response.errorMessage);
return;
}
this.$store.dispatch('success', 'Account info successfully updated!');
this.$data.isAccountSettingsEditing = false;
},
@ -284,14 +286,19 @@ import DeleteAccountPopup from '@/components/dashboard/account/DeleteAccountPopu
return;
}
let isSuccess = await this.$store.dispatch('updatePassword', this.$data.newPassword);
if (!isSuccess) {
// TODO Change to popup
console.error('error while updating user password');
let response = await this.$store.dispatch('changePassword',
{
oldPassword: this.$data.oldPassword,
newPassword: this.$data.newPassword
}
);
if (!response.isSuccess) {
this.$store.dispatch('error', response.errorMessage);
return;
}
this.$store.dispatch('success', 'Password successfully changed!');
(this.$refs['oldPasswordInput'] as HeaderedInput).setValue('');
(this.$refs['newPasswordInput'] as HeaderedInput).setValue('');
(this.$refs['confirmationPasswordInput'] as HeaderedInput).setValue('');
@ -302,6 +309,15 @@ import DeleteAccountPopup from '@/components/dashboard/account/DeleteAccountPopu
this.$data.isPopupShown = ! this.$data.isPopupShown;
},
},
computed: {
user: function() {
return {
firstName: this.$store.getters.user.firstName,
lastName: this.$store.getters.user.lastName,
email: this.$store.getters.user.email,
};
}
},
components: {
Button,
HeaderedInput,

View File

@ -1,195 +0,0 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="delete-account-container">
<div class="delete-account">
<div class="delete-account__info-panel-container">
<h2 class="delete-account__info-panel-container__main-label-text">Delete account</h2>
<div v-html="imageSource"></div>
</div>
<div class="delete-account__form-container">
<p>Are you sure that you want to delete your account? All your information, projects, API Keys will be deleted from the Satellite forever.</p>
<HeaderedInput
label="Enter your password"
placeholder="Your Password"
class="full-input"
width="100%"
:error="nameError"
@setData="setProjectName">
</HeaderedInput>
<div class="delete-account__form-container__button-container">
<Button label="Cancel" width="205px" height="48px" :onPress="onCloseClick" isWhite/>
<Button label="Delete" width="205px" height="48px" class="red" :onPress="createProject"/>
</div>
</div>
<div class="delete-account__close-cross-container">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" v-on:click="onCloseClick">
<path d="M15.7071 1.70711C16.0976 1.31658 16.0976 0.683417 15.7071 0.292893C15.3166 -0.0976311 14.6834 -0.0976311 14.2929 0.292893L15.7071 1.70711ZM0.292893 14.2929C-0.0976311 14.6834 -0.0976311 15.3166 0.292893 15.7071C0.683417 16.0976 1.31658 16.0976 1.70711 15.7071L0.292893 14.2929ZM1.70711 0.292893C1.31658 -0.0976311 0.683417 -0.0976311 0.292893 0.292893C-0.0976311 0.683417 -0.0976311 1.31658 0.292893 1.70711L1.70711 0.292893ZM14.2929 15.7071C14.6834 16.0976 15.3166 16.0976 15.7071 15.7071C16.0976 15.3166 16.0976 14.6834 15.7071 14.2929L14.2929 15.7071ZM14.2929 0.292893L0.292893 14.2929L1.70711 15.7071L15.7071 1.70711L14.2929 0.292893ZM0.292893 1.70711L14.2929 15.7071L15.7071 14.2929L1.70711 0.292893L0.292893 1.70711Z" fill="#384B65"/>
</svg>
</div>
</div>
<!-- <div class="delete-account">
<div class="delete-account__info-panel-container">
<h2 class="delete-account__info-panel-container__main-label-text">Delete account</h2>
<div v-html="imageSource"></div>
</div>
<div class="delete-account__form-container">
<p class="text">Sorry, we cant delete your account form the Satellite because it seems that you are the main person in one or few projects created on Satellite. Please check all your projects where you are an administrator and reassign it to another account with another valid Payment details. Or just simply pay all fees you still need to pay into all your projects where you are an administrator and then delete each of those projects. Then you will be able to delete your account.</p>
<p class="text">If you still have any questions on this please contact us:</p>
<a href="mailto:support@storj.io">support@storj.io</a>
<div class="delete-account__form-container__button-container">
<Button label="Go to Project" width="100%" height="48px" :onPress="onCloseClick" isWhite/>
</div>
</div>
<div class="delete-account__close-cross-container">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" v-on:click="onCloseClick">
<path d="M15.7071 1.70711C16.0976 1.31658 16.0976 0.683417 15.7071 0.292893C15.3166 -0.0976311 14.6834 -0.0976311 14.2929 0.292893L15.7071 1.70711ZM0.292893 14.2929C-0.0976311 14.6834 -0.0976311 15.3166 0.292893 15.7071C0.683417 16.0976 1.31658 16.0976 1.70711 15.7071L0.292893 14.2929ZM1.70711 0.292893C1.31658 -0.0976311 0.683417 -0.0976311 0.292893 0.292893C-0.0976311 0.683417 -0.0976311 1.31658 0.292893 1.70711L1.70711 0.292893ZM14.2929 15.7071C14.6834 16.0976 15.3166 16.0976 15.7071 15.7071C16.0976 15.3166 16.0976 14.6834 15.7071 14.2929L14.2929 15.7071ZM14.2929 0.292893L0.292893 14.2929L1.70711 15.7071L15.7071 1.70711L14.2929 0.292893ZM0.292893 1.70711L14.2929 15.7071L15.7071 14.2929L1.70711 0.292893L0.292893 1.70711Z" fill="#384B65"/>
</svg>
</div>
</div> -->
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HeaderedInput from '@/components/common/HeaderedInput.vue';
import Button from '@/components/common/Button.vue';
import { EMPTY_STATE_IMAGES } from '@/utils/constants/emptyStatesImages';
@Component(
{
data: function () {
return {
imageSource: EMPTY_STATE_IMAGES.DELETE_ACCOUNT,
};
},
components: {
HeaderedInput,
Button
}
}
)
export default class DeleteAccontPopup extends Vue {
}
</script>
<style scoped lang="scss">
.delete-account-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(134, 134, 148, 0.4);
z-index: 1121;
display: flex;
justify-content: center;
align-items: center;
}
.input-container.full-input {
width: 100%;
}
.red {
background-color: #EB5757;
}
.text {
margin: 0;
margin-bottom: 0 !important;
font-family: 'montserrat_regular' !important;
font-size: 16px;
line-height: 25px;
}
.delete-account {
width: 100%;
max-width: 845px;
background-color: #FFFFFF;
border-radius: 6px;
display: flex;
flex-direction: row;
align-items: flex-start;
position: relative;
justify-content: center;
padding: 100px 100px 100px 80px;
&__info-panel-container {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
margin-right: 100px;
&__main-label-text {
font-family: 'montserrat_bold';
font-size: 32px;
line-height: 39px;
color: #384B65;
margin-bottom: 60px;
margin-top: 0;
}
}
&__form-container {
width: 100%;
max-width: 450px;
p {
margin: 0;
margin-bottom: 25px;
font-family: 'montserrat_medium';
font-size: 16px;
line-height: 25px;
&:nth-child(2) {
margin-top: 20px;
}
}
a {
font-family: 'montserrat_medium';
font-size: 16px;
color: #2683FF;
}
&__button-container {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-top: 30px;
}
}
&__close-cross-container {
display: flex;
justify-content: center;
align-items: flex-start;
position: absolute;
right: 30px;
top: 40px;
svg {
cursor: pointer;
}
}
}
@media screen and (max-width: 720px) {
.delete-account {
&__info-panel-container {
display: none;
}
&__form-container {
&__button-container {
width: 100%;
}
}
}
}
</style>

View File

@ -57,10 +57,10 @@ import { EMPTY_STATE_IMAGES } from '@/utils/constants/emptyStatesImages';
this.$data.password = value;
},
onDeleteAccountClick: async function() {
let isSuccessDeletion = await this.$store.dispatch('deleteUserAccount', this.$data.password);
let response = await this.$store.dispatch('deleteAccount', this.$data.password);
if (!isSuccessDeletion) {
this.$store.dispatch('error', 'Error during account deletion');
if (!response.isSuccess) {
this.$store.dispatch('error', response.errorMessage);
return;
}

View File

@ -105,20 +105,19 @@ import Button from '@/components/common/Button.vue';
return;
}
let isSuccess = this.$store.dispatch('createProject', {
let response = await this.$store.dispatch('createProject', {
name: this.$data.name,
description: this.$data.description,
isTermsAccepted: this.$data.isTermsAccepted,
isTermsAccepted: this.$data.isTermsAccepted
});
if (!isSuccess) {
// TODO: show popup here
console.error('error during project creation!');
if (!response.isSuccess) {
this.$store.dispatch('error', response.errorMessage);
return;
}
this.$store.dispatch('success', 'Project created successfully!');
this.$emit('onClose');
}
},

View File

@ -27,23 +27,12 @@ import ProjectSelectionDropdown from './ProjectSelectionDropdown.vue';
},
methods: {
toggleSelection: async function (): Promise<any> {
// TODO: add progress indicator while fetching
let isFetchSuccess = await this.$store.dispatch('fetchProjects');
if (!isFetchSuccess || this.$store.getters.projects.length === 0) {
// TODO: popup error here
console.error('error during project fetching!');
const response = await this.$store.dispatch('fetchProjects');
if (!response.isSuccess) {
this.$store.dispatch('error', response.errorMessage);
return;
}
if (this.$store.getters.selectedProject.id) {
const isFetchProjectMemberSuccess = await this.$store.dispatch('fetchProjectMembers', {limit: 20, offset: 0});
if (!isFetchProjectMemberSuccess) {
// TODO: Replace with popup
console.error('Unable to fetch project members');
}
}
this.$data.isChoiceShown = !this.$data.isChoiceShown;
}

View File

@ -48,6 +48,16 @@ import { Component, Vue } from 'vue-property-decorator';
onProjectSelected: function (projectID: string): void {
this.$store.dispatch('selectProject', projectID);
this.$emit('onClose');
// TODO: uncomment after updating proj members api
// if (this.$store.getters.selectedProject.id) {
// const response = await this.$store.dispatch('fetchProjectMembers', {limit: 20, offset: 0});
// if (!response.isSuccess) {
// this.$store.dispatch('error', 'Unable to fetch project members');
//
// return;
// }
// }
}
},
}

View File

@ -73,18 +73,19 @@ import { EMPTY_STATE_IMAGES } from '@/utils/constants/emptyStatesImages';
return;
}
let isDeleteSuccess = await this.$store.dispatch(
let response = await this.$store.dispatch(
'deleteProject',
this.$store.getters.selectedProject.id,
);
if (!isDeleteSuccess) {
if (!response.isSuccess) {
this.$store.dispatch('error', 'Error during project deletion');
return;
}
this.$store.dispatch('success', 'Project was successfully deleted');
this.$store.dispatch('fetchProjects');
this.$router.push('/');
}
},

View File

@ -95,21 +95,21 @@ import DeleteProjectPopup from '@/components/projectDetails/DeleteProjectPopup.v
this.$data.newDescription = value;
},
onSaveButtonClick: async function (): Promise<any> {
let isUpdateSuccess = await this.$store.dispatch(
let response = await this.$store.dispatch(
'updateProjectDescription', {
id: this.$store.getters.selectedProject.id,
description: this.$data.newDescription,
}
);
isUpdateSuccess
response.isSuccess
// TODO: call toggleEditing method instead of this IIF
? (() => {
this.$data.isEditing = !this.$data.isEditing;
this.$data.newDescription = '';
this.$store.dispatch('success', 'Project updated successfully!')
})()
// TODO: popup error here
: console.error('error during project updating!');
: this.$store.dispatch('error', response.errorMessage);
},
toggleDeleteDialog: function (): void {
this.$data.isDeleteDialogShown = !this.$data.isDeleteDialogShown;

View File

@ -35,7 +35,6 @@ export const projectsModule = {
[PROJECTS_MUTATIONS.UPDATE](state: any, updateProjectModel: UpdateProjectModel): void {
const selected = state.projects.find((project: any) => project.id === updateProjectModel.id);
if (!selected) {
// TODO: notify about error
return;
}
@ -52,53 +51,44 @@ export const projectsModule = {
},
},
actions: {
fetchProjects: async function ({commit}: any): Promise<boolean> {
let response = await fetchProjects();
fetchProjects: async function ({commit}: any): Promise<RequestResponse<Project[]>> {
let response: RequestResponse<Project[]> = await fetchProjects();
if (!response || !response.data) {
return false;
if (response.isSuccess) {
commit(PROJECTS_MUTATIONS.FETCH, response.data);
}
commit(PROJECTS_MUTATIONS.FETCH, response.data.myProjects);
return true;
return response;
},
createProject: async function ({commit}: any, project: Project): Promise<boolean> {
createProject: async function ({commit}: any, project: Project): Promise<RequestResponse<Project>> {
let response = await createProject(project);
if (!response || response.errors) {
return false;
if (response.isSuccess) {
commit(PROJECTS_MUTATIONS.CREATE, response.data);
}
commit(PROJECTS_MUTATIONS.CREATE, response);
return true;
return response;
},
selectProject: function ({commit}: any, projectID: string) {
commit(PROJECTS_MUTATIONS.SELECT, projectID);
},
updateProjectDescription: async function ({commit}: any, updateProjectModel: UpdateProjectModel): Promise<boolean> {
updateProjectDescription: async function ({commit}: any, updateProjectModel: UpdateProjectModel): Promise<RequestResponse<null>> {
let response = await updateProject(updateProjectModel.id, updateProjectModel.description);
if (!response || response.errors) {
return false;
if (response.isSuccess) {
commit(PROJECTS_MUTATIONS.UPDATE, updateProjectModel);
}
commit(PROJECTS_MUTATIONS.UPDATE, updateProjectModel);
return true;
return response;
},
deleteProject: async function ({commit}: any, projectID: string): Promise<boolean> {
deleteProject: async function ({commit}: any, projectID: string): Promise<RequestResponse<null>> {
let response = await deleteProject(projectID);
if (!response || response.errors) {
return false;
if (response.isSuccess) {
commit(PROJECTS_MUTATIONS.DELETE, projectID);
}
commit(PROJECTS_MUTATIONS.FETCH);
commit(PROJECTS_MUTATIONS.DELETE, projectID);
return true;
return response;
},
},
getters: {

View File

@ -3,9 +3,9 @@
import { USER_MUTATIONS, } from '../mutationConstants';
import {
deleteUserAccountRequest,
updateBasicUserInfoRequest,
updatePasswordRequest,
deleteAccountRequest,
updateAccountRequest,
changePasswordRequest,
getUserRequest
} from '@/api/users';
@ -14,17 +14,13 @@ export const usersModule = {
user: {
firstName: '',
lastName: '',
email: '',
id: '',
email: ''
}
},
mutations: {
[USER_MUTATIONS.SET_USER_INFO](state: any, user: User): void {
state.user.firstName = user.firstName;
state.user.lastName = user.lastName;
state.user.email = user.email;
state.user.id = user.id;
state.user = user;
},
[USER_MUTATIONS.REVERT_TO_DEFAULT_USER_INFO](state: any): void {
@ -35,44 +31,34 @@ export const usersModule = {
},
[USER_MUTATIONS.UPDATE_USER_INFO](state: any, user: User): void {
state.user.firstName = user.firstName;
state.user.lastName = user.lastName;
state.user.email = user.email;
state.user = user;
},
},
actions: {
updateBasicUserInfo: async function ({commit}: any, userInfo: User): Promise<boolean> {
let response = await updateBasicUserInfoRequest(userInfo);
if (!response || !response.data) {
return false;
updateAccount: async function ({commit}: any, userInfo: User): Promise<RequestResponse<User>> {
let response = await updateAccountRequest(userInfo);
if (response.isSuccess) {
commit(USER_MUTATIONS.UPDATE_USER_INFO, response.data);
}
commit(USER_MUTATIONS.UPDATE_USER_INFO, userInfo);
return true;
return response;
},
updatePassword: async function ({state}: any, password: string): Promise<boolean> {
let response = await updatePasswordRequest(state.user.id, password);
return response !== null;
changePassword: async function ({state}: any, updateModel: UpdatePasswordModel): Promise<RequestResponse<null>> {
return await changePasswordRequest(updateModel.oldPassword, updateModel.newPassword);
},
deleteUserAccount: async function ({commit, state}: any, password: string): Promise<boolean> {
let response = await deleteUserAccountRequest(password);
return response !== null;
deleteAccount: async function ({commit, state}: any, password: string): Promise<RequestResponse<null>> {
return await deleteAccountRequest(password);
},
getUser: async function ({commit}: any): Promise<boolean> {
getUser: async function ({commit}: any): Promise<RequestResponse<User>> {
let response = await getUserRequest();
if (!response) {
return false;
if (response.isSuccess) {
commit(USER_MUTATIONS.SET_USER_INFO, response.data);
}
commit(USER_MUTATIONS.SET_USER_INFO, response.data.user);
return true;
return response;
}
},

8
web/satellite/src/types/response.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
declare type RequestResponse<T> = {
isSuccess: boolean,
errorMessage: string,
data: T
}

View File

@ -4,6 +4,11 @@
declare type User = {
firstName: string,
lastName: string,
email: string,
id: string,
}
email: string
}
// Used in users module to pass parameters to action
declare type UpdatePasswordModel = {
oldPassword: string,
newPassword: string
}

View File

@ -17,11 +17,21 @@
import { Component, Vue } from 'vue-property-decorator';
import DashboardHeader from '@/components/dashboard/DashboardHeader.vue';
import NavigationArea from '@/components/navigation/NavigationArea.vue';
import { removeToken } from '@/utils/tokenManager';
@Component({
beforeMount: async function() {
// TODO: check error and show notification
this.$store.dispatch('getUser');
let response: RequestResponse<User> = await this.$store.dispatch('getUser');
if (response.isSuccess) {
return;
}
this.$store.dispatch('error', response.errorMessage);
this.$router.push('/login');
removeToken();
},
components: {
NavigationArea,

View File

@ -64,14 +64,14 @@ import { getTokenRequest } from '@/api/users';
this.$data.password = value;
},
onLogin: async function () {
let loginData = await getTokenRequest(this.$data.email, this.$data.password);
let loginResponse = await getTokenRequest(this.$data.email, this.$data.password);
if (!loginResponse.isSuccess) {
this.$store.dispatch('error', loginResponse.errorMessage);
if (!loginData) {
// TODO: show popup here
return;
}
setToken(loginData.data.token.token);
setToken(loginResponse.data);
this.$router.push(ROUTES.DASHBOARD.path);
}

View File

@ -152,8 +152,9 @@ import { createUserRequest } from '@/api/users';
};
let response = await createUserRequest(user, this.$data.password);
if (!response) {
// TODO: show popup here
if (!response.isSuccess) {
this.$store.dispatch('error', response.errorMessage);
return;
}