Satellite frontned empty states (#1682)

* V3-1421 Empty State Pages in no Project Case
This commit is contained in:
Yehor Butko 2019-04-05 18:08:14 +03:00 committed by GitHub
parent 07afba521d
commit f4430f9063
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 656 additions and 528 deletions

View File

@ -143,6 +143,11 @@ func (s *Service) ActivateAccount(ctx context.Context, activationToken string) (
return
}
_, err = s.store.Users().GetByEmail(ctx, normalizeEmail(claims.Email))
if err == nil {
return errs.New(fmt.Sprintf("%s is already in use", claims.Email))
}
user, err := s.store.Users().Get(ctx, claims.ID)
if err != nil {
return
@ -495,11 +500,16 @@ func (s *Service) DeleteProjectMembers(ctx context.Context, projectID uuid.UUID,
// GetProjectMembers returns ProjectMembers for given Project
func (s *Service) GetProjectMembers(ctx context.Context, projectID uuid.UUID, pagination Pagination) (pm []ProjectMember, err error) {
defer mon.Task()(&ctx)(&err)
_, err = GetAuth(ctx)
auth, err := GetAuth(ctx)
if err != nil {
return nil, err
}
_, err = s.isProjectMember(ctx, auth.User.ID, projectID)
if err != nil {
return nil, ErrUnauthorized.Wrap(err)
}
if pagination.Limit > maxLimit {
pagination.Limit = maxLimit
}

View File

@ -11,32 +11,29 @@ export async function fetchAPIKeys(projectID: string) {
data: []
};
try {
let response: any = await apollo.query({
query: gql(
`query {
project(
id: "${projectID}",
) {
apiKeys {
id,
name,
createdAt
}
let response: any = await apollo.query({
query: gql(
`query {
project(
id: "${projectID}",
) {
apiKeys {
id,
name,
createdAt
}
}`
),
fetchPolicy: 'no-cache'
});
}
}`
),
fetchPolicy: 'no-cache',
errorPolicy: 'all',
});
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data = response.data.project.apiKeys;
}
} catch (e) {
result.errorMessage = e.message;
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data = response.data.project.apiKeys;
}
return result;
@ -49,37 +46,34 @@ export async function createAPIKey(projectID: string, name: string) {
data: null
};
try {
let response: any = await apollo.mutate({
mutation: gql(
`mutation {
createAPIKey(
projectID: "${projectID}",
name: "${name}"
) {
key,
keyInfo {
id,
name,
createdAt
}
let response: any = await apollo.mutate({
mutation: gql(
`mutation {
createAPIKey(
projectID: "${projectID}",
name: "${name}"
) {
key,
keyInfo {
id,
name,
createdAt
}
}`
),
fetchPolicy: 'no-cache'
});
}
}`
),
fetchPolicy: 'no-cache',
errorPolicy: 'all',
});
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data = {
key: response.data.createAPIKey.key,
keyInfo: response.data.createAPIKey.keyInfo
};
}
} catch (e) {
result.errorMessage = e.message;
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data = {
key: response.data.createAPIKey.key,
keyInfo: response.data.createAPIKey.keyInfo
};
}
return result;
@ -92,26 +86,23 @@ export async function deleteAPIKeys(ids: string[]) {
data: null
};
try {
let response: any = await apollo.mutate({
mutation: gql(
`mutation {
deleteAPIKeys(id: [${prepareIdList(ids)}]) {
id
}
}`
),
fetchPolicy: 'no-cache'
});
let response: any = await apollo.mutate({
mutation: gql(
`mutation {
deleteAPIKeys(id: [${prepareIdList(ids)}]) {
id
}
}`
),
fetchPolicy: 'no-cache',
errorPolicy: 'all',
});
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data = response.data.deleteAPIKeys;
}
} catch (e) {
result.errorMessage = e.message;
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data = response.data.deleteAPIKeys;
}
return result;

View File

@ -6,7 +6,6 @@ import gql from 'graphql-tag';
import { ProjectMemberSortByEnum } from '@/utils/constants/ProjectMemberSortEnum';
// Performs graqhQL request.
// Throws an exception if error occurs
export async function addProjectMembersRequest(projectID: string, emails: string[]): Promise<RequestResponse<null>> {
let result: RequestResponse<null> = {
errorMessage: '',
@ -14,35 +13,31 @@ export async function addProjectMembersRequest(projectID: string, emails: string
data: null
};
try {
let response: any = await apollo.mutate(
{
mutation: gql(`
mutation {
addProjectMembers(
projectID: "${projectID}",
email: [${prepareEmailList(emails)}]
) {id}
}`,
),
fetchPolicy: 'no-cache',
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
let response: any = await apollo.mutate(
{
mutation: gql(`
mutation {
addProjectMembers(
projectID: "${projectID}",
email: [${prepareEmailList(emails)}]
) {id}
}`,
),
fetchPolicy: 'no-cache',
errorPolicy: 'all',
}
} catch (e) {
result.errorMessage = e.message;
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
}
return result;
}
// Performs graqhQL request.
// Throws an exception if error occurs
export async function deleteProjectMembersRequest(projectID: string, emails: string[]): Promise<RequestResponse<null>> {
let result: RequestResponse<null> = {
errorMessage: '',
@ -50,35 +45,31 @@ export async function deleteProjectMembersRequest(projectID: string, emails: str
data: null
};
try {
let response: any = await apollo.mutate(
{
mutation: gql(`
mutation {
deleteProjectMembers(
projectID: "${projectID}",
email: [${prepareEmailList(emails)}]
) {id}
}`
),
fetchPolicy: 'no-cache',
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
let response: any = await apollo.mutate(
{
mutation: gql(`
mutation {
deleteProjectMembers(
projectID: "${projectID}",
email: [${prepareEmailList(emails)}]
) {id}
}`
),
fetchPolicy: 'no-cache',
errorPolicy: 'all',
}
} catch (e) {
result.errorMessage = e.message;
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
}
return result;
}
// Performs graqhQL request.
// Throws an exception if error occurs
export async function fetchProjectMembersRequest(projectID: string, limit: string, offset: string, sortBy: ProjectMemberSortByEnum, searchQuery: string): Promise<RequestResponse<TeamMemberModel[]>> {
let result: RequestResponse<TeamMemberModel[]> = {
errorMessage: '',
@ -86,38 +77,35 @@ export async function fetchProjectMembersRequest(projectID: string, limit: strin
data: []
};
try {
let response: any = await apollo.query(
{
query: gql(`
query {
project(
id: "${projectID}",
) {
members(limit: ${limit}, offset: ${offset}, order: ${sortBy}, search: "${searchQuery}") {
user {
id,
fullName,
shortName,
email
},
joinedAt
}
}
}`
),
fetchPolicy: 'no-cache',
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data = response.data.project.members;
let response: any = await apollo.query(
{
query: gql(`
query {
project(
id: "${projectID}",
) {
members(limit: ${limit}, offset: ${offset}, order: ${sortBy}, search: "${searchQuery}") {
user {
id,
fullName,
shortName,
email
},
joinedAt
}
}
}`
),
fetchPolicy: 'no-cache',
errorPolicy: 'all',
}
} catch (e) {
result.errorMessage = e.message;
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data = response.data.project.members;
}
return result;

View File

@ -12,31 +12,28 @@ export async function createProjectRequest(project: Project): Promise<RequestRes
data: project
};
try {
let response: any = await apollo.mutate(
{
mutation: gql(`
mutation {
createProject(
input: {
name: "${project.name}",
description: "${project.description}",
}
) {id}
}`
),
fetchPolicy: 'no-cache',
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data.id = response.data.createProject.id;
let response: any = await apollo.mutate(
{
mutation: gql(`
mutation {
createProject(
input: {
name: "${project.name}",
description: "${project.description}",
}
) {id}
}`
),
fetchPolicy: 'no-cache',
errorPolicy: 'all',
}
} catch (e) {
result.errorMessage = e.message;
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data.id = response.data.createProject.id;
}
return result;
@ -50,31 +47,28 @@ export async function fetchProjectsRequest(): Promise<RequestResponse<Project[]>
data: []
};
try {
let response: any = await apollo.query(
{
query: gql(`
query {
myProjects{
name
id
description
createdAt
}
}`
),
fetchPolicy: 'no-cache',
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data = response.data.myProjects;
let response: any = await apollo.query(
{
query: gql(`
query {
myProjects{
name
id
description
createdAt
}
}`
),
fetchPolicy: 'no-cache',
errorPolicy: 'all',
}
} catch (e) {
result.errorMessage = e.message;
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data = response.data.myProjects;
}
return result;
@ -88,28 +82,25 @@ export async function updateProjectRequest(projectID: string, description: strin
data: null
};
try {
let response: any = await apollo.mutate(
{
mutation: gql(`
mutation {
updateProjectDescription(
id: "${projectID}",
description: "${description}"
) {name}
}`
),
fetchPolicy: 'no-cache',
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
let response: any = await apollo.mutate(
{
mutation: gql(`
mutation {
updateProjectDescription(
id: "${projectID}",
description: "${description}"
) {name}
}`
),
fetchPolicy: 'no-cache',
errorPolicy: 'all',
}
} catch (e) {
result.errorMessage = e.message;
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
}
return result;
@ -123,27 +114,24 @@ export async function deleteProjectRequest(projectID: string): Promise<RequestRe
data: null
};
try {
let response = await apollo.mutate(
{
mutation: gql(`
mutation {
deleteProject(
id: "${projectID}"
) {name}
}`
),
fetchPolicy: 'no-cache',
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
let response = await apollo.mutate(
{
mutation: gql(`
mutation {
deleteProject(
id: "${projectID}"
) {name}
}`
),
fetchPolicy: 'no-cache',
errorPolicy: 'all',
}
} catch (e) {
result.errorMessage = e.message;
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
}
return result;

View File

@ -17,36 +17,33 @@ export async function updateAccountRequest(user: User): Promise<RequestResponse<
}
};
try {
let response: any = await apolloManager.mutate(
{
mutation: gql(`
mutation {
updateAccount (
input: {
email: "${user.email}",
fullName: "${user.fullName}",
shortName: "${user.shortName}"
}
) {
email,
fullName,
shortName
let response: any = await apolloManager.mutate(
{
mutation: gql(`
mutation {
updateAccount (
input: {
email: "${user.email}",
fullName: "${user.fullName}",
shortName: "${user.shortName}"
}
}`
),
fetchPolicy: 'no-cache',
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data = response.data.updateAccount;
) {
email,
fullName,
shortName
}
}`
),
fetchPolicy: 'no-cache',
errorPolicy: 'all',
}
} catch (e) {
result.errorMessage = e.message;
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data = response.data.updateAccount;
}
return result;
@ -61,38 +58,33 @@ export async function changePasswordRequest(password: string, newPassword: strin
data: null
};
try {
let response: any = await apolloManager.mutate(
{
mutation: gql(`
mutation {
changePassword (
password: "${password}",
newPassword: "${newPassword}"
) {
email
}
}`
),
fetchPolicy: 'no-cache',
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
let response: any = await apolloManager.mutate(
{
mutation: gql(`
mutation {
changePassword (
password: "${password}",
newPassword: "${newPassword}"
) {
email
}
}`
),
fetchPolicy: 'no-cache',
errorPolicy: 'all',
}
} catch (e) {
result.errorMessage = e.message;
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
}
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, secret: string): Promise<RequestResponse<null>> {
let result: RequestResponse<null> = {
errorMessage: '',
@ -100,41 +92,37 @@ export async function createUserRequest(user: User, password: string, secret: st
data: null
};
try {
let response = await apolloManager.mutate(
{
mutation: gql(`
mutation {
createUser(
input:{
email: "${user.email}",
password: "${password}",
fullName: "${user.fullName}",
shortName: "${user.shortName}",
},
secret: "${secret}",
){email}
}`
),
fetchPolicy: 'no-cache',
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
let response = await apolloManager.mutate(
{
mutation: gql(`
mutation {
createUser(
input:{
email: "${user.email}",
password: "${password}",
fullName: "${user.fullName}",
shortName: "${user.shortName}",
},
secret: "${secret}",
){email}
}`
),
fetchPolicy: 'no-cache',
errorPolicy: 'all',
}
} catch (e) {
result.errorMessage = e.message;
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
}
return result;
}
// Performs graqhQL request.
// Returns Token, User objects.
// Throws an exception if error occurs
// Returns Token.
export async function getTokenRequest(email: string, password: string): Promise<RequestResponse<string>> {
let result: RequestResponse<string> = {
errorMessage: '',
@ -142,37 +130,33 @@ export async function getTokenRequest(email: string, password: string): Promise<
data: ''
};
try {
let response: any = await apolloManager.query(
{
query: gql(`
query {
token(email: "${email}",
password: "${password}") {
token
}
}`
),
fetchPolicy: 'no-cache',
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data = response.data.token.token;
let response: any = await apolloManager.query(
{
query: gql(`
query {
token(email: "${email}",
password: "${password}") {
token
}
}`
),
fetchPolicy: 'no-cache',
errorPolicy: 'all',
}
} catch (e) {
result.errorMessage = e.message;
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data = response.data.token.token;
}
return result;
}
// Performs graqhQL request.
// Returns Token, User objects.
// Throws an exception if error occurs
// Returns User object.
export async function getUserRequest(): Promise<RequestResponse<User>> {
let result: RequestResponse<User> = {
errorMessage: '',
@ -184,38 +168,33 @@ export async function getUserRequest(): Promise<RequestResponse<User>> {
}
};
try {
let response: any = await apolloManager.query(
{
query: gql(`
query {
user {
fullName,
shortName,
email,
}
}`
),
fetchPolicy: 'no-cache',
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data = response.data.user;
let response: any = await apolloManager.query(
{
query: gql(`
query {
user {
fullName,
shortName,
email,
}
}`
),
fetchPolicy: 'no-cache',
errorPolicy: 'all',
}
} catch (e) {
result.errorMessage = e.message;
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data = response.data.user;
}
return result;
}
// Performs graqhQL request.
// User object.
// Throws an exception if error occurs
export async function deleteAccountRequest(password: string): Promise<RequestResponse<null>> {
let result: RequestResponse<null> = {
errorMessage: '',
@ -223,27 +202,24 @@ export async function deleteAccountRequest(password: string): Promise<RequestRes
data: null
};
try {
let response = await apolloManager.mutate(
{
mutation: gql(`
mutation {
deleteAccount(password: "${password}") {
email
}
}`
),
fetchPolicy: 'no-cache'
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
let response = await apolloManager.mutate(
{
mutation: gql(`
mutation {
deleteAccount(password: "${password}") {
email
}
}`
),
fetchPolicy: 'no-cache',
errorPolicy: 'all',
}
} catch (e) {
result.errorMessage = e.message;
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
}
return result;

View File

@ -48,6 +48,7 @@ import { APP_STATE_ACTIONS, USER_ACTIONS, NOTIFICATION_ACTIONS } from '@/utils/c
password: '',
passwordError: '',
imageSource: EMPTY_STATE_IMAGES.DELETE_ACCOUNT,
isLoading: false,
};
},
methods: {
@ -55,16 +56,25 @@ import { APP_STATE_ACTIONS, USER_ACTIONS, NOTIFICATION_ACTIONS } from '@/utils/c
this.$data.password = value;
},
onDeleteAccountClick: async function() {
if (this.$data.isLoading) {
return;
}
this.$data.isLoading = true;
let response = await this.$store.dispatch(USER_ACTIONS.DELETE, this.$data.password);
if (!response.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, response.errorMessage);
this.$data.isLoading = false;
return;
}
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Account was successfully deleted');
removeToken();
this.$data.isLoading = false;
this.$router.push('/login');
},
onCloseClick: function (): void {

View File

@ -54,6 +54,7 @@ Vue.use(VueClipboards);
imageSource: EMPTY_STATE_IMAGES.ADD_API_KEY,
name: '',
key: '',
isLoading: false,
};
},
methods: {
@ -61,16 +62,25 @@ Vue.use(VueClipboards);
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_NEW_API_KEY);
},
onCreateClick: async function (): Promise<any> {
if (this.$data.isLoading) {
return;
}
this.$data.isLoading = true;
let result: any = await this.$store.dispatch(API_KEYS_ACTIONS.CREATE, this.$data.name);
if (!result.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, result.errorMessage);
this.$data.isLoading = false;
return;
}
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Successfully created new api key');
this.$data.key = result.data.key;
this.$data.isLoading = false;
},
onChangeName: function (value: string): void {
this.$data.name = value;

View File

@ -3,7 +3,7 @@
<template>
<div class="new-project-container">
<div class="new-project-button-container" v-on:click="toggleSelection" id="newProjectButton">
<div class="new-project-button-container" :class="{ active: !hasProjects }" v-on:click="toggleSelection" id="newProjectButton">
<h1>New Project +</h1>
</div>
<NewProjectPopup v-if="isPopupShown"/>
@ -28,7 +28,12 @@ import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
NewProjectPopup
},
computed: mapState({
isPopupShown: (state: any) => state.appStateModule.appState.isNewProjectPopupShown,
isPopupShown: function (state: any): boolean {
return state.appStateModule.appState.isNewProjectPopupShown;
},
hasProjects: function (state: any): boolean {
return state.projectsModule.projects.length;
}
}),
}
)
@ -74,4 +79,17 @@ export default class NewProjectArea extends Vue {
}
}
}
.new-project-button-container.active {
background-color: #2683FF;
border: 1px solid #2683FF;
box-shadow: 0px 4px 20px rgba(35, 121, 236, 0.4);
h1 {
color: white;
}
&:hover {
box-shadow: none;
}
}
</style>

View File

@ -3,7 +3,8 @@
<template>
<div class="project-selection-container" id="projectDropdownButton">
<div class="project-selection-toggle-container" v-on:click="toggleSelection">
<p class="project-selection-container__no-projects-text" v-if="!hasProjects">You have no projects</p>
<div class="project-selection-toggle-container" v-on:click="toggleSelection" v-if="hasProjects">
<h1>{{name}}</h1>
<div class="project-selection-toggle-container__expander-area">
<img v-if="!isDropdownShown" src="../../../../static/images/register/BlueExpand.svg"/>
@ -40,7 +41,10 @@ import { APP_STATE_ACTIONS, PROJETS_ACTIONS, NOTIFICATION_ACTIONS } from '@/util
return selectedProject.id ? selectedProject.name : 'Choose project';
},
isDropdownShown: (state: any) => state.appStateModule.appState.isProjectsDropdownShown
isDropdownShown: (state: any) => state.appStateModule.appState.isProjectsDropdownShown,
hasProjects: function (state: any): boolean {
return state.projectsModule.projects.length;
}
}),
components: {
ProjectSelectionDropdown
@ -60,6 +64,15 @@ export default class ProjectSelectionArea extends Vue {
background-color: #FFFFFF;
cursor: pointer;
&__no-projects-text {
font-family: 'font_medium';
font-size: 16px;
line-height: 23px;
color: #354049;
opacity: 0.7;
cursor: default !important;
}
h1 {
font-family: 'font_medium';
font-size: 16px;

View File

@ -33,6 +33,7 @@ import AddUserPopup from '@/components/team/AddUserPopup.vue';
},
computed: mapState({
isAddTeamMembersPopupShown: (state: any) => state.appStateModule.appState.isAddTeamMembersPopupShown,
isProjectNotSelected: (state: any) => state.projectsModule.selectedProject.id === '',
}),
}
)

View File

@ -22,11 +22,11 @@
<path d="M60.92 3.11795V0H69V10.3385C69 12.2256 68.52 13.6205 67.56 14.6051C66.6 15.5077 65.4 16 63.88 16C62.04 16 60.36 15.2615 59 13.7846L60.76 11.241C61.72 12.3077 62.68 12.8 63.72 12.8C64.2 12.8 64.6 12.6359 65 12.2256C65.32 11.8154 65.56 11.241 65.56 10.5026V3.0359L60.92 3.11795Z" fill="#2683FF"/>
</svg>
</div>
<router-link class="navigation-area__item-container" v-for="navItem in navigation" v-bind:key="navItem.label" :to="navItem.path">
<router-link class="navigation-area__item-container" v-bind:class="{disabled: isProjectNotSelected && navItem.path !== '/project-details'}" v-for="navItem in navigation" v-bind:key="navItem.label" :to="navItem.path">
<div class="navigation-area__item-container__link-container" >
<div v-html="navItem.svg"></div>
<h1>{{navItem.label}}</h1>
<div class="navigation-area__item-container__link-container__add-button" id="addTeamMemberPopupButtonSVG" v-if="navItem.label == 'Team'">
<div class="navigation-area__item-container__link-container__add-button" id="addTeamMemberPopupButtonSVG" v-if="navItem.label === 'Team'">
<div v-on:click="togglePopup">
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="20" fill="#2683FF"/>

View File

@ -102,6 +102,11 @@ a {
outline: none;
}
.disabled {
pointer-events:none;
opacity:0.6;
}
@media screen and (max-width: 1024px) {
.navigation-area {
width: 80px;

View File

@ -1,7 +1,7 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
// This component needs to be improved in future
// TODO: temporary file
<style scoped lang="scss">
.datepicker-overlay {
position: fixed;

View File

@ -48,6 +48,7 @@ import { Component, Vue } from 'vue-property-decorator';
import Button from '@/components/common/Button.vue';
import { EMPTY_STATE_IMAGES } from '@/utils/constants/emptyStatesImages';
import { PROJETS_ACTIONS, NOTIFICATION_ACTIONS, PM_ACTIONS, APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { API_KEYS_ACTIONS } from '@/utils/constants/actionNames';
@Component(
{
@ -56,6 +57,7 @@ import { PROJETS_ACTIONS, NOTIFICATION_ACTIONS, PM_ACTIONS, APP_STATE_ACTIONS }
projectName: '',
nameError: '',
imageSource: EMPTY_STATE_IMAGES.DELETE_PROJECT,
isLoading: false,
};
},
methods: {
@ -63,8 +65,15 @@ import { PROJETS_ACTIONS, NOTIFICATION_ACTIONS, PM_ACTIONS, APP_STATE_ACTIONS }
this.$data.nameError = '';
},
onDeleteProjectClick: async function (): Promise<any> {
if (this.$data.isLoading) {
return;
}
this.$data.isLoading = true;
if (this.$data.projectName !== this.$store.getters.selectedProject.name) {
this.$data.nameError = 'Name doesn\'t match with current project name';
this.$data.isLoading = false;
return;
}
@ -76,22 +85,43 @@ import { PROJETS_ACTIONS, NOTIFICATION_ACTIONS, PM_ACTIONS, APP_STATE_ACTIONS }
if (!response.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Error during project deletion');
this.$data.isLoading = false;
return;
}
this.$store.dispatch(PM_ACTIONS.CLEAR);
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Project was successfully deleted');
this.$store.dispatch(PROJETS_ACTIONS.FETCH);
this.$router.push('/');
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_DEL_PROJ);
if (this.$store.state.projectsModule.projects.length > 0) {
this.$store.dispatch(
PROJETS_ACTIONS.SELECT,
this.$store.state.projectsModule.projects[0].id,
);
const pmResponse = await this.$store.dispatch(PM_ACTIONS.FETCH);
const keysResponse = await this.$store.dispatch(API_KEYS_ACTIONS.FETCH);
if (!pmResponse.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch project members');
}
if (!keysResponse.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch api keys');
}
}
this.$data.isLoading = false;
},
onCloseClick: function (): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_DEL_PROJ);
}
},
},
computed: {
isDeleteButtonDisabled: function (): boolean {
return (this.$data.projectName === '' || this.$data.nameError !== '');
},
},

View File

@ -62,6 +62,7 @@
nameError: '',
createdProjectId: '',
self: null,
isLoading: false,
};
},
methods: {
@ -76,11 +77,21 @@
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_NEW_PROJ);
},
createProjectClick: async function (): Promise<any> {
if (this.$data.isLoading) {
return;
}
this.$data.isLoading = true;
if (!this.$data.self.validateProjectName(this.$data.projectName)) {
this.$data.isLoading = false;
return;
}
if (!await this.$data.self.createProject()) {
this.$data.isLoading = false;
return;
}
@ -89,6 +100,8 @@
this.$data.self.fetchProjectMembers();
this.$data.self.checkIfsFirstProject();
this.$data.isLoading = false;
},
validateProjectName: function(): boolean {
this.$data.projectName = this.$data.projectName.trim();

View File

@ -36,22 +36,6 @@
</div>
</div>
</div>
<!--Commented out section for future purpose-->
<!--<div class="project-details-info-container" >-->
<!--<div class="project-details-info-container__portability-container">-->
<!--<div class="project-details-info-container__portability-container__info">-->
<!--<img src="../../../static/images/projectDetails/Portability.png" alt="">-->
<!--<div class="project-details-info-container__portability-container__info__text">-->
<!--<h4>Data Portability</h4>-->
<!--<h2>Backup project data to recover or move between Satellites</h2>-->
<!--</div>-->
<!--</div>-->
<!--<div class="project-details-info-container__portability-container__buttons-area">-->
<!--<Button label="Export" width="170px" height="48px" :onPress="onExportClick" isWhite/>-->
<!--<Button label="Import" width="170px" height="48px" :onPress="onImportClick"/>-->
<!--</div>-->
<!--</div>-->
<!--</div>-->
<div class="project-details-info-container" >
<div class="project-details-info-container__usage-report-container">
<div class="project-details-info-container__usage-report-container__info">
@ -97,7 +81,6 @@ import DeleteProjectPopup from '@/components/project/DeleteProjectPopup.vue';
isEditing: false,
newDescription: '',
emptyImage: EMPTY_STATE_IMAGES.PROJECT,
additionalEmptyText:'Please click the button {{<b>New Project</b>}} in the right corner'
};
},
methods: {
@ -135,11 +118,9 @@ import DeleteProjectPopup from '@/components/project/DeleteProjectPopup.vue';
},
computed: {
name: function (): string {
return this.$store.getters.selectedProject.name;
},
description: function (): string {
return this.$store.getters.selectedProject.description ?
this.$store.getters.selectedProject.description :
'No description yet. Please enter some information about the project if any.';
@ -147,11 +128,9 @@ import DeleteProjectPopup from '@/components/project/DeleteProjectPopup.vue';
// this computed is used to indicate if project is selected.
// if false - we should change UI
isProjectSelected: function (): boolean {
return this.$store.getters.selectedProject.id !== '';
},
isPopupShown: function (): boolean {
return this.$store.state.appStateModule.appState.isDeleteProjectPopupShown;
}
},

View File

@ -82,10 +82,17 @@ import { validateEmail } from '@/utils/validation';
formError: '',
imageSource: EMPTY_STATE_IMAGES.ADD_USER,
imageDeleteUser: EMPTY_STATE_IMAGES.DELETE_USER,
isLoading: false,
};
},
methods: {
onAddUsersClick: async function() {
if (this.$data.isLoading) {
return;
}
this.$data.isLoading = true;
let length = this.$data.inputs.length;
let newInputsArray: any[] = [];
let areAllEmailsValid = true;
@ -124,12 +131,17 @@ import { validateEmail } from '@/utils/validation';
}
}
if (!areAllEmailsValid) return;
if (!areAllEmailsValid) {
this.$data.isLoading = false;
return;
}
let result = await this.$store.dispatch(PM_ACTIONS.ADD, emailArray);
if (!result.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Error during adding team members!');
this.$data.isLoading = false;
return;
}
@ -138,6 +150,7 @@ import { validateEmail } from '@/utils/validation';
if (!response.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch project members');
this.$data.isLoading = false;
return;
}
@ -151,6 +164,8 @@ import { validateEmail } from '@/utils/validation';
}
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_TEAM_MEMBERS);
this.$data.isLoading = false;
},
addInput: function(): void {
let inputsLength = this.$data.inputs.length;

View File

@ -31,9 +31,9 @@
this.$store.dispatch(PM_ACTIONS.SET_SEARCH_QUERY, this.$data.searchQuery);
const response = await this.$store.dispatch(PM_ACTIONS.FETCH);
if (response.isSuccess) return;
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch project members');
if (!response.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch project members');
}
},
clearSearch: function () {
this.$data.searchQuery = '';

View File

@ -9,7 +9,7 @@ import Register from '@/views/register/Register.vue';
import ForgotPassword from '@/views/forgotPassword/ForgotPassword.vue';
import Dashboard from '@/views/Dashboard.vue';
import AccountArea from '@/components/account/AccountArea.vue';
import ProjectDetails from '@/components/project/ProjectDetailsArea.vue';
import ProjectDetailsArea from '@/components/project/ProjectDetailsArea.vue';
import TeamArea from '@/components/team/TeamArea.vue';
import Page404 from '@/components/errors/Page404.vue';
import ApiKeysArea from '@/components/apiKeys/ApiKeysArea.vue';
@ -17,6 +17,7 @@ import UsageReport from '@/components/project/UsageReport.vue';
import ReportTable from '@/components/project/ReportTable.vue';
import BucketArea from '@/components/buckets/BucketArea.vue';
import { getToken } from '@/utils/tokenManager';
import store from '@/store';
Vue.use(Router);
@ -40,7 +41,6 @@ let router = new Router({
},
{
path: ROUTES.DASHBOARD.path,
name: ROUTES.DASHBOARD.name,
meta: {
requiresAuth: true
},
@ -54,7 +54,13 @@ let router = new Router({
{
path: ROUTES.PROJECT_DETAILS.path,
name: ROUTES.PROJECT_DETAILS.name,
component: ProjectDetails,
component: ProjectDetailsArea
},
// Remove when dashboard will be created
{
path: '/',
name: 'default',
component: ProjectDetailsArea
},
{
path: ROUTES.TEAM.path,
@ -71,11 +77,11 @@ let router = new Router({
name: ROUTES.USAGE_REPORT.name,
component: UsageReport,
},
{
path: ROUTES.BUCKETS.path,
name: ROUTES.BUCKETS.name,
component: BucketArea
},
// {
// path: ROUTES.BUCKETS.path,
// name: ROUTES.BUCKETS.name,
// component: BucketArea
// },
// {
// path: '/',
// name: 'dashboardArea',
@ -97,7 +103,14 @@ let router = new Router({
});
// Makes check that Token exist at session storage before any route except Login and Register
// and if we are able to navigate to page without existing project
router.beforeEach((to, from, next) => {
if (isUnavailablePageWithoutProject(to.name as string)) {
next(ROUTES.PROJECT_DETAILS);
return;
}
if (to.matched.some(route => route.meta.requiresAuth)) {
if (!getToken()) {
next(ROUTES.LOGIN);
@ -109,4 +122,14 @@ router.beforeEach((to, from, next) => {
next();
});
// isUnavailablePageWithoutProject checks if we are able to navigate to page without existing project
function isUnavailablePageWithoutProject(pageName: string): boolean {
let unavailablePages: string[] = [ROUTES.TEAM.name, ROUTES.API_KEYS.name];
const state = store.state as any;
let isProjectSelected = state.projectsModule.selectedProject.id !== '';
return unavailablePages.includes(pageName) && !isProjectSelected;
}
export default router;

View File

@ -3,11 +3,13 @@
import { APP_STATE_MUTATIONS } from '../mutationConstants';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { AppState } from '@/utils/constants/appStateEnum';
export const appStateModule = {
state: {
// Object that contains all states of views
appState: {
fetchState: AppState.LOADING,
isAddTeamMembersPopupShown: false,
isNewProjectPopupShown: false,
isProjectsDropdownShown: false,
@ -78,52 +80,55 @@ export const appStateModule = {
state.appState.isSuccessfulRegistrationPopupShown = false;
state.appState.isSuccessfulProjectCreationPopupShown = false;
},
[APP_STATE_MUTATIONS.CHANGE_STATE](state: any, newFetchState: AppState): void {
state.appState.fetchState = newFetchState;
},
},
actions: {
// Commits mutation for changing app popups and dropdowns visibility state
toggleAddTeamMembersPopup: function ({commit, state}: any): void {
[APP_STATE_ACTIONS.TOGGLE_TEAM_MEMBERS]: function ({commit, state}: any): void {
if (!state.appState.isAddTeamMembersPopupShown) {
commit(APP_STATE_MUTATIONS.CLOSE_ALL);
}
commit(APP_STATE_MUTATIONS.TOGGLE_ADD_TEAMMEMBER_POPUP);
},
toggleNewProjectPopup: function ({commit, state}: any): void {
[APP_STATE_ACTIONS.TOGGLE_NEW_PROJ]: function ({commit, state}: any): void {
if (!state.appState.isNewProjectPopupShown) {
commit(APP_STATE_MUTATIONS.CLOSE_ALL);
}
commit(APP_STATE_MUTATIONS.TOGGLE_NEW_PROJECT_POPUP);
},
toggleProjectsDropdown: function ({commit, state}: any): void {
[APP_STATE_ACTIONS.TOGGLE_PROJECTS]: function ({commit, state}: any): void {
if (!state.appState.isProjectsDropdownShown) {
commit(APP_STATE_MUTATIONS.CLOSE_ALL);
}
commit(APP_STATE_MUTATIONS.TOGGLE_PROJECT_DROPDOWN);
},
toggleAccountDropdown: function ({commit, state}: any): void {
[APP_STATE_ACTIONS.TOGGLE_ACCOUNT]: function ({commit, state}: any): void {
if (!state.appState.isAccountDropdownShown) {
commit(APP_STATE_MUTATIONS.CLOSE_ALL);
}
commit(APP_STATE_MUTATIONS.TOGGLE_ACCOUNT_DROPDOWN);
},
toggleDeleteProjectPopup: function ({commit, state}: any): void {
[APP_STATE_ACTIONS.TOGGLE_DEL_PROJ]: function ({commit, state}: any): void {
if (!state.appState.isDeleteProjectPopupShown) {
commit(APP_STATE_MUTATIONS.CLOSE_ALL);
}
commit(APP_STATE_MUTATIONS.TOGGLE_DELETE_PROJECT_DROPDOWN);
},
toggleDeleteAccountPopup: function ({commit, state}: any): void {
[APP_STATE_ACTIONS.TOGGLE_DEL_ACCOUNT]: function ({commit, state}: any): void {
if (!state.appState.isDeleteAccountPopupShown) {
commit(APP_STATE_MUTATIONS.CLOSE_ALL);
}
commit(APP_STATE_MUTATIONS.TOGGLE_DELETE_ACCOUNT_DROPDOWN);
},
toggleSortProjectMembersByPopup: function ({commit, state}: any): void {
[APP_STATE_ACTIONS.TOGGLE_SORT_PM_BY_DROPDOWN]: function ({commit, state}: any): void {
if (!state.appState.isSortProjectMembersByPopupShown) {
commit(APP_STATE_MUTATIONS.CLOSE_ALL);
}
@ -151,8 +156,11 @@ export const appStateModule = {
commit(APP_STATE_MUTATIONS.TOGGLE_SUCCESSFUL_PROJECT_CREATION_POPUP);
},
closePopups: function ({commit}: any): void {
[APP_STATE_ACTIONS.CLOSE_POPUPS]: function ({commit}: any): void {
commit(APP_STATE_MUTATIONS.CLOSE_ALL);
},
[APP_STATE_ACTIONS.CHANGE_STATE]: function ({commit}: any, newFetchState: AppState): void {
commit(APP_STATE_MUTATIONS.CHANGE_STATE, newFetchState);
},
},
};

View File

@ -23,6 +23,10 @@ export const notificationsModule = {
},
// Mutaion for deleting notification to queue
[NOTIFICATION_MUTATIONS.DELETE](state: any): void {
if (state.notificationQueue.length < 1) {
return;
}
state.notificationQueue[0].pause();
state.notificationQueue.shift();

View File

@ -71,9 +71,7 @@ export const projectMembersModule = {
addProjectMembers: async function ({rootGetters}: any, emails: string[]): Promise<RequestResponse<null>> {
const projectId = rootGetters.selectedProject.id;
const response = await addProjectMembersRequest(projectId, emails);
return response;
return await addProjectMembersRequest(projectId, emails);
},
deleteProjectMembers: async function ({commit, rootGetters}: any, projectMemberEmails: string[]): Promise<RequestResponse<null>> {
const projectId = rootGetters.selectedProject.id;

View File

@ -4,17 +4,18 @@
import { PROJECTS_MUTATIONS } from '../mutationConstants';
import { createProjectRequest, deleteProjectRequest, fetchProjectsRequest, updateProjectRequest } from '@/api/projects';
let defaultSelectedProject: Project = {
name: '',
id: '',
description: '',
createdAt: '',
isSelected: true,
};
export const projectsModule = {
state: {
projects: [],
selectedProject: {
name: 'Choose Project',
id: '',
companyName: '',
description: '',
isTermsAccepted: false,
createdAt: '',
}
selectedProject: defaultSelectedProject
},
mutations: {
[PROJECTS_MUTATIONS.CREATE](state: any, createdProject: Project): void {
@ -25,7 +26,6 @@ export const projectsModule = {
},
[PROJECTS_MUTATIONS.SELECT](state: any, projectID: string): void {
const selected = state.projects.find((project: any) => project.id === projectID);
if (!selected) {
return;
}
@ -39,26 +39,17 @@ export const projectsModule = {
}
selected.description = updateProjectModel.description;
if (state.selectedProject.id === updateProjectModel.id) {
state.selectedProject.description = updateProjectModel.description;
}
},
[PROJECTS_MUTATIONS.DELETE](state: any, projectID: string): void {
state.projects = state.projects.filter(proj => proj.id !== projectID);
if (state.selectedProject.id === projectID) {
state.selectedProject.id = '';
state.selectedProject = defaultSelectedProject;
}
},
[PROJECTS_MUTATIONS.CLEAR](state: any): void {
state.projects = [];
state.selectedProject = {
name: 'Choose Project',
id: '',
companyName: '',
description: '',
isTermsAccepted: false,
createdAt: '',
};
state.selectedProject = defaultSelectedProject;
},
},
actions: {

View File

@ -64,4 +64,5 @@ export const APP_STATE_MUTATIONS = {
TOGGLE_SUCCESSFUL_REGISTRATION_POPUP: 'TOGGLE_SUCCESSFUL_REGISTRATION_POPUP',
TOGGLE_SUCCESSFUL_PROJECT_CREATION_POPUP: 'TOGGLE_SUCCESSFUL_PROJECT_CREATION_POPUP',
CLOSE_ALL: 'CLOSE_ALL',
CHANGE_STATE: 'CHANGE_STATE',
};

View File

@ -10,7 +10,6 @@ import { getToken } from '@/utils/tokenManager';
// Satellite url
const satelliteUrl = new HttpLink({
uri: '/api/graphql/v0',
});
// Adding auth headers

View File

@ -13,6 +13,7 @@ export const APP_STATE_ACTIONS = {
TOGGLE_SUCCESSFUL_REGISTRATION_POPUP: 'toggleSuccessfulRegistrationPopup',
TOGGLE_SUCCESSFUL_PROJECT_CREATION_POPUP: 'toggleSuccessfulProjectCreationPopup',
CLOSE_POPUPS: 'closePopups',
CHANGE_STATE: 'changeFetchState',
};
export const NOTIFICATION_ACTIONS = {

View File

@ -0,0 +1,9 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
export enum AppState {
LOADING = 1,
LOADED,
LOADED_EMPTY,
ERROR,
}

View File

@ -58,15 +58,15 @@ const NAVIGATION_ITEMS = {
</defs>
</svg>`
},
BUCKETS: {
label: 'Buckets',
path: ROUTES.BUCKETS.path,
svg: `<svg class="svg" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 12.4548V20.273C1 21.273 1.81116 22.0003 2.71245 22.0003H20.1974C21.1888 22.0003 21.9099 21.1821 21.9099 20.273V12.4548C21.9099 11.4548 21.0987 10.7275 20.1974 10.7275H2.71245C1.81116 10.7275 1 11.4548 1 12.4548ZM14.97 14.0912H7.75966C7.30901 14.0912 6.85837 13.7275 6.85837 13.1821C6.85837 12.7275 7.21888 12.273 7.75966 12.273H14.97C15.4206 12.273 15.8712 12.6366 15.8712 13.1821C15.8712 13.7275 15.5107 14.0912 14.97 14.0912Z" fill="#354049"/>
<path d="M2.53227 9.81792C2.17175 9.81792 1.90137 9.54519 1.90137 9.18155V5.90882C1.90137 5.54519 2.17175 5.27246 2.53227 5.27246H20.4679C20.8284 5.27246 21.0988 5.54519 21.0988 5.90882V8.99973C21.0988 9.36337 20.8284 9.6361 20.4679 9.6361C20.1074 9.6361 19.837 9.36337 19.837 8.99973V6.54519H3.16317V9.18155C3.16317 9.54519 2.89278 9.81792 2.53227 9.81792Z" fill="#354049"/>
<path d="M20.4679 4.27273H2.53227C2.17175 4.27273 1.90137 4 1.90137 3.63636C1.90137 3.27273 2.17175 3 2.53227 3H20.4679C20.8284 3 21.0988 3.27273 21.0988 3.63636C21.0988 4 20.8284 4.27273 20.4679 4.27273Z" fill="#354049"/>
</svg>`
},
// BUCKETS: {
// label: 'Buckets',
// path: ROUTES.BUCKETS.path,
// svg: `<svg class="svg" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
// <path d="M1 12.4548V20.273C1 21.273 1.81116 22.0003 2.71245 22.0003H20.1974C21.1888 22.0003 21.9099 21.1821 21.9099 20.273V12.4548C21.9099 11.4548 21.0987 10.7275 20.1974 10.7275H2.71245C1.81116 10.7275 1 11.4548 1 12.4548ZM14.97 14.0912H7.75966C7.30901 14.0912 6.85837 13.7275 6.85837 13.1821C6.85837 12.7275 7.21888 12.273 7.75966 12.273H14.97C15.4206 12.273 15.8712 12.6366 15.8712 13.1821C15.8712 13.7275 15.5107 14.0912 14.97 14.0912Z" fill="#354049"/>
// <path d="M2.53227 9.81792C2.17175 9.81792 1.90137 9.54519 1.90137 9.18155V5.90882C1.90137 5.54519 2.17175 5.27246 2.53227 5.27246H20.4679C20.8284 5.27246 21.0988 5.54519 21.0988 5.90882V8.99973C21.0988 9.36337 20.8284 9.6361 20.4679 9.6361C20.1074 9.6361 19.837 9.36337 19.837 8.99973V6.54519H3.16317V9.18155C3.16317 9.54519 2.89278 9.81792 2.53227 9.81792Z" fill="#354049"/>
// <path d="M20.4679 4.27273H2.53227C2.17175 4.27273 1.90137 4 1.90137 3.63636C1.90137 3.27273 2.17175 3 2.53227 3H20.4679C20.8284 3 21.0988 3.27273 21.0988 3.63636C21.0988 4 20.8284 4.27273 20.4679 4.27273Z" fill="#354049"/>
// </svg>`
// },
// HELP: {
// label: 'Help',
// path: '/help',

View File

@ -1,14 +1,20 @@
import {AppState} from "../utils/constants/appStateEnum";
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="dashboard-container">
<div v-if="isLoading" class="loading-overlay active">
<img src="../../static/images/register/Loading.gif">
</div>
<div class="dashboard-container__wrap">
<NavigationArea />
<div class="dashboard-container__wrap__column">
<DashboardHeader />
<div class="dashboard-container__main-area">
<router-view />
<keep-alive>
<router-view />
</keep-alive>
</div>
</div>
</div>
@ -17,64 +23,71 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import DashboardHeader from '@/components/header/Header.vue';
import NavigationArea from '@/components/navigation/NavigationArea.vue';
import { removeToken, setToken } from '@/utils/tokenManager';
import {
NOTIFICATION_ACTIONS,
PROJETS_ACTIONS,
PM_ACTIONS,
USER_ACTIONS,
API_KEYS_ACTIONS,
PROJECT_USAGE_ACTIONS
} from '@/utils/constants/actionNames';
import ROUTES from '@/utils/constants/routerConstants';
import ProjectCreationSuccessPopup from '@/components/project/ProjectCreationSuccessPopup.vue';
import { Component, Vue } from 'vue-property-decorator';
import DashboardHeader from '@/components/header/Header.vue';
import NavigationArea from '@/components/navigation/NavigationArea.vue';
import { removeToken } from '@/utils/tokenManager';
import {
API_KEYS_ACTIONS,
APP_STATE_ACTIONS,
NOTIFICATION_ACTIONS,
PM_ACTIONS,
PROJETS_ACTIONS,
USER_ACTIONS,
PROJECT_USAGE_ACTIONS,
} from '@/utils/constants/actionNames';
import ROUTES from '@/utils/constants/routerConstants';
import ProjectCreationSuccessPopup from '@/components/project/ProjectCreationSuccessPopup.vue';
import { AppState } from '../utils/constants/appStateEnum';
@Component({
beforeMount: async function() {
// TODO: should place here some animation while all needed data is fetching
let response: RequestResponse<User> = await this.$store.dispatch(USER_ACTIONS.GET);
@Component({
mounted: async function() {
setTimeout(async () => {
let response: RequestResponse<User> = await this.$store.dispatch(USER_ACTIONS.GET);
if (!response.isSuccess) {
this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.ERROR);
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, response.errorMessage);
this.$router.push(ROUTES.LOGIN);
removeToken();
if (!response.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, response.errorMessage);
this.$router.push(ROUTES.LOGIN);
removeToken();
return;
}
return;
}
let getProjectsResponse: RequestResponse<Project[]> = await this.$store.dispatch(PROJETS_ACTIONS.FETCH);
let getProjectsResponse: RequestResponse<Project[]> = await this.$store.dispatch(PROJETS_ACTIONS.FETCH);
if (!getProjectsResponse.isSuccess || getProjectsResponse.data.length < 1) {
this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADED_EMPTY);
if (!getProjectsResponse.isSuccess || getProjectsResponse.data.length < 1) {
return;
}
return;
}
this.$store.dispatch(PROJETS_ACTIONS.SELECT, getProjectsResponse.data[0].id);
this.$store.dispatch(PROJETS_ACTIONS.SELECT, getProjectsResponse.data[0].id);
const projectMembersResponse = await this.$store.dispatch(PM_ACTIONS.FETCH);
if (!projectMembersResponse.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch project members');
}
if (!this.$store.getters.selectedProject.id) return;
const keysResponse = await this.$store.dispatch(API_KEYS_ACTIONS.FETCH);
if (!keysResponse.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch api keys');
}
this.$store.dispatch(PM_ACTIONS.SET_SEARCH_QUERY, '');
const currentDate = new Date();
const previousDate = new Date();
previousDate.setMonth(currentDate.getMonth() - 1);
const projectMembersResponse = await this.$store.dispatch(PM_ACTIONS.FETCH);
if (!projectMembersResponse.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch project members');
}
const usageResponse = await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH, {startDate: previousDate, endDate: currentDate});
if (!usageResponse.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch project usage');
}
const keysResponse = await this.$store.dispatch(API_KEYS_ACTIONS.FETCH);
if (!keysResponse.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch api keys');
}
const currentDate = new Date();
const previousDate = new Date();
previousDate.setMonth(currentDate.getMonth() - 1);
const usageResponse = await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH, {startDate: previousDate, endDate: currentDate});
if (!usageResponse.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch project usage');
this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADED);
}, 800);
},
computed: {
isLoading: function() {
return this.$store.state.appStateModule.appState.fetchState === AppState.LOADING;
}
},
components: {
@ -121,4 +134,32 @@ export default class Dashboard extends Vue {
}
}
}
.loading-overlay {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 0;
left: 0;
right: 0;
left: 0;
height: 100vh;
z-index: 100;
background-color: rgba(134, 134, 148, 1);
visibility: hidden;
opacity: 0;
-webkit-transition: all 0.5s linear;
-moz-transition: all 0.5s linear;
-o-transition: all 0.5s linear;
transition: all 0.5s linear;
img {
z-index: 200;
}
}
.loading-overlay.active {
visibility: visible;
opacity: 1;
}
</style>

View File

@ -1,4 +1,4 @@
// Copyright (C) 2019 Storj Labs, Inc.
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template src="./login.html"></template>
@ -9,9 +9,10 @@ import HeaderlessInput from '../../components/common/HeaderlessInput.vue';
import Button from '../../components/common/Button.vue';
import { setToken } from '../../utils/tokenManager';
import ROUTES from '../../utils/constants/routerConstants';
import { NOTIFICATION_ACTIONS } from '../../utils/constants/actionNames';
import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS } from '../../utils/constants/actionNames';
import { getTokenRequest } from '../../api/users';
import { LOADING_CLASSES } from '../../utils/constants/classConstants';
import { AppState } from '../../utils/constants/appStateEnum';
@Component({
data: function () {
@ -53,7 +54,8 @@ import { LOADING_CLASSES } from '../../utils/constants/classConstants';
}
setToken(loginResponse.data);
this.$router.push(ROUTES.DASHBOARD.path);
this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADING);
this.$router.push(ROUTES.PROJECT_DETAILS.path);
},
onSignUpClick: function (): void {
this.$router.push(ROUTES.REGISTER.path);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -36,6 +36,8 @@
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
"node_modules",
// TODO: temporary file
"src/components/project/DatePicker.vue"
]
}

View File

@ -5,7 +5,9 @@
],
"linterOptions": {
"exclude": [
"node_modules/**"
"node_modules/**",
// TODO: temporary file
"src/components/project/DatePicker.vue"
]
},
"rules": {